AOSP 8.0 系统启动之一内核启动

文章详细阐述了Android操作系统启动时Linux内核的角色,特别是idle进程、init进程和kthreadd进程的启动与作用。通过分析`init/main.c`等源码,解释了内核如何创建init进程和kthreadd进程,以及init进程如何在用户空间中启动Android系统的初始化。此外,提到了启动参数的解析,如通过`cmdline`设置`init`和`rdinit`。
摘要由CSDN通过智能技术生成

目录

 

一、前言

二、涉及源码​​​​​​​

三、源码分析​​​​​​​


一、前言

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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值