推广 热搜: 百度  搜索引擎  企业  可以  使用  选择  page 

Android -- Android Init进程的处理流程分析

   日期:2024-12-31     作者:2kzvw    caijiyuan  
核心提示:最近在看Android Init进程的处理流程,现记录如下。 在Android中,Init进程是Linux内核启动后创建的第一个用户进程&


最近在看Android Init进程的处理流程,现记录如下。

在Android中,Init进程是Linux内核启动后创建的第一个用户进程,地位非常重要。Init进程的可执行文件在/system/core/init/目录下,我们直接看Init进程的main()函数,该函数的代码处理流程较长,我们分两大段来分析。首先看第一大段

函数一开始,会首先判断当前启动程序名是否是ueventd或者watchdogd,如果是,则会走对应的初始化流程后,并终止程序。 

首先,主程序会创建一些需要的目录,并挂载几个文件系统到系统中;其次,会重定向标准输入、标准输出、标准错误流到/dev/__null_设备文件下,并初始化内核Log系统,使我们此时可以输出log(因为此时Android的Log系统还未初始化;接着,处理kernel启动参数,设置系统默认属性,并对SELinux的内容进行一些初始化操作等。

我们这里只看一些重要的跟init.rc文件相关的处理内容,其他的部分可以参考代码中的注释加以理解。

接下来分析第二段重要代码,它包含了init.rc文件解析和init进程如何变成守护进程的操作

epoll机制跟select机制是类似的,两者都可以处理一组fd,监听它们的读写情况,当关注的fd有事件产生时,我们可以进行处理;只不过epoll机制比select机制更高效,所以这里采用了epoll机制进行轮询,而非select。 
我们调用epoll_create()函数创建了一个epoll句柄,并保存到全局变量epoll_fd中。 
接着,我们初始化signal和property处理系统,两函数的处理流程类似:创建、获取一个socket的文件描述符fd,通过epoll_ctl()将有兴趣的fd添加到epoll_fd进行监听。 

先看signal事件部分的处理

调用socketpair()创造一对未命名的、相互连接的UNIX域套接字,接着创建sigaction结构实例,初始化函数句柄、设置标志位,最终设置SIGCHLD信息处理方方式。 

reap_any_outstanding_children()会调用wait_for_one_process()循环等待有进程终止的信号,并对它进行处理,这部分后面再分析。

register_epoll_handler(signal_read_fd, handle_signal)函数就是把我们关注的fd添加到epoll_fd中,让它轮询查询

注意,这里指定了signal事件处理的函数句柄:handle_signal()。 

属性服务事件的监听处理与signal类似

 

 

 

先按设置创建一个socket,并会在/dev/socket目录下创建一个对应的设备文件,接着在此socket上进行绑定并开始监听,这表明此处是服务端,会有客户端连接此socket,并发送属性设置、读取请求;epoll监听到该fd有数据可读时,就会调用注册的函数句柄handle_property_set_fd()处理这个请求。这一部分内容只是将signal、property要监听的fd加入到了epoll_fd中,还未真正开始轮询事件。

再接着看

这是真正解析init.rc文件的函数
先把init.rc配置文件的内容读到内存中,再调用parse_config()函数进行解析
init.rc文件的解析流程如下图描述

  • "service":调用parse_service()初始化一个结构service,并把它添加到service_list列表中去;把行处理函数设置为parse_line_service(),以解析它的Options。
  • "on":调用parse_action()初始化一个action结构,并把它添加到action_list列表中去;把行处理函数设置为parse_line_action(),以解析它的Commands。
  • "import":调用parse_import()初始化一个import结构,并把它添加到import_list列表中去。
这里涉及到了几个链表结构,它们的定义、初始化过程是
 
而listnode结构体的定义是
 

从listnode的定义可知,这三个链表结构都是双向链表;但有一点很奇怪,该链表没有定义数据域,那通过什么方式来获取节点的数据呢?这个在后面会分析。

在看Service、Action的解析过程之前,有必要看下init.rc文件中所使用的指令对应的函数集,这直接影响着各个指令所代表的具体操作是什么。init.rc所使用的各个关键字定义在keywords.h中

从头文件的内容可以得知,里面的KEYWORD宏是否定义,会直接影响到文件中的定义过程;接着看init_parser.cpp中是如何使用该头文件的
我们可以看到,在init_parser.cpp文件中,对keywords.h引用了两次;而这两次引用,会因KEYWORD宏的定义改变,而得到一些初始化结果。我们对这两次引用分别做分析。 

第一次引用时,KEYWORD宏未定义,此时做的操作是声明了很多个方法,这些方法就是某些指令对应的功能代码实现;并且定义了两个宏

这样ifndef KEYWORD...endif段的主要内容就结束了了,接着定义了一个枚举结构,这个枚举结构通过之前定义的KEYWORD宏实现,并只用了第一个参数。至此,我们就得到了一个由许多类似K_chmod、K_class这样的关键字填充的枚举定义。 

再看第二次引用的结果。继第一次引用之后,init_parser.cpp对KEYWORD宏又进行了定义

并声明、创建了一个结构体
 

此时第二次引用keyword.h,由于KEYWORD已经定义,__MAKE_KEYWORD_ENUM__未定义;这时就是用KEYWORD第二次定义的形式(此次使用四个参数,去初始化keyword_info数组。这个数组,在后续对Action的解析中,会被用来查找与某个Command对应的功能函数。

在Service、Action的解析过程中,用到了lookup_keyword()函数。lookup_keyword()就是根据传入的关键字,返回K_xxx结构的关键字供解析过程判断当前解析的是哪些指令,解析过程同时也用到了上面介绍过的宏定义函数,它的作用已经做了说明。

我们先看servcie的解析过程

 

结构体service是init.rc中定义的服务的代码表示,它会保存定义该服务时所配置的所有参数。我们看到最后将解析的service添加到service_list列表中

可以看出,service_list是一个首尾循环的双向链表,action_list和action_queue也是这样。parse_line_service()解析完配置的option后,会将数据写入service结构中。另外,我们从service解析的流程中看出,Android中并不允许在init.rc文件中配置多个重复的service。 

再看action的解析处理

 

action的解析过程跟service的解析有些类似,不过它并没有做同名action的判断处理;所以,Android中允许定义重复的action。

另外,代码中使用到的宏定义函数代码也贴出来

再看对import的处理
此处,解析完import后,会存储到import_list中。我们知道import是导入一个新的rc配置文件来扩展功能的,而对新导入的rc配置文件的处理是在解析完当前.rc配置后才去处理的。Init处理import_list时,会先遍历这个列表取出每一个import结构对象,在分别对import中保存的文件名对应的rc配置文件调用init_parse_config_file()函数来解析;这个过程与前面介绍的内容是一致的
我们在前面看listnode的定义,发现它没有定义数据域,那么它是怎么获取一个节点的数据呢?看node_to_item()的处理
这里的offsetof宏用来计算member在container结构中的偏移量;所以列表节点对应的数据对象的地址其实是通过node节点的指针计算到的。 

看完了init_parse_config_file()处理流程后,我们再接着看它后面的内容

这一块代码主要涉及到两个函数的调用:action_for_each_trigger()、queue_builtin_action()。 

分别来看

 
从代码可知action_for_each_trigger()函数就是找到action_list中所有trigger跟参数匹配的action,然后调用回调函数处理这些action;而传入的回调函数是:action_add_queue_tail(),它的作用就是将这些action加入到action_queue中。其实,这里的"early-init"、"init"、"charger"、"late-init"等action代表了init执行过程中的几个时间点,这点可以从init.rc中的定义可以知道,哪些操作属于哪个时间点,是由配置文件决定中的定义决定的;这些不同的action下都定义了很多需要执行的操作;由于有些操作必须在某些动作完成才能正常执行,所以这里就确定了先后之分,以保证程序初始化正常。 

再看

 

从代码可知,queue_builtin_action()会新创建一个action,并把它加入到action_queue中。新创建的action由传入的函数指针和代表名称的字符串组成。老版本的Android中是直接调用这些函数来完成初始化工作的,但是,这些函数的处理可能会依赖init.rc里定义的一些命令和服务的执行情况。所以现在把这些初始化函数以Action的形式加入到执行列表中,我们就可以控制它们的调用、执行顺序了。

插入的函数大概功能是
  • wait_for_coldboot_done_action():等待冷插拔设备初始化完成。
  • mix_hwrng_into_linux_rng_action():从硬件RNG的设备文件/dev/hw_random中读取512字节并写到Linux RNG的设备文件/dev/urandom中。
  • keychord_init_action():初始化组合键监听模块。
  • console_init_action():在屏幕上显示Android字样的Logo。
  • queue_property_triggers_action():检查Action列表中通过修改属性来触发的Action,查看相关的属性是否已经设置,如果已经设置,则加入到action_queue中。
我们再看init.cpp::main()函数的最后一部分处理
 

最后,处理过程会进入一个无限while()循环,每次循环开始都会调用execute_one_command()获取action_queue列表中的一个action(其实就是执行该action中的各条Command,然后执行、并从action_queue移除掉它

 

循环调用restart_processes()去重新启动service_list中的带有SVC_RESTARTING标志的服务(这个服务已经退出但需要重新启动)。

我们再看下restart_processes()的处理

 
 
 
restart_processes()函数会检查service_list中的每一项服务,凡是带有SVC_RESTARTING标志的,都会去调用restart_service_if_needed()函数。 

restart_service_if_needed()函数又会调用servcie_start()函数来启动服务。service_start()函数中会为该服务fork()一个新的进程,如果该服务声明了socket,同时也会帮它创建一个socket且进行bind,并将该socket的fd以键值对的形式发布到系统中:ANDROID_SOCKET_"socket_name" = "socket_fd";好让别处能有途径获取到这个创建的socket的文件描述符并使用它。最后,在子进程中,传入在.rc文件中配置的参数,并调用execve()函数去执行该服务对应的主程序;那么这个服务就已经启动了。最后会将服务的启动结果写入到init.svc.<servicename>属性中

 

再执行一个命令和启动了所有的服务进程后,Init进程会开启epoll轮询(epoll_wait(),等待受关注的事件的发生(signal、property和keychord

epoll可以设置等待超时的时间,参数为-1表示无限等待,参数为0表示立即返回,参数为正值表示要等待的时间。代码中,timeout的初始值为-1。如果还有服务需要启动,则会把timeout设置为下次启动服务的时间;process_needs_restart在restart_service_if_needed()中有设置动作。如果action_queue中还有action需要执行,则会将timeout置0。 

需要注意的是,Init进程并不是把命令列表中的命令一次执行完,而是和epoll_wait()交替执行。这里主要的考虑执行完所有命令太耗时,如果这期间有事件到来,处理就会耽搁。因此,每执行一条列表中的Command,就检查一次epll的事件。根据前面介绍的向epoll_fd注册需要监听的fd部分的内容,当有事件到来时,epoll_wait()接收事件,就会相应的调用我们注册的事件处理函数来处理事件。


分析到这里,我们只看到了某个服务退出、但需要重新启动的过程,而没有看到一开始启动服务的过程,这是怎么回事呢?其实,init.rc中定义的服务要启动,是靠class_start这个关键字来实现的:class_start <serviceclass>:启动所有指定服务名称下的未运行服务;由前面的介绍可知,虽然这里它是关键字,但实际上它代表了一个函数操作。

在keywords.h这个文件中定义了class_startt关键字的对应的函数

从文件可以看出,class_start 指令对应的函数是do_class_start(),它会启动一个不带disabled标志的服务
 

init.rc中class关键字定义了三个分类:core(核心服务,该服务如果不启动会影响系统的运行)、main(基础服务,这些服务保障Android的正常运行)、later_start(可以晚些启动的服务)。

我们以启动Zygote这个服务进程为例,先看它的定义

其中 
 

就是给Zygote服务制定了一个名字;class关键字的描述为:class <name>:给Service指定一个名字。所有同名字的服务可以同时启动和停止。如果不通过class选项指定一个名字,则默认是“default”。从这就可以看出class <name>关键字跟class_start <serviceclass>关键字之间的联系了;比如init.rc中,如果检测到设备不是加密设备,则"class_start main"这个Action就会执行,它就将启动定义中所有通过"class main"指定了名称的服务,其中就包括Zygote。

关于Android设备加密的内容可以参看邓大大的文章

http://blog.csdn.net/innost/article/details/44519775;讲解的很透彻。

do_class_start()函数最终是调用service_start()方法去启动一个服务,这个函数之前已经分析过了。
总之,当init.rc中执行了class_start <serviceclass>语句,它就会去启动所有通过class指定了"serviceclass"且不带disabled标志的服务;这就是init.rc中启动服务的方式。

待续......






















本文地址:http://ww.kub2b.com/tnews/3976.html     企库往 http://ww.kub2b.com/ ,  查看更多

特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。

 
 
更多>同类生活信息

文章列表
相关文章
最新动态
推荐图文
生活信息
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号