Android系统开机启动流程及init进程浅析
Android系统启动概述
Android系统开机流程基于Linux系统,总体可分为三个阶段:
Boot Loader引导程序启动
Linux内核启动
Android系统启动,Launcher/app启动
启动流程如图1形象展示:
图1 Android开机启动一般性流程
图1只简单地描述了开机启动一般性流程,“正常开机”(注意,是正常模式,不是工厂模式、recovery模式)流程为:
1. 手机、TV等android设备上电或重启后,系统硬件进行相应的复位操作,然后CPU开始执行第一条指令,该指令固化在ROM或者flash中某地址处,不可更改,由芯片制造商确定,该指令的作用就是加载引导程序到RAM执行。
2. 引导程序(boot loader),顾名思义,引导操作系统(比如Linux、Android、windows等)启动的程序,
就是在操作系统运行之前运行的一段程序,其作用是初始化硬件设备、创建存储器空间的映射等软件运行时所需要的最小环境;加载Linux内核镜像文件(本文只针对Android、Linux)到RAM中某个地址处执行,此时引导程序的控制权就交给了内核。
各家厂商都有可能自行设计boot loader,常见的有:U-Boot、RedBoot、ARMBoot等。
3. 当内核镜像文件被加载到RAM时,通过汇编编写的程序初始化硬件、堆栈、进行一些必要的环境设置等,然后调用decompress_kernel函数解压内核镜像,再调用c语言编写的start_kernel函数启动内核,内核启动时,会进行一些列初始化工作,包括:初始化调度程序、内存管理区、日期时间、缓存、中断等,再创建init内核线程,最后调用可执行程序init执行。
4. init进程启动后,创建、挂载文件系统、设备节点,解析init.rc文件,再启动各种系统守护进程,包括Android部分最重要的Zygote、ServiceManager进程,由此进入到Android系统启动部分。无论什么Linux发行版本还是Android系统,init进程都是用户空间的第一个进程(不是内核空间),其进程号固定为1,init进程是通向Linux、Android文件系统的大门,其他用户级进程都由init进程直接、间接创建,本文主要关注init进程的来龙去脉。
启动init进程
在kernel/init/路径下,main.c文件中的start_kernel函数就是启动内核的入口,经过一些列的初始化操作后进入到rest_init:
| 123456789101112131415161718192021222324252627 | staticnoinline void__init_refok rest_init(void){intpid;rcu_scheduler_starting();/** We need to spawn init first so that it obtains pid 1, however* the init task will end up wanting to create kthreads, which, if* we schedule it before we create kthreadd, will OOPS.*/kernel_thread(kernel_init,NULL,CLONE_FS|CLONE_SIGHAND);numa_default_policy();pid=kernel_thread(kthreadd,NULL,CLONE_FS|CLONE_FILES);rcu_read_lock();kthreadd_task=find_task_by_pid_ns(pid,&init_pid_ns);rcu_read_unlock();complete(&kthreadd_done);/** The boot idle thread must execute schedule()* at least once to get things moving:*/init_idle_bootup_task(current);schedule_preempt_disabled();/* Call into cpu_idle with preempt disabled */cpu_startup_entry(CPUHP_ONLINE);} |
kernel_thread函数调用do_fork创建了2号内核线程kthreadd、1号内核线程init,他们的父进程是内核0号进程,2号内核线程kthreadd作用是管理调度其他内核线程,而init内核线程通过调用init可执行程序转变成init进程,进程号还是1,kernel_thread函数第一个参数就是kernel_init函数:
| 123456789101112131415161718192021222324252627282930313233343536373839 | staticint__ref kernel_init(void*unused){kernel_init_freeable();/* need to finish all async __init code before freeing the memory */async_synchronize_full();free_initmem();mark_rodata_ro();system_state=SYSTEM_RUNNING;numa_default_policy();flush_delayed_fput();if(ramdisk_execute_command){if(!run_init_process(ramdisk_execute_command))return0;pr_err("Failed to execute %s\n",ramdisk_execute_command);}/** We try each of these until one succeeds.** The Bourne shell can be used instead of init if we are* trying to recover a really broken machine.*/if(execute_command){if(!run_init_process(execute_command))return0;pr_err("Failed to execute %s. Attempting defaults...\n",execute_command);}if(!run_init_process("/sbin/init")|| !run_init_process("/etc/init")|| !run_init_process("/bin/init")|| !run_init_process("/bin/sh"))return0;panic("No init found. Try passing init= option to kernel. " "See Linux Documentation/init.txt for guidance.");} |
第一行就是kernel_init_freeable函数:
| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859 | staticnoinline void__init kernel_init_freeable(void){/** Wait until kthreadd is all set-up.*/wait_for_completion(&kthreadd_done);/* Now the scheduler is fully set up and can do blocking allocations */gfp_allowed_mask=__GFP_BITS_MASK;/** init can allocate pages on any node*/set_mems_allowed(node_states[N_MEMORY]);/** init can run on any cpu.*/set_cpus_allowed_ptr(current,cpu_all_mask);cad_pid=task_pid(current);smp_prepare_cpus(setup_max_cpus);do_pre_smp_initcalls();lockup_detector_init();smp_init();sched_init_smp();do_basic_setup();/* Open the /dev/console on the rootfs, this should never fail */if(sys_open((constchar__user *)"/dev/console",O_RDWR,0)<0)pr_err("Warning: unable to open an initial console.\n");(void)sys_dup(0);(void)sys_dup(0);/** check if there is an early userspace init. If yes, let it do all* the work*/if(!ramdisk_execute_command)ramdisk_execute_command="/init";if(sys_access((constchar__user *)ramdisk_execute_command,0)!=0){ramdisk_execute_command=NULL;prepare_namespace();}/** Ok, we have completed the initial bootup, and* we're essentially up and running. Get rid of the* initmem segments and start the user-mode stuff..*//* rootfs is available now, try loading default modules */load_default_modules();} |
wait_for_completion(&kthreadd_done);
kernel_init函数在内核线程init中执行,执行前必须等待kthreadd线程建立好后才能进行,随后进行内存页分配等工作。
do_basic_setup();
在执行此函数之前,CPU、内存管理、进程管理等已经初始化完成,但是和设备相关的工作还没有展开,而此函数就是进行设备相关的初始化,重点包括初始化设备驱动程序(编译进内核的),这由driver_init函数完成。由此可知,在前文讨论了诸多初始化,都没有涉及到驱动,一直等到内核线程init创建时才开始。
| 12 | if(!ramdisk_execute_command) ramdisk_execute_command="/init"; |
一开始,ramdisk_execute_command为空,被初始化为”/init”,默认init进程的路径在根目录下
返回到kernel_init函数继续看
| 12345 | if(ramdisk_execute_command){if(!run_init_process(ramdisk_execute_command))return0;pr_err("Failed to execute %s\n",ramdisk_execute_command);} |
ramdisk_execute_command已经被赋值为”/init”不为空,调用run_init_process处理init可执行程序,run_init_process函数中的do_execve就是系统调用execve的具体实现,作用是运行可执行程序。
| 12345678 | if(!run_init_process("/sbin/init")|| !run_init_process("/etc/init")|| !run_init_process("/bin/init")|| !run_init_process("/bin/sh")) return0; panic("No init found. Try passing init= option to kernel. " "See Linux Documentation/init.txt for guidance."); |
如果根目录下没有init可执行程序,就会在”/sbin”、”/etc”、”/bin”下查找,直到找到后执行,如果都没有找到,系统尝试建立一个交互的shell命令可执行程序,让管理员尝试修复。如果sh也没有,此时Android系统启动失败,调用panic把错误提示保存到磁盘中,待重启后显示。
注:本文关于内核部分基于3.1版本所述,与2.6有差别,比如没有init_post函数,但核心内容没有太大区别。
init进程执行过程
上文通过内核启动init可执行程序,内核把控制权交到了用户空间,开始真正的Android之旅!init进程源码所在路径:
system\core\init
查看Android.mk文件,有这两句:
LOCAL_MODULE:= init // 编译后生成的模块的名字,具体是什么模块,看include关键字编译成什么
include $(BUILD_EXECUTABLE) //编译成可执行程序
init进程主要代码是init.c,标准c语言写的程序,其main函数是入口,init.c源码如下:
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 | intmain(intargc,char**argv){ intfd_count=0; structpollfd ufds[4]; char*tmpdev; char*debuggable; chartmp[32]; intproperty_set_fd_init=0; intsignal_fd_init=0; intkeychord_fd_init=0; boolis_charger=false; if(!strcmp(basename(argv[0]),"ueventd")) returnueventd_main(argc,argv); if(!strcmp(basename(argv[0]),"watchdogd")) returnwatchdogd_main(argc,argv); /* clear the umask */ umask(0); /* 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); /* indicate that booting is in progress to background fw loaders, etc */ close(open("/dev/.booting",O_WRONLY|O_CREAT,0000)); /* We must have some place other than / to create the * device nodes for kmsg and null, otherwise we won't * be able to remount / read-only later on. * Now that tmpfs is mounted on /dev, we can actually * talk to the outside world. */ open_devnull_stdio(); klog_init(); property_init(); get_hardware_name(hardware,&revision); process_kernel_cmdline(); if(is_initselinux()) { union selinux_callback cb; cb.func_log=log_callback; 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"); } is_charger=!strcmp(bootmode,"charger"); INFO("property init\n"); property_load_boot_defaults(); INFO("reading config file\n"); init_parse_config_file("/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); /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random * wasn't ready immediately after wait_for_coldboot_done */ queue_builtin_action(mix_hwrng_into_linux_rng_action,"mix_hwrng_into_linux_rng"); queue_builtin_action(property_service_init_action,"property_service_init"); queue_builtin_action(signal_init_action,"signal_init"); /* Don't mount filesystems or start core system services if in charger mode. */ if(is_charger){ action_for_each_trigger("charger",action_add_queue_tail); }else{ action_for_each_trigger("late-init",action_add_queue_tail); } /* run all property triggers based on current state of the properties */ queue_builtin_action(queue_property_triggers_action,"queue_property_triggers");#if BOOTCHART queue_builtin_action(bootchart_init_action,"bootchart_init");#endif for(;;){ intnr,i,timeout=-1; execute_one_command(); restart_processes(); if(!property_set_fd_init&&get_property_set_fd()>0){ ufds[fd_count].fd=get_property_set_fd(); ufds[fd_count].events=POLLIN; ufds[fd_count].revents=0; fd_count++; property_set_fd_init=1; } if(!signal_fd_init&&get_signal_fd()>0){ ufds[fd_count].fd=get_signal_fd(); ufds[fd_count].events=POLLIN; ufds[fd_count].revents=0; fd_count++; signal_fd_init=1; } if(!keychord_fd_init&&get_keychord_fd()>0){ ufds[fd_count].fd=get_keychord_fd(); ufds[fd_count].events=POLLIN; ufds[fd_count].revents=0; fd_count++; keychord_fd_init=1; } if(process_needs_restart){ timeout=(process_needs_restart-gettime())*1000; if(timeout<0) timeout=0; } if(!action_queue_empty()||cur_action) timeout=0;#if BOOTCHART if(bootchart_count>0){ if(timeout<0||timeout>BOOTCHART_POLLING_MS) timeout=BOOTCHART_POLLING_MS; if(bootchart_step()<0||--bootchart_count==0){ bootchart_finish(); bootchart_count=0; } }#endif 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(); elseif(ufds[i].fd==get_keychord_fd()) handle_keychord(); elseif(ufds[i].fd==get_signal_fd()) handle_signal(); } } } return0;} |
init进程主要做的事情都在main函数中,前两个语句:
| 12345 | if(!strcmp(basename(argv[0]),"ueventd")) returnueventd_main(argc,argv);if(!strcmp(basename(argv[0]),"watchdogd")) returnwatchdogd_main(argc,argv); |
libc库函数basename去除诸如:
/sbin/ueventd
前面的斜杠和前缀,获得字符串”ueventd”,如果argv数组包含该参数,就执行ueventd_main函数。ueventd也是一个可执行程序,在system\core\init\Android.mk中:
| 1234 | # Make a symlink from /sbin/ueventd and /sbin/watchdogd to /initSYMLINKS:=\$(TARGET_ROOT_OUT)/sbin/ueventd\$(TARGET_ROOT_OUT)/sbin/watchdogd |
这段代码编译后生成三个可执行文件:/init、/sbin/ueventd、/sbin/watchdogd。SYMLINKS关键字确定ueventd和watchdogd可执行程序文件作为init的软链接(也称符号链接)存在,查看运行环境:
| 123456 | root@soniq_v600:/# ls -l sbin/ -rwxr-xr-xcompass radio 3098442015-12-3014:16adbd-rwxr-xr-xcompass radio 3247962015-12-3014:16healthd-rwxr-xr-xcompass radio 1699842015-12-3014:16mkfs.f2fslrwxrwxrwx compass radio 2015-12-3014:17ueventd->../initlrwxrwxrwx compass radio 2015-12-3014:17watchdogd->../init |
ueventd和watchdogd都指向了init程序。init程序运行时,实际上同时运行了三个程序,之所以把ueventd和watchdogd作为init进程的软链接,是因为这个三个进程共享了共同资源,放在同一份代码中即可,不用额外再写出分别针对ueventd和watchdogd的程序,这样造成了代码的冗余,也不便于维护。但是,放在同一份代码中如何区别当前进程是哪一个?这就是作者在main函数开头用了两个if语句的原因,通过进程名字判断到底是哪个进程。
ueventd进程用来管理设备,如果有新设备插入,就会在/dev创建对应的设备文件;watchdogd进程是看门狗程序,每隔一段时间通过系统调用write向内核看门狗设备发一个信息,以确保系统正常运行。
| 12345678910 | 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); |
把虚拟文件系统tmpfs、devpts、profs、sysfs分别挂载/dev、/dev/pts、/proc、/sys目录下。
sysfs是一种基于内存的虚拟文件系统,在内核中产生,作用是把设备、驱动等内核信息从内核空间输出到用户空间,也可以对设备和驱动程序做设置。一般情况下,该文件系统挂在在/sys目录,但不是强制规定,也可以挂载在其他位置。sysfs文件内容以二进制、ASCII格式保存,一个文件只保存一个数据。
procfs文件系统(进程文件系统,procfs:process data filesystem),装载在/proc目录,也可以在其他位置,也是一种基于内存的虚拟文件系统,通过内核生成与系统状态、配置相关信息,其信息不能从块设备读取,只有在读取文件内容时,动态生成相应的内容。
所谓动态生成相应内容,就是在用户访问某信息时,实时生成相关信息供用户访问。
比如,ls -l proc/version 返回
-r-r-r- root root 0 2015-12-29 16:45 version
字节大小为0,没有任何内容
但是如果用cat proc/version 返回
Linux version 3.10.0_s5 (abc@H58M-SERVER) (gcc version 4.4.1 (Hisilicon_v200(gcc4.4-290+glibc-2.11+eabi+nptl)) ) #3 SMP Wed Dec 23 10:42:11 CST 2015
确实有数据的,这是内核实时产生的有关系统内核版本,系统固件版本名称等,该信息从内核内存的数据结构中实时获取而来,这就是动态生成信息的本意。
tmpfs也是一种虚拟内存文件系统,最大特点是其存储空间在VM(虚拟内存),而VM大小最大可达到实际内存+swap交换分区。
tmpfs的作用:linux中把一些程序的临时文件放在tmpfs中,利用tmpfs比硬盘速度快的特点提升系统性能。
devfs是一种设备文件系统,作用是提供一种高效率方式管理通常位于 /dev 的所有块设备和字符设备。
| 1 | get_hardware_name(hardware,&revision); |
既然procfs文件系统已经挂载,就可以使用了,get_hardware_name函数从/proc/cpuinfo中读取处理器、硬件版本号、序列号等信息。
| 1 | process_kernel_cmdline |
从/proc/cmdline中获取内核命令参数并保存到相应的属性中
| 1234 | property_get("ro.bootmode",tmp);strlcpy(bootmode,tmp,sizeof(bootmode));is_charger=!strcmp(bootmode,"charger"); |
property_get从属性ro.bootmode中获得值保存到tmp中,再用strlcpy库函数把tmp值拷贝到bootmode中,strcmp比较是否等于”charge”,如果是,is_charger为true,代表充电模式,否则,非充电模式。
| 123456789101112131415161718192021222324252627 | property_load_boot_defaults();voidproperty_load_boot_defaults(void){ load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT,NULL);}staticvoidload_properties_from_file(constchar*fn,constchar*filter){ char*data; unsignedsz; data=read_file(fn,&sz); if(data!=0){ load_properties(data,filter); free(data); }}staticvoidload_properties(char*data,constchar*filter){ ... ... property_set(key,value);} |
property_load_boot_defaults对系统属性进行初始化,一开始调用了load_properties_from_file,第一个参数PROP_PATH_RAMDISK_DEFAULT的位置在
| 123 | /bionic/libc/include/sys/_system_properties.h#define PROP_PATH_RAMDISK_DEFAULT "/default.prop" |
函数load_properties读取属性文件default.prop中每一个属性key和value,通过property_set把值都保存到相应的key中。default.prop中都是系统属性,系统编译后会copy到root/default.prop中,其原始位置在/build/core/main.mk中。
| 12345678910111213 | init_parse_config_file("/init.rc");intinit_parse_config_file(constchar*fn){char*data;data=read_file(fn,0);if(!data)return-1;parse_config(fn,data);DUMP();return0;} |
解析init.rc文件,read_file读取init.rc文件内容,保存到缓冲区,返回缓冲区指针。
| 1 | parse_config(fn,data); |
parse_config中有一个for循环,循环读取每一行行内容,parse_new_section里面的parse_service、parse_line_service解析service;parse_action、parse_line_action解析Action。解析的最后一句是:
| 123 | list_add_tail(&service_list,&svc->slist);list_add_tail(&action_list,&act->alist); |
分别把解析得到的service段和action段的地址放入到service_list列表和action_list列表保存起来,待后面使用时取出来。action段就是以on关键字开头的一部分命令组成的区域,比如:on early-init、on init及其后面跟随的各种命令,service段以service关键字开头的及其属性选项构成了一个section段,比如:
| 1234567891011 | service ueventd/sbin/ueventd classcore critical seclabelu:r:ueventd:s0service logd/system/bin/logd classcore socket logd stream0666logd logd socket logdr seqpacket0666logd logd socket logdw dgram0222logd logd seclabelu:r:logd:s0 |
分别表示uevented服务段、logd服务段,其他依次类推。
| 1 | action_for_each_trigger("early-init",action_add_queue_tail); |
action_for_each_trigger函数根据给定的action段字符串如early-init、init、late-init、boot等,在action_list中查询相应的action段,把qlist插入到action_queue尾部,就是把action段放到即将要执行的列表中,待执行。action_for_each_trigger把相应的段按顺序加入到队列中,其先后执行顺序就决定了段action段的执行顺序,因此,on early-init段最先执行,其次是on init、on charge(必须在充电模式下才能起作用)、on late-init等。
queue_builtin_action的作用和action_for_each_trigger差不多,主要处理以on property开头的属性服务相关的action。
进程执行到此,init.rc文件已经解析完毕,解析过程中把action段、service段、属性段等放到了“即将执行队列”待执行。
进程继续执行到一个for无限循环,从“即将执行队列”中取出action段来执行。执行者是execute_one_command函数,该函数先取出action中的command,再调用:
| 1 | ret=cur_command->func(cur_command->nargs,cur_command->args); |
func函数是parse_line_action中得到的,如下所示,把func赋值为kw_func(kw)
| 1 | cmd->func=kw_func(kw); |
kw_func是一个宏
| 1 | #define kw_func(kw) (keyword_info[kw].func) |
然后继续找下去直到core\init\Keywords.h,在keywords.h中找到每个action中的command都有对应执行函数如下:
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 | #define KEYWORD(symbol, flags, nargs, func) K_##symbol,enum{ K_UNKNOWN,#endif KEYWORD(capability, OPTION, 0,0) KEYWORD(chdir, COMMAND,1,do_chdir) KEYWORD(chroot, COMMAND,1,do_chroot) KEYWORD(class, OPTION, 0,0) KEYWORD(class_start,COMMAND,1,do_class_start) KEYWORD(class_stop, COMMAND,1,do_class_stop) KEYWORD(class_reset,COMMAND,1,do_class_reset) KEYWORD(console, OPTION, 0,0) KEYWORD(critical, OPTION, 0,0) KEYWORD(disabled, OPTION, 0,0) KEYWORD(domainname, COMMAND,1,do_domainname) KEYWORD(enable, COMMAND,1,do_enable) KEYWORD(exec, COMMAND,1,do_exec) KEYWORD(export, COMMAND,2,do_export) KEYWORD(group, OPTION, 0,0) KEYWORD(hostname, COMMAND,1,do_hostname) KEYWORD(ifup, COMMAND,1,do_ifup) KEYWORD(insmod, COMMAND,1,do_insmod) KEYWORD(import, SECTION,1,0) KEYWORD(keycodes, OPTION, 0,0) KEYWORD(mkdir, COMMAND,1,do_mkdir) KEYWORD(mount_all, COMMAND,1,do_mount_all) KEYWORD(mount, COMMAND,3,do_mount) KEYWORD(on, SECTION,0,0) KEYWORD(oneshot, OPTION, 0,0) KEYWORD(onrestart, OPTION, 0,0) KEYWORD(powerctl, COMMAND,1,do_powerctl) KEYWORD(restart, COMMAND,1,do_restart) KEYWORD(restorecon, COMMAND,1,do_restorecon) KEYWORD(restorecon_recursive, COMMAND,1,do_restorecon_recursive) KEYWORD(rm, COMMAND,1,do_rm) KEYWORD(rmdir, COMMAND,1,do_rmdir) KEYWORD(tsfastboot, COMMAND,0,do_tsfastboot) KEYWORD(seclabel, OPTION, 0,0) KEYWORD(service, SECTION,0,0) KEYWORD(setcon, COMMAND,1,do_setcon) KEYWORD(setenforce, COMMAND,1,do_setenforce) KEYWORD(setenv, OPTION, 2,0) KEYWORD(setkey, COMMAND,0,do_setkey) KEYWORD(setprop, COMMAND,2,do_setprop) KEYWORD(setrlimit, COMMAND,3,do_setrlimit) KEYWORD(setsebool, COMMAND,2,do_setsebool) KEYWORD(socket, OPTION, 0,0) KEYWORD(start, COMMAND,1,do_start) KEYWORD(stop, COMMAND,1,do_stop) KEYWORD(swapon_all, COMMAND,1,do_swapon_all) KEYWORD(trigger, COMMAND,1,do_trigger) KEYWORD(symlink, COMMAND,1,do_symlink) KEYWORD(sysclktz, COMMAND,1,do_sysclktz) KEYWORD(user, OPTION, 0,0) KEYWORD(wait, COMMAND,1,do_wait) KEYWORD(write, COMMAND,2,do_write) KEYWORD(copy, COMMAND,2,do_copy) KEYWORD(chown, COMMAND,2,do_chown) KEYWORD(chmod, COMMAND,2,do_chmod) KEYWORD(loglevel, COMMAND,1,do_loglevel) KEYWORD(load_persist_props, COMMAND,0,do_load_persist_props) KEYWORD(load_all_props, COMMAND,0,do_load_all_props) KEYWORD(ioprio, OPTION, 0,0) |
比如,在init.rc中开头
| 1234567 | on early-init# Set init and its forked children's oom_adj.write/proc/1/oom_score_adj-1000...start ueventd# create mountpointsmkdir/mnt0775root system |
| 123 | on boot...class_start core |
start ueventd对应这do_start、mkdir对应着do_mkdir函数,class_start core对应着do_class_start等。在start命令对应的do_start函数中,当service_start函数执行到如下语句时:
| 123 | if(execve(svc->args[0],(char**)svc->args,(char**)ENV)<0){ ERROR("cannot execve('%s'): %s\n",svc->args[0],strerror(errno));} |
系统会调用execve函数执行可执行程序。对于start ueventd来说,就是执行 ueventd程序,开始启动 ueventd进程。除此之外,chmod、mount都是action中的command,分别对应do_write、do_mkdir函数,以此类推,其他action也是如此执行。
在执行action的command时就有部分服务被启动,这部分服务并没有以service关键字开头执行,那么init.rc中那些以service关键字开头的服务又是在哪里启动?
一般来说,大部分服务(在init.rc中以service关键字开头的)进程是在执行class_start时被启动的,因为这些服务进程是属于某一类别的,比如,class core、class main类别,只要在含有class_start core或class_start main的action被执行时,就会启动所有类别标识为core、main的服务。可以看到在on boot的这个动作段中含有class_start core,那么相应被启动的服务进程有logd、netd、adbd、healthd、lm、servicemanager、vold、surfaceflinger、bootanimation、netd、debuggerd、rild、drmserver、mediaserver、installd、racoon、mtpd、keystore、dumpstate、mdnsd、uncrypt、zygote等(zygote通过import命令import /init.${ro.zygote}.rc引用进来,在init.rc开始部分),查看这些服务,发现其option选项中含有class core、class main这样的类别,用ps命令查看当前系统那些进程是init启动,以验证上文分析是否正确:
如红色字体所示,第一行就是init进程,进程号PID为1,PPID是父进程号,查看所有PPID为1的进程可知,ueventd、logd、servicemanager、zygote等都是有init进程fork并启动的,是其子进程。
除了这些android通用服务进程外,还有一些与具体硬件产品、厂商相关的服务进程等,在init.rc开头处看到如下内容:
| 12345 | import/init.environ.rcimport/init.usb.rcimport/init.${ro.hardware}.rcimport/init.${ro.zygote}.rcimport/init.trace.rc |
与usb相关的服务进程都在init.usb.rc中,与厂商、硬件相关的都在init.<hardware>.rc中,比如,与wifi相关的wpa_supplicant进程、与ppoe相关的ppp进程、与电视厂商相关的显示进程、tv进程等、虚拟按键板等进程,由厂商自行创建。
init.rc文件解析完毕后,会继续解析由import导入的其他rc文件,函数调用流程:
init_parse_config_file—>parse_config—>init_parse_config_file
import关键字也是一个段second,解析过程和init.rc大致相同,不再赘述。
init进程主要做了三件事:
a. 创建/dev、/dev/pts、/proc、/sys目录,并挂载虚拟文件系统tmpfs、devpts、profs、sysfs
b. 解析init.rc等rc文件,启动系统服务进程,包括ServiceManager、Zygote、adbd、logd等
开机启动流程图
经过上文分析,本文最后给出开机启动流程图,主要以进程创建的方式描述:
图2 开机启动流程图
zygote是android部分的第一个进程,创建了ART,包括虚拟机以及各种库,最后创建了SystemServer,SystemServer用来创建系统最关键性服务包括ActivityManagerService、PowerManagerService、DisplayManagerService,随后创建了核心服务LightsService、BatteryService、AccountManagerService、VibratorService、WindowManagerService等,这些核心服务必须在ServiceManager中注册方可使用。当所有这些java层服务创建后,系统启动Launcher,系统启动过程结束。
总结
以上是生活随笔为你收集整理的Android系统开机启动流程及init进程浅析的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: android 修改framework下
- 下一篇: Android 系统自动重启Bug(高通