3.2.2 内核启动阶段

内核初始化阶段结束时,通过调用start_kernel函数进入内核启动阶段。在内核启动阶段,首先进行一些应用级的初始化工作,最后调用rest_init函数启动init进程。init进程是Android用户空间的1号进程,担负Android运行环境启动的重要职责。本节首先分析内核启动阶段都做了哪些工作。这部分代码主要位于kernel/init/main.c,如下所示:


asmlinkage void__init start_kernel(void)

{

……//省略部分内容

local_irq_disable();//关闭当前CPU中断

early_boot_irqs_disabled=true;

page_address_init();//页地址初始化

printk(KERN_NOTICE"%s",linux_banner);//输出内核版本信息

setup_arch(&command_line);//体系结构相关的初始化

//command_line由bootloader传入

……//省略部分内容

sched_init();//初始化进程调度器

preempt_disable();//禁止抢占

early_irq_init();//初始化中断处理函数

init_IRQ();

init_timers();//初始化定时器

hrtimers_init();//高精度时钟的初始化

softirq_init();//初始化软中断

time_init();//初始化系统时间

profile_init();//初始化内核性能调试工具profile

early_boot_irqs_disabled=false;

local_irq_enable();//打开IRQ中断

console_init();//初始化控制台显示printk打印的内容

//在此之前调用的printk只是缓存信息

fork_init(totalram_pages);//计算允许创建进程的数量

……//省略部分内容

signals_init();//初始化信号量

ftrace_init();

rest_init();//创建init进程

}


start_kernel的源代码可以分成以下两部分:

1)C部分的初始化过程。

2)调用rest_init创建init进程。

如果读者对C部分的初始化感兴趣,可以参考专门介绍Linux内核的书。本书将重点放在init进程创建过程。定位到rest_init方法的实现部分,代码如下:


static noinline void__init_refok rest_init(void)

{

int pid;

rcu_scheduler_starting();

/*通过kernel_thread启动内核线程,执行kernel_init

函数,此函数首先创建了init进程,以使其成为1号进程/

kernel_thread(kernel_init, NULL, CLONE_FS|CLONE_SIGHAND);

numa_default_policy();

/启动内核线程kthreadd/

pid=kernel_thread(kthreadd, NULL, CLONE_FS|CLONE_FILES);

……//省略部分代码

/调用schedule,释放当前线程占用的CPU,以激活其他线程/

schedule();

preempt_disable();

/调用cpu_idle,使当前线程变成idle线程,减少系统资源占用/

cpu_idle();

}


rest_init中的主要任务是通过kernel_thread启动了两个内核线程。kernel_thread的实现代码位于kernel/common/arch/arm/kernel/process.c,函数原型如下:


pid_t kernel_thread(int(fn)(void),void*arg, unsigned long flags)


kernel_thread用来启动一个内核线程,第一个参数fn是要执行的函数的指针;第二个参数arg是传递给该函数的参数;第三个参数flags为do_fork创建线程时的标志。对于rest_init方法中第一个kernel_thread调用,实际上调用了kernel_init函数。进入kernel_init函数体一看究竟,代码如下:


static int__init kernel_init(void*unused)

{

wait_for_completion(&kthreadd_done);

set_mems_allowed(node_states[N_HIGH_MEMORY]);

set_cpus_allowed_ptr(current, cpu_all_mask);

cad_pid=task_pid(current);

……//省略多CPU激活部分代码

do_basic_setup();//驱动程序和内核子系统的一般初始化

……//省略部分代码

//定义init进程

if(!ramdisk_execute_command)ramdidk_execute_command="/init";

if(sys_access(

(const char__user*)ramdisk_execute_command,0)!=0)

{

ramdisk_execute_command=NULL;

prepare_namespace();

}

//最后调用init_post,启动进程负责用户空间的初始化

init_post();

return 0;

}


kernel_init方法中,前面的部分做了一些初始化工作,然后定义了init进程的位置,接着就调用了init_post函数。接下来定位到init_post的函数体,代码如下:


static noinline int init_post(void)

{

……//省略部分代码

if(ramdisk_execute_command){

//run_init_process执行后将不再返回

run_init_process(ramdisk_execute_command);

}

if(execute_command){

run_init_process(execute_command);

}

run_init_process("/sbin/init");

run_init_process("/etc/init");

run_init_process("/bin/init");

run_init_process("/bin/sh");

}


execute_command是bootloader传递给内核的参数,一般是/init(即根目录下的init程序),也就是调用文件系统里的init进程。如果找不到就会继续寻找"/sbin/init"、"/etc/init"、"/bin/init"、"/bin/sh",找到后便执行run_init_process,且不再返回。run_init_process的函数体非常简单,仅仅是对kernel_execve函数的封装,代码如下:


static void run_init_process(const char*init_filename)

{

argv_init[0]=init_filename;

kernel_execve(init_filename, argv_init, envp_init);

}


kernel_execve是Linux内核中创建用户进程的方法接口,其实现位于arch/arm/kernel/sys_arm.c,这里不详述。

至此,已经对Android Kernel如何引导以及用户空间1号进程(init进程)如何启动做了详细分析。如果读者并不熟悉Linux和汇编语言也不要紧,这不影响继续分析Android的启动过程。接下来,将详细分析Android init进程的执行过程。