本人现在每日一道算法题的Github地址。
https://github.com/Jensenczx/...
附带OJ地址和本人的代码
博客新址,这里更有趣
序言
昨日阿里二面,跪的很惨,项目,算法,计算机基础不问,问Linux内核,我是投的android实习岗,要求是对于android底层有很深厚的基础,问了binder的实现机制,activity栈的管理回退等等,这都是android高级工程师进阶的书里才会讲的东西,这让我很慌呀。啃底层,再战。
讲底层,先从系统启动流程开始,这个过程发生了什么,然后逐步分析这个每个流程又做了些什么,在我们的系统中起到一个什么作用。不打算深入代码细节去看,当然能力也是不够的,从整体上有个森林。
启动过程
先对启动的过程来一个概览,因为android是基于Linux,所以这里从Linux内核的启动开始到android中的相应服务的启动,对于具体应用的开启下面讲。
根据这个图,讲一下整个流程。
当我们开机的时候,此时通电了,此时会产生一个确定的复位时序,保证CPU是第一个被复位的器件,然后CPU开始执行第一条指令,该指令是位于固定的内存地址上的,所谓的引导程序。
BIOS从我们的磁盘中寻找我们操作系统引导程序,将引导程序加载到内存中,执行,然后将我们的Linux内核加载到内存中。
启动init进程,这个进程是用户态所有进程的祖先,内核态是叫做内核线程和用户进程差不多。()
init进程首先通过fork创建子进程,首先是Daemon进程也就是守护进程,包括USB守护进程,Debug进程,无线通信连接守护进程。
fork出Context Manager,通过上图我们可以看出,android系统提供的服务都要向其进行注册,然后其它的进程才可以调用这个服务。
Media Server,这个服务是本地服务,不是Java服务,不需要再通过dalvik虚拟机装载执行,本地服务单独开启了一个进程。这里包括Audio Flinger和Camera Service。
Zygote,所有Android应用程序的祖先,其用来缩短应用程序的加载时间,具体怎么节省,下面详解。
System Server是android系统中的一个核心进程,提供了管理应用程序生命周期,地理位置信息等各种服务。同时其需要将自身注册到Context Manager.但是我们的服务管理器是基于C语言的,所以我们需要JNI本地编程接口。这个时候Activity Manager Service会运行HOME应用。
大致我们已经了解了启动的流程Linux内核,init进程fork出多个进程,zygote用来孵化Java系统服务,同时孵化应用程序。接下来,我们再深入的去看一下。init进程做了什么?zygote是如何被创建出来的。
init进程
init进程是第一个启动的用户进程,其它用户进程都是其子进程,或者其子进程的子进程,,
init进程作用
子进程终止处理。
应用程序访问设备驱动时,生成设备结点文件。
提供属性服务,保存系统运行所需要的环境变量。
init运行
init进程会先注册一些消息处理器,然后是创建并挂载启动所需要的文件目录(socket文件,虚拟内存文件),在dev目录下生成设备节点文件,然后将标准输入,输出,错误输出重定向到这里。创建设备节点文件,创建输出log的文件,同时将错误信息重定向到这里,init进程生成输出设备之后,开始解析init.rc脚本文件,记录init进程执行的功能,init.rc用于通用的环境变量和进程相关的定义,通过函数 iparse_config_file来读取其脚本,读取分析之后,生成服务列表和动作列表。服务列表和动作列表会注册到service_list和action_list中,其为在init进程中声明的全局结构体,调用device_init函数,生成静态设别结点文件,之后,全局属性值的生成在init进程中propertyinit函数中进行初始化,在共享内存区域,创建并初始化属性值,对于全局属性的修改,只有init进程可以修改,当要修改的时候,需要预先向其提交申请,然后init进程通过之后,才会去修改属性值,提交申请的过程会创建一个socket用来接收提交的申请。执行到这系统将Android系统的Logo显示在桌面上。这个时候设置事件处理循环的监视事件,注册在POLL中的文件描述符会在poll函数中等待事件,事件发生,则从poll函数中跳出并处理事件。各种文件描述符都会前来注册。
init.rc文件的处理过程
init.rc文件分析函数,通过read_file函数,parse_config函数,用来分析读入的字符串。AIL ,Android Init Language编写。
init.rc文件大致上分为两个部分,一部分是以“on”关键字开头的动作列表,另一部分是以”service”关键字开头的服务服务列表。
动作列表:主要设置环境变量,生成系统运行所需的文件或目录,修改相应的权限,并挂载和系统运行相关的目录。在挂载文件的时候,主要挂载/system和/data两个目录,两个目录挂载完毕,android根文件系统准备好了。根文件系统大致可分为shell使用程序,system目录(提供库和基础应用),data目录(保存用户应用和数据),Android采用闪存设备,其采用了yaffs2文件系统,启动的时候要挂载到/system和/data目录下,然后是on boot段落,该部分设置应用程序终止条件,应用程序驱动目录和文件权限。为各应用制定OOM调整至,OOM用来监视内核分配给应用程序的内存,当内存不足的时候,应用程序会被终止执行。
服务列表:init.rc脚本文件,service段落用来记录init进程启动的进程,由init进程启动的子进程或者是一次性程序,系统相关的Daemon进程
创建设别节点文件:
和Linux相同,应用程序通过驱动程序访问硬件设备,设备节点文件是设备驱动的逻辑文件,应用程序通过设备节点文件来访问设备驱动程序。
设备节点的两种创建方式,一种,根据预先定义的设备信息,创建设备节点文件,第二种,在系统运行中,当设备插入时,init进程会接收这一事件,为插入设备动态创建设备节点文件。
当设备插入的时候内核会加载相应的驱动程序,而后驱动程序会调用启动函数probe,将主,次设备号类型保存到/sys文件系统中。然后发出uevent,并传递给守护进程,vevent,是内核向用户控件进程传递信息的信号系统,内核通过uevent将信息传递到用户空间,守护进程会根据uevent读取设备信息,创建设备节点文件。对于一些设备,采用冷插拔的方式,监听设备的uevent,然后调用其函数,创建设备节点文件。
何为设备节点文件
Linux对于系统中的设备都会抽象成一个文件,内核为了高效的管理已经被打开的文件,通过一个文件描述符来表示,在Linux中Everything is file,通过这种方式,对于启动的时候,对于文件描述符,0代表标注输入,1表示标准输出,2是错误处理,然后设备文件,socket文件都会获得一个文件描述符来表述它。但是Linux会对其做相应的限制,同时可能会对一些进程进行限制,限制给进程分配多少个文件描述符。
Zygote创建过程
对于Service的创建,在后面讲到Service的时候单独再说,先讲Zygote,因为这是我们的所有应用开启的基石。因为zygote是Java代码,所以需要装载到Dalvik VM上执行,所以在启动Zygote之前要启动Dalvik VM,所以这里要分为两步了,第一步启动Dalvik VM,第二步是启动Zygote。
首先生成了一个AppRuntime对象,该类是继承自AndroidRuntime的,该类用来初始化并且运行Dalvik 虚拟机,为运行Android应用做好准备。接受main函数传递进来的参数,然后初始化虚拟机。
虚拟机初始化之后,运行ZygoteInit类,通过路径查找找到,现在程序的执行转向了虚拟机Java代码的执行,首先执行器main函数,接下来就是Zygote的作用过程了,在下面部分。
Zygote启动应用过程
Zygote执行之后,首先是绑定一个套接字,用来接收从Activity Manager来的应用启动请求(这里我们可以想到网络通讯中的Socket通信)
将应用程序框架中的类,平台资源(图像,XML信息,字符串)预先加载到内存中,借此提升程序的执行速度。Android也是通过在Zygote创建的时候加载资源,生成信息链接等,以后再有应用启动,fork出一个子进程和父进程共享这些信息,而不需要重新加载,同时也共享虚拟机,因为虚拟机在初始化和创建的过程是很耗时的。
前面也提到过一些system server也是java的,而且我们的应用启动等需要这些server的参与,所以在启动应用之前先启动了这些System server,然后启动Server Thread来执行android framework的服务,同时这些服务在启动的时候都是需要通过JNI向服务管理器Context Manager注册。
经历了上述步骤的zygote处在轮询监听Socket,当有请求到达,读取请求,fork出子进程,加载进程所需要的类,然后执行要执行程序的main函数,代码转给了Dalvik Vm,我们的应用程序也就启动起来了。这个时候,将关闭套接字,删除请求描述符,防止出现重复启动。
上面只是对整体流程进行了一个概述,细节涉及不深,这里再提一下,虚拟机,究竟是什么呢?这里的虚拟机可以理解成,可以解析Java字节码的内存上的一条条指令,各个应用共享这个虚拟机,也就是都是知道虚拟机的内存中地址,传递进java字节码,然后解析字节码,将字节码实际代表的操作反应出来,多个应用程序可以交替使用,通过寄存器记录彼此执行后的状态。fork出一个进程之后,我们装载应用程序相应的类,然后执行。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。