(懒人最近想起我还有csdn好久没打理了,这个android init躺在我的草稿箱中快5年了,稍微改改发出来吧)
android设备上电,引导程序引导进入boot(通常是uboot),加载initramfs、kernel镜像,启动kernel后,进入用户态程序。第一个用户空间程序是init, PID固定是1.在android系统上,init的代码位于/system/core/init下,基本功能有:
init进程的系统初始化和服务流程,可以简单的看下init.c中的main函数。这里简要分析一下这个函数的主要作用,细节不展开或后继再展开。
简要说下main函数的各项操作吧:
if (!strcmp(basename(argv[0]), "ueventd")) return ueventd_main(argc, argv); if (!strcmp(basename(argv[0]), "watchdogd")) 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集成那么多命令工具一样的道理。
/* clear the umask */ umask(0);
清理umask,这个主要是文件权限的问题,设了umask,可以全局mask掉一些文件权限。
/* Get the basic filesystem setup we need put * together in the initramdisk on / and then we'll * let the rc file figure out the rest. */ mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL);
Linux需要的dev,proc, sys等文件系统的加载。
/* indicate that booting is in progress to background fw loaders, etc */ close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));
创建一个/dev/.booting文件。 /dev是内存文件系统,不会保存的,每次开机都要重新创建。这个是指示,目前在booting过程中,具体干什么用的,介绍ueventd的时候就清楚了。就是加载设备的fimware用的。
open_devnull_stdio(); klog_init(); 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属性。
get_hardware_name(hardware, &revision);
从 /proc/cpuinfo中读到hardware信息,设置到ro.hardware属性中,便于后面解析 init.${ro.hardware}.rc使用
process_kernel_cmdline();
kernel的command参数,解析后也放到property中,供以后的子进程或其他服务等使用。
union selinux_callback cb; cb.func_log = klog_write; selinux_set_callback(SELINUX_CB_LOG, cb); cb.func_audit = audit_callback; selinux_set_callback(SELINUX_CB_AUDIT, cb); selinux_initialize(); /* These directories were necessarily created before initial policy load * and therefore need their security context restored to the proper value. * This must happen before /dev is populated by ueventd. */ restorecon("/dev"); restorecon("/dev/socket"); restorecon("/dev/__properties__"); restorecon_recursive("/sys");
selinux的初始化和检查,没仔细研究selinux,则个检查过程有时候还挺耗费时间的。
INFO("property init\n"); if (!is_charger) property_load_boot_defaults();
这个是加载并设置properties。这些是预置的property,通常是/system/build.prop和default.prop文件中预置的那些property。
init_parse_config_file("/init.rc");
这是开始解析init.rc文件了。细节和init.rc的格式、写法不说了,网上到处都是。主要说一下常见问题:
1. 下载代码的服务器,umask有时候可能会有影响,init.rc文件other和group用户是不能有写权限的,编译的PC的umask最好设置成0022。
2. init.rc文件严格来说只是配置文件,不算脚本,循环、条件判断等等都不支持的,不要想这里能干太多事情。
action_for_each_trigger("early-init", action_add_queue_tail); queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); /* execute all the boot actions to get us started */ action_for_each_trigger("init", action_add_queue_tail); ...
这是开始处理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中统一设置,读取进程可以自己读共享内存)
for(;;) { int nr, i, timeout = -1; execute_one_command(); restart_processes(); ...
有时候需要优化开机的话,可以测量一下execute_on_command中的命令执行时间,把较长的(比如大于50ms)的打印出来,再想办法优化一下。
每次循环,执行一条命令,检查是否有需要重启的服务..
nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; for (i = 0; i < fd_count; i++) { if (ufds[i].revents == POLLIN) { if (ufds[i].fd == get_property_set_fd()) handle_property_set_fd(); else if (ufds[i].fd == get_keychord_fd()) handle_keychord(); else if (ufds[i].fd == get_signal_fd()) handle_signal(); } }
多路polling,当polling到东西,就执行相应的动作。
timeout时间到,执行下轮循环。init.rc中的command没处理完的话,timeout是0,这样之前的actions list可以一顺下来都执行掉。
注意,init.rc中定义的服务,是在class_start这个command中做的。