目录
二、涉及源码
三、源码分析
一、前言
Android本质上就是一个基于Linux内核的操作系统,与Ubuntu Linux、Fedora Linux类似,我们要讲Android,必定先要了解一些Linux内核的知识。
Linux内核的东西特别多,相关的知识体系许多也不太理解,由于本文主要讲解Android系统启动流程,所以这里主要讲一些内核启动相关的知识。
Linux内核启动主要涉及3个特殊的进程,idle进程(PID = 0), init进程(PID = 1)和kthreadd进程(PID = 2),这三个进程是内核的基础。
- idle进程是Linux系统第一个进程,是init进程和kthreadd进程的父进程
- init进程是Linux系统第一个用户进程,是Android系统应用程序的始祖,我们的app都是直接或间接以它为父进程
- kthreadd进程是Linux系统内核管家,所有的内核线程都是直接或间接以它为父进程
二、涉及源码
init/main.c
kernel/fork.c
kernel/kthread.c
include/linux/kthread.h
kernel/pid.c
三、源码分析
//内核启动的第一行代码
asmlinkage void __init start_kernel(void){
......
rest_init(); //启动内核的剩余部分
}
static noinline void __init_refok rest_init(void)
{
int pid;
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //创建init进程,此时pid = 1,且init创建之后会调用kernel_init这个函数指针(重点分析)
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); //创建kthreadd进;此时pid = 2
complete(&kthreadd_done); // complete和wait_for_completion是配套的同步机制,跟java的notify和wait差不多,之前kernel_init函数调用了wait_for_completion(&kthreadd_done),这里调用complete就是通知kernel_init进程kthreadd进程已创建完成,可以继续执行
init_idle_bootup_task(current); // 0号进程进入idle模式;,不会在进程(线程)列表中出现
}
init进程启动分为前后两部分,前一部分是在内核启动的,主要是完成创建和内核初始化工作,内容都是跟Linux内核相关的; 后一部分是在用户空间启动的,主要完成Android系统的初始化工作。
// 1号kernel_init进程完成linux的各项配置(包括启动AP)后,就会在/sbin,/etc,/bin寻找init程序来运行。
// 该init程序会替换kernel_init进程(注意:并不是创建一个新的进程来运行init程序,而是一次变身,使用sys_execve函数改变核心进程的正文段,
// 将核心进程kernel_init转换成用户进程init),
// 此时处于内核态的1号kernel_init进程将会转换为用户空间内的1号进程init。
static int __ref kernel_init(void *unused)
{
kernel_init_freeable(); //会给ramdisk_execute_command赋值为“/init”
......
if (ramdisk_execute_command) { //ramdisk_execute_command值为/init
if (!run_init_process(ramdisk_execute_command))
return 0;
pr_err("Failed to execute %s\n", ramdisk_execute_command);
}
.....
panic("No init found. Try passing init= option to kernel. See Linux Documentation/init.txt for guidance.");
}
static noinline void __init kernel_init_freeable(void)
{
.....
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init"
//根文件系统中是否存在init文件
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
}
static int run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
// 会将proc/cmdline的内容作为参数,传递给init 进程
// do_execve 核心的工作就是把文件映射到进程的空间,对于ELF可执行文件会被默认映射0x8048000。
// 需要动态链接的可执行文件先加载链接器ld(load _ elf _ interp 动态链接库动态链接文件),动态链接器的起点。如果它是一个静态链接,可直接将文件地址入口进行赋值。
return do_execve(init_filename,(const char __user *const __user *)argv_init, (const char __user *const __user *)envp_init);
}
目前还在内核空间中,主要是加载init可执行文件,并执行 。
kernel_init主要工作是完成一些init的初始化操作,然后去系统根目录下依次找ramdisk_execute_command和execute_command设置的应用程序,如果这两个目录都找不到,就依次去根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动,只要这些应用程序有一个启动了,其他就不启动了
ramdisk_execute_command和execute_command的值是通过bootloader传递过来的参数设置的,ramdisk_execute_command通过"rdinit"参数赋值,execute_command通过"init"参数赋值
ramdisk_execute_command如果没有被赋值,kernel_init_freeable函数会赋一个初始值"/init"
四、开启启动参数
节点proc/cmdline,是系统开机的引导参数
节点proc/kmsg 内核日志中,有设备启动的引导参数打印
Kernel command line: earlycon androidboot.selinux=permissive uart_dma keep_dbgclk_on clk_ignore_unused initrd=0xd0000000,38711808 rw crash_page=0x8f040000 initrd=/recoveryrc boot_reason=0x2000 ota_status=0x1001
1、在linux kernel中解析cmdline参数
(以init,rdinit为例)vim kernel/linux/init/main.c (即kernel_init函数所在的文件)
如代码所示__setup是一个宏,如果cmdline中有这个参数,则会执行对应的后面的函数。
下面这两个函数,会将cmdline中解析到的“init=”、“rdinit=”后面的字串写入到execute_command和ramdisk_execute_command全局变量中.
然后在kernel的代码中,就可以使用这两个变量了.
static int __init init_setup(char *str)
{
unsigned int i;
execute_command = str;
/*
* In case LILO is going to boot us with default command line,
* it prepends "auto" before the whole cmdline which makes
* the shell think it should execute a script with such name.
* So we ignore all arguments entered _before_ init=... [MJ]
*/
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("init=", init_setup);
static int __init rdinit_setup(char *str)
{
unsigned int i;
ramdisk_execute_command = str;
/* See "auto" comment in init_setup */
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("rdinit=", rdinit_setup);