西部数码主机 | 阿里云主机| 虚拟主机 | 服务器 | 返回乐道官网

Android系统开机启动流程及init进程浅析

时间:2016-03-29 22:45来源:未知 作者:好模板 点击:
Android系统开机流程基于Linux系统,总体可分为三个阶段: Boot Loader引导程序启动 Linux内核启动 Android系统启动,Launcher/app启动 启动流程如图1形象展示: 图1 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:

static noinline void __init_refok rest_init ( void )

{

int pid ;

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函数:

static int __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 ) )

return 0 ;

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 ) )

return 0 ;

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" ) )

return 0 ;

panic ( "No init found.  Try passing init= option to kernel. "

"See Linux Documentation/init.txt for guidance." ) ;

}

第一行就是kernel_init_freeable函数:

static noinline 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 ( ( const char __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 ( ( const char __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创建时才开始。

if ( ! ramdisk_execute_command )

ramdisk_execute_command = "/init" ;

一开始,ramdisk_execute_command为空,被初始化为”/init”,默认init进程的路径在根目录下

返回到kernel_init函数继续看

if ( ramdisk_execute_command ) {

if ( ! run_init_process ( ramdisk_execute_command ) )

return 0 ;

pr_err ( "Failed to execute %s\n" , ramdisk_execute_command ) ;

}

ramdisk_execute_command已经被赋值为”/init”不为空,调用run_init_process处理init可执行程序,run_init_process函数中的do_execve就是系统调用execve的具体实现,作用是运行可执行程序。

if ( ! run_init_process ( "/sbin/init" ) ||

! run_init_process ( "/etc/init" ) ||

! run_init_process ( "/bin/init" ) ||

! run_init_process ( "/bin/sh" ) )

return 0 ;

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源码如下:

int main ( int argc , char * * argv )

{

int fd_count = 0 ;

struct pollfd ufds [ 4 ] ;

char * tmpdev ;

char * debuggable ;

char tmp [ 32 ] ;

int property_set_fd_init = 0 ;

int signal_fd_init = 0 ;

int keychord_fd_init = 0 ;

bool is_charger = false ;

if ( ! strcmp ( basename ( argv [ 0 ] ) , "ueventd" ) )

return ueventd_main ( argc , argv ) ;

if ( ! strcmp ( basename ( argv [ 0 ] ) , "watchdogd" ) )

return watchdogd_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 ( ; ; ) {

int nr , 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 ( ) ;

else if ( ufds [ i ] . fd == get_keychord_fd ( ) )

handle_keychord ( ) ;

else if ( ufds [ i ] . fd == get_signal_fd ( ) )

handle_signal ( ) ;

}

}

}

return 0 ;

}

init进程主要做的事情都在main函数中,前两个语句:

if ( ! strcmp ( basename ( argv [ 0 ] ) , "ueventd" ) )

return ueventd_main ( argc , argv ) ;

if ( ! strcmp ( basename ( argv [ 0 ] ) , "watchdogd" ) )

return watchdogd_main ( argc , argv ) ;

libc库函数basename去除诸如:

/sbin/ueventd

前面的斜杠和前缀,获得字符串”ueventd”,如果argv数组包含该参数,就执行ueventd_main函数。ueventd也是一个可执行程序,在system\core\init\Android.mk中:

# Make a symlink from /sbin/ueventd and /sbin/watchdogd to /init

SYMLINKS : = \

$ ( TARGET_ROOT_OUT ) / sbin / ueventd \

$ ( TARGET_ROOT_OUT ) / sbin / watchdogd

这段代码编译后生成三个可执行文件:/init、/sbin/ueventd、/sbin/watchdogd。SYMLINKS关键字确定ueventd和watchdogd可执行程序文件作为init的软链接(也称符号链接)存在,查看运行环境:

root @ soniq_v600 : / # ls -l  sbin/                                              

- rwxr - xr - x compass   radio       309844 2015 - 12 - 30 14 : 16 adbd

- rwxr - xr - x compass   radio       324796 2015 - 12 - 30 14 : 16 healthd

- rwxr - xr - x compass   radio       169984 2015 - 12 - 30 14 : 16 mkfs . f2fs

lrwxrwxrwx compass   radio             2015 - 12 - 30 14 : 17 ueventd -> . . / init

lrwxrwxrwx compass   radio             2015 - 12 - 30 14 : 17 watchdogd -> . . / init

ueventd和watchdogd都指向了init程序。init程序运行时,实际上同时运行了三个程序,之所以把ueventd和watchdogd作为init进程的软链接,是因为这个三个进程共享了共同资源,放在同一份代码中即可,不用额外再写出分别针对ueventd和watchdogd的程序,这样造成了代码的冗余,也不便于维护。但是,放在同一份代码中如何区别当前进程是哪一个?这就是作者在main函数开头用了两个if语句的原因,通过进程名字判断到底是哪个进程。

ueventd进程用来管理设备,如果有新设备插入,就会在/dev创建对应的设备文件;watchdogd进程是看门狗程序,每隔一段时间通过系统调用write向内核看门狗设备发一个信息,以确保系统正常运行。

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 的所有块设备和字符设备。

get_hardware_name ( hardware , & revision ) ;

既然procfs文件系统已经挂载,就可以使用了,get_hardware_name函数从/proc/cpuinfo中读取处理器、硬件版本号、序列号等信息。

process_kernel_cmdline

从/proc/cmdline中获取内核命令参数并保存到相应的属性中

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,代表充电模式,否则,非充电模式。

property_load_boot_defaults ( ) ;

void property_load_boot_defaults ( void )

{

load_properties_from_file ( PROP_PATH_RAMDISK_DEFAULT , NULL ) ;

}

static void load_properties_from_file ( const char * fn , const char * filter )

{

char * data ;

unsigned sz ;

data = read_file ( fn , & sz ) ;

if ( data != 0 ) {

load_properties ( data , filter ) ;

free ( data ) ;

}

}

static void load_properties ( char * data , const char * filter )

{

. . .

. . .

property_set ( key , value ) ;

}

property_load_boot_defaults对系统属性进行初始化,一开始调用了load_properties_from_file,第一个参数PROP_PATH_RAMDISK_DEFAULT的位置在

/ 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中。

init_parse_config_file ( "/init.rc" ) ;

int init_parse_config_file ( const char * fn )

{

char * data ;

data = read_file ( fn , 0 ) ;

if ( ! data ) return - 1 ;

parse_config ( fn , data ) ;

DUMP ( ) ;

return 0 ;

}

解析init.rc文件,read_file读取init.rc文件内容,保存到缓冲区,返回缓冲区指针。

parse_config ( fn , data ) ;

parse_config中有一个for循环,循环读取每一行行内容,parse_new_section里面的parse_service、parse_line_service解析service;parse_action、parse_line_action解析Action。解析的最后一句是:

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段,比如:

service ueventd / sbin / ueventd

class core

critical

seclabel u : r : ueventd : s0

service logd / system / bin / logd

class core

socket logd stream 0666 logd logd

socket logdr seqpacket 0666 logd logd

socket logdw dgram 0222 logd logd

seclabel u : r : logd : s0

分别表示uevented服务段、logd服务段,其他依次类推。

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,再调用:

ret = cur_command -> func ( cur_command -> nargs , cur_command -> args ) ;

func函数是parse_line_action中得到的,如下所示,把func赋值为kw_func(kw)

cmd -> func = kw_func ( kw ) ;

kw_func是一个宏

#define kw_func(kw) (keyword_info[kw].func)

然后继续找下去直到core\init\Keywords.h,在keywords.h中找到每个action中的command都有对应执行函数如下:

#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中开头

on early - init

# Set init and its forked children's oom_adj.

write / proc / 1 / oom_score_adj - 1000

. . .

start ueventd

# create mountpoints

mkdir / mnt 0775 root system

on boot

. . .

class_start core

start ueventd对应这do_start、mkdir对应着do_mkdir函数,class_start core对应着do_class_start等。在start命令对应的do_start函数中,当service_start函数执行到如下语句时:

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开头处看到如下内容:

import / init . environ . rc

import / init . usb . rc

import / init . $ { ro . hardware } . rc

import / init . $ { ro . zygote } . rc

import / 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,系统启动过程结束。

(责任编辑:好模板)
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
栏目列表
热点内容