android init进程分析 基本流程

来源:互联网 发布:js 参数中有双引号 编辑:程序博客网 时间:2024/05/21 11:10

(懒人最近想起我还有csdn好久没打理了,这个Android init躺在我的草稿箱中快5年了,稍微改改发出来吧)

 android设备上电,引导程序引导进入boot(通常是uboot),加载initramfs、kernel镜像,启动kernel后,进入用户态程序。第一个用户空间程序是init, PID固定是1.在android系统上,init的代码位于/system/core/init下,基本功能有:

  • 管理设备
  • 解析并处理启动脚本init.rc
  • 实时维护这个init.rc中的服务 

init进程的系统初始化和服务流程,可以简单的看下init.c中的main函数。这里简要分析一下这个函数的主要作用,细节不展开或后继再展开。

简要说下main函数的各项操作吧:

[cpp] view plain copy
  1. if (!strcmp(basename(argv[0]), "ueventd"))  
  2.     return ueventd_main(argc, argv);  
  3.   
  4. if (!strcmp(basename(argv[0]), "watchdogd"))  
  5.     return watchdogd_main(argc, argv);  

以上这段,是main函数入口的第一句,这是判断是跑那个程序。

/system/core/init 这里面的一份代码,编出的二进制可执行程序init,实际是分为三个程序运行的,指示大家共享一份代码而已。三份分别是:
/init: 实际init可执行程序
/sbin/ueventd:ueventd daemon程序,是init的软链接
/sbin/watchdogd: watchdogd daemon程序,也是init的软链接。

我们都知道,main函数的参数argv的第一个,argv[0]为自身运行目录路径和程序名,这里就根据这个条件来判断代码走的路径。为何这样做?其实完全可以再另外写一个ueventd或watchdogd的一套程序,定义main函数啊。其实这里原因也是很简单,他们共享了太多东西,直接写到一起多简单!就像busybox或toolbox集成那么多命令工具一样的道理。

[cpp] view plain copy
  1. /* clear the umask */  
  2. umask(0);  

清理umask,这个主要是文件权限的问题,设了umask,可以全局mask掉一些文件权限。

[cpp] view plain copy
  1.     /* Get the basic filesystem setup we need put 
  2.      * together in the initramdisk on / and then we'll 
  3.      * let the rc file figure out the rest. 
  4.      */  
  5. mkdir("/dev", 0755);  
  6. mkdir("/proc", 0755);  
  7. mkdir("/sys", 0755);  
  8.   
  9. mount("tmpfs""/dev""tmpfs", MS_NOSUID, "mode=0755");  
  10. mkdir("/dev/pts", 0755);  
  11. mkdir("/dev/socket", 0755);  
  12. mount("devpts""/dev/pts""devpts", 0, NULL);  
  13. mount("proc""/proc""proc", 0, NULL);  
  14. mount("sysfs""/sys""sysfs", 0, NULL);  

Linux需要的dev,proc, sys等文件系统的加载。

[cpp] view plain copy
  1. /* indicate that booting is in progress to background fw loaders, etc */  
  2. e(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));  

创建一个/dev/.booting文件。 /dev是内存文件系统,不会保存的,每次开机都要重新创建。这个是指示,目前在booting过程中,具体干什么用的,介绍ueventd的时候就清楚了。就是加载设备的fimware用的。

[cpp] view plain copy
  1. open_devnull_stdio();  
  2. klog_init();  
  3. property_init();  

这三句,分别是将 stdin,stdout和stderr先初始化到/dev/__null__,这样用printf或其他打印的,都输出不了,也不会引起其它异常(这个阶段,其实不能用,会出错的)。

注意这个/dev/__null__是临时起的名字,创建node后删了,不影响系统真正的/dev/null,这里只需要fd即可,有了fd后,文件名就无用了,有了还会有干扰。

klog_init,是将init进程中的log,打印到内核printk的log buffer中的方法。这对调试init很有帮助,毕竟此时没有shell,通用的log输出方法,如printf等还不能工作,借助底层已有的内核调试功能当然是最好的了。

property的初始化也是在这里,property读取可以在各个用户进程中做,但设置的入口必须是到init进程中来。在4.4上,property这块修改了好多,现在是通过字典树的方式存储,可以支持更多的property属性。

[cpp] view plain copy
  1. get_hardware_name(hardware, &revision);  

从 /proc/cpuinfo中读到hardware信息,设置到ro.hardware属性中,便于后面解析 init.${ro.hardware}.rc使用

[cpp] view plain copy
  1. process_kernel_cmdline();  

kernel的command参数,解析后也放到property中,供以后的子进程或其他服务等使用。

[cpp] view plain copy
  1. union selinux_callback cb;  
  2. cb.func_log = klog_write;  
  3. selinux_set_callback(SELINUX_CB_LOG, cb);  
  4.   
  5. cb.func_audit = audit_callback;  
  6. selinux_set_callback(SELINUX_CB_AUDIT, cb);  
  7.   
  8. selinux_initialize();  
  9. /* These directories were necessarily created before initial policy load 
  10.  * and therefore need their security context restored to the proper value. 
  11.  * This must happen before /dev is populated by ueventd. 
  12.  */  
  13. restorecon("/dev");  
  14. restorecon("/dev/socket");  
  15. restorecon("/dev/__properties__");  
  16. restorecon_recursive("/sys");  

selinux的初始化和检查,没仔细研究selinux,则个检查过程有时候还挺耗费时间的。

[cpp] view plain copy
  1. INFO("property init\n");  
  2. if (!is_charger)  
  3.     property_load_boot_defaults();  

这个是加载并设置properties。这些是预置的property,通常是/system/build.prop和default.prop文件中预置的那些property。

[cpp] view plain copy
  1. init_parse_config_file("/init.rc");  

这是开始解析init.rc文件了。细节和init.rc的格式、写法不说了,网上到处都是。主要说一下常见问题:
1. 下载代码的服务器,umask有时候可能会有影响,init.rc文件other和group用户是不能有写权限的,编译的PC的umask最好设置成0022。
2. init.rc文件严格来说只是配置文件,不算脚本,循环、条件判断等等都不支持的,不要想这里能干太多事情。

[cpp] view plain copy
  1. action_for_each_trigger("early-init", action_add_queue_tail);  
  2.   
  3. queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");  
  4. queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");  
  5. queue_builtin_action(keychord_init_action, "keychord_init");  
  6. queue_builtin_action(console_init_action, "console_init");  
  7.   
  8. /* execute all the boot actions to get us started */  
  9. action_for_each_trigger("init", action_add_queue_tail);  
  10. ...  

这是开始处理init.rc中parser好的各个命令了。

action_for_each_trigger是对此类trigger所在呃所有命令,都加入到actions的list中去。对实际代码或项目上要全局的看一下,所有的*.rc的同一个trigger都一起处理的。

queue_builtin_action这是内建的actions,也是将actions动作加入到actions list中。

这里需要注意的是,各个trigger的加载顺序,先加入的先执行,后加入的后执行,要特别注意,尤其是要修改init.rc文件的时候,不了解这个容易因为前后依赖关系造成问题。


都准备好了的话,就到了服务处理阶段了,这是一个死循环,主要工作就是:
1. 将init.rc及内建的actions命令,一条一条执行
2. 负责对service的管理。
3. 对signal及进程退出的处理
4. 响应property设置的请求(设置都在init中统一设置,读取进程可以自己读共享内存)

[cpp] view plain copy
  1.    for(;;) {  
  2.        int nr, i, timeout = -1;  
  3.   
  4.        execute_one_command();  
  5.        restart_processes();  
  6. ...  

有时候需要优化开机的话,可以测量一下execute_on_command中的命令执行时间,把较长的(比如大于50ms)的打印出来,再想办法优化一下。

每次循环,执行一条命令,检查是否有需要重启的服务..

[cpp] view plain copy
  1. nr = poll(ufds, fd_count, timeout);  
  2. if (nr <= 0)  
  3.     continue;  
  4.   
  5. for (i = 0; i < fd_count; i++) {  
  6.     if (ufds[i].revents == POLLIN) {  
  7.         if (ufds[i].fd == get_property_set_fd())  
  8.             handle_property_set_fd();  
  9.         else if (ufds[i].fd == get_keychord_fd())  
  10.             handle_keychord();  
  11.         else if (ufds[i].fd == get_signal_fd())  
  12.             handle_signal();  
  13.     }  
  14. }  

多路polling,当polling到东西,就执行相应的动作。

timeout时间到,执行下轮循环。init.rc中的command没处理完的话,timeout是0,这样之前的actions list可以一顺下来都执行掉。
注意,init.rc中定义的服务,是在class_start这个command中做的。

0 0