《Android的设计与实现:卷I》——第3章 3.2Kernel启动过程

3.2 Kernel启动过程

Android Kernel启动过程与标准Linux Kernel的启动过程基本一致,都是对start_kernel函数的调用和执行。本节将分析Android正常启动流程的第二步:Kernel启动过程。

本节涉及的源码文件如下:

kernel/arch/arm/kernel/head.S
kernel /arch/arm/kernel/head-common.S
kernel/init/main.c
Kernel启动过程分为两个阶段:

1)内核引导阶段。通常使用汇编语言编写,主要检查内核与当前硬件是否匹配。这部分也与硬件体系结构相关。内核引导阶段相关的代码主要位于 kernel /arch/arm/kernel/head.S和kernel /arch/arm/kernel/head-common.S。

2)内核启动阶段。引导阶段结束前,将调用start_kernel()进入内核启动阶段。内核启动阶段相关的代码主要位于kernel/init/main.c。

3.2.1 内核引导阶段

head.S是Linux内核启动的汇编程序入口,但head.S中并没有直接调用start_kernel()。可以先找到调用start_kernel()的函数,然后通过回溯法找到其调用的源头。

在head-common.S中可以找到start_kernel()的调用位置,代码如下:
__INIT
@函数 __mmap_switched
__mmap_switched:
@取 __mmap_switched_data的地址到r3
adr r3, __mmap_switched_data
……@省略部分代码,以下都是ARM汇编指令
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7})
THUMB( ldr sp, [r3, #16])
……@省略部分代码

@这里真正调用了start_kernel

b start_kernel
@b指令是跳转指令
@遇到b指令,处理器跳转到给定
@的目标地址,从目标地址继续执行
ENDPROC(__mmap_switched)

从head-common.S代码中可以看到,系统在__mmap_switched中通过b汇编指令调用了start_kernel。那么__mmap_switched又是在哪里被调用的呢?回到head.S这个汇编入口,代码如下:

@ MMU可用后,将跳转到这个地址
ldr r13, =__mmap_switched 
adr lr, BSYM(1f) @ 返回地址 (PIC)
@ set TTBR1 to swapper_pg_dir
mov r8, r4  
ARM(add pc, r10, #PROCINFO_INITFUNC )
THUMB(add r12, r10, #PROCINFO_INITFUNC )
THUMB(mov pc, r12)
1:b __enable_mmu @启动MMU
ENDPROC(stext)
这里将__mmap_switched的地址保存到r13,那pc指针又是什么时候指向r13的呢?
在源码中搜索,发现是在turn_mmu_on结束之时将pc指针指向r13的。代码如下:
.align 5
__turn_mmu_on:
……@省略部分代码
mov r3, r13 @跳转到r13,即__mmap_switched
mov pc, r3 @pc指针,指向当前要运行的指令
__enable_mmu_end:
ENDPROC(__turn_mmu_on)
到这里,可以继续跟踪__turn_mmu_on是在哪里调用的。这很容易找到,系统通过b汇编指令跳转到__turn_mmu_on。代码如下:
__enable_mmu:
……@省略部分代码
mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
domain_val(DOMAIN_IO, DOMAIN_CLIENT))
@ load domain access register
mcr p15, 0, r5, c3, c0, 0 
@ load page table pointer
mcr p15, 0, r4, c2, c0, 0 
@系统在这里跳转到__turn_mmu_on
b__turn_mmu_on
ENDPROC(__enable_mmu)

到这里,可以看出start_kernel的调用流程:系统在开启MMU的时候,在函数__enable_mmu中调用__turn_mmu_on,然后__turn_mmu_on通过“mov r3, r13”和“mov pc, r3”两条指令调用__mmap_switched。而函数__mmap_switched是head-common.S中的函数,在该函数中调用start_kernel。

start_kernel函数是Linux内核通用的启动函数,也是汇编代码执行完毕后的第一个C语言函数,它的实现代码位于init/main.c中。start_kernel的代码比较复杂,其中做了很多软硬件初始化的工作,比如调用了setup_arch()、timer_init()、init_IRQ、console_init()。

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进程的执行过程。

时间: 2024-08-01 15:04:11

《Android的设计与实现:卷I》——第3章 3.2Kernel启动过程的相关文章

Android启动过程深入解析

当按下Android设备电源键时究竟发生了什么? Android的启动过程是怎么样的? 什么是Linux内核? 桌面系统linux内核与Android系统linux内核有什么区别? 什么是引导装载程序? 什么是Zygote? 什么是X86以及ARM linux? 什么是init.rc? 什么是系统服务? 当我们想到Android启动过程时,脑海中总是冒出很多疑问.本文将介绍Android的启动过程,希望能帮助你找到上面这些问题的答案. Android是一个基于Linux的开源操作系统.x86(x

《Android的设计与实现:卷I》——第3章 3.6init循环监听处理事件

3.6 init循环监听处理事件 init触发所有Action后,便进入一个无限循环,在这个无限循环里首先执行两条指令: execute_one_command()和restart_processes(). 其中execute_one_command()已经分析过,用来启动Action和Service:restart_processes()也容易理解,就是重启这些Action和Service.此后便在init中调用了系统函数poll等待一些事件发生,代码如下: nr = poll(ufds, f

《Android的设计与实现:卷I》——第3章 Android启动过程的底层实现

第3章 Android启动过程的底层实现 Android支持多种启动模式,主要有正常模式(normal mode).安全模式(safe mode).恢复模式(recovery mode).工厂模式(factory mode).快速启动模式(fastboot mode)等.除正常模式外,都是刷机或者测试模式,本书只讲解正常模式下Android的启动过程.如果读者对其他启动模式感兴趣,可以自行查阅相关资料. 3.1 Android正常模式启动流程 Android的正常模式启动流程大体如下:步骤1 系

Android UI设计的幻灯片:新的UI设计模式

文章描述:谷歌Android UI设计技巧:新的UI设计模式. 本系列文章原是Android的官方开发者博客的一份Android UI设计的幻灯片,51CTO的译者将这份教程5部分进行翻译整理,希望对Android开发者能有帮助.本文为<谷歌Android UI设计技巧>第四部分:新的UI设计模式. 本文为<谷歌Android UI设计技巧>第四部分:新的UI设计模式. [1] [2]  下一页

Android UI设计的幻灯片:图标与指导说明

文章描述:谷歌Android UI设计技巧:图标与指导说明. 本系列文章原是Android的官方开发者博客的一份Android UI设计的幻灯片,51CTO的译者将这份教程5部分进行翻译整理,希望对Android开发者能有帮助.本文为<谷歌Android UI设计技巧>第五部分也就是最后一部分:图标与指导说明. 本文为<谷歌Android UI设计技巧>第五部分也就是最后一部分:图标与指导说明.

Android应用设计:选项菜单Options Menu

文章描述:Android硬体键交互之"选项菜单". 众所周知Android没有明确的GuideLine,虽说没有严格的规范来限制设计与创新很赞,但这也导致市场上的Android应用设计上的混乱.一个典型例子就是选项菜单Options Menu. 混乱的菜单 Android机器采用的硬体键来呼出菜单,这种方式在表现上隐性的,用户对于何种情况下可以呼出何种菜单没有预见性,甚至是否可以呼出菜单都没有预期.   如何解决 为降低用户的认知成本,建议设计中遵循以下方式. Question 1:何

谷歌Android UI设计技巧:新的UI设计模式

本系列文章原是Android的官方开发者博客的一份Android UI设计的幻灯片,51CTO的译者将这份教程5部分进行翻译整理,希望对Android开发者能有帮助.本文为<谷歌Android UI设计技巧>第四部分:新的UI设计模式. 本文为<谷歌Android UI设计技巧>第四部分:新的UI设计模式.

谷歌Android UI设计技巧:框架特性

本系列文章原是Android的官方开发者博客的一份Android UI设计的幻灯片,51CTO的译者将这份教程5部分进行翻译整理,希望对Android开发者能有帮助.本文为<谷歌Android UI设计技巧>第三部分:框架特性. 本文为<谷歌Android UI设计技巧>第三部分:框架特性. 注:相对布局和线性布局是Android里面常用的两种布局,线性布局比较简单,而相对布局可以做出比较复杂的布局管理,所以仅仅了解线性布局,很多时候是不够的.不过以作者之前Qt的经验来看,Andr

谷歌Android UI设计技巧:优秀UI设计准则

本文原是Android的官方开发者博客的一份Android UI设计的幻灯片,51CTO的译者将这份教程5部分进行翻译整理,希望对Android开发者能有帮助.本文为<谷歌Android UI设计技巧>第二部分:优秀UI设计准则. 本文为<谷歌Android UI设计技巧>第二部分:优秀UI设计准则.