您的位置:首页 > 运维架构 > Linux

Android 8.0 开机流程 (三) Linux 内核 init 进程的启动

2019-03-22 16:25 225 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/mahang123456/article/details/88741650

前面分析了kthredd 内核守护进程的分析,接下里会分析init 进程。init是用户空间的第一个进程,也是所有用户空间的所有父进程。这也是从内核态切换到用户态。

1.kernel_init

kernel_init 定义在 Kernel\init\main.c

[code]static int __ref kernel_init(void *unused)
{
#if (MP_CHECKPT_BOOT == 1)
unsigned int PiuTick;
unsigned int PiuTime;
#endif
int ret;

kernel_init_freeable();//前期初始化动作
/* need to finish all async __init code before freeing the memory */
//等待所有异步调用完成,释放内存前,必须完成所有的异步_init代码
async_synchronize_full();//
#if !defined(CONFIG_MP_MSTAR_STR_BASE)
free_initmem(); //释放所有init.*段中的代码
#endif
mark_readonly();
system_state = SYSTEM_RUNNING; //设置系统状态为运行态
numa_default_policy(); //设定NUMA系统的默认内存访问策略

flush_delayed_fput();//释放有延时的文件结构体struct file

rcu_end_inkernel_boot();
#ifdef CONFIG_MP_DEBUG_TOOL_MEMORY_USAGE_MONITOR
show_mm_time(0);
#endif

//ramdisk_execute_command 值为/init
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);//执行根目录下的init程序
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}

/*
* 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.
*/
//execute_command 如果有定义,就去相应的根目录下执行的相应的程序
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
// ramdisk_execute_command  execute_command定义的应用程序没有找到
//在根目录下 /sbin/init  /etc/init  /bin/init  /bin/sh 找相应的应用程序
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found.  Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}

在kthread_init 中 有一些相关的初始化,然后根据 ramdisk_execute_command  execute_command和去执行相应程序,如果没有找到相应的程序,再去/sbin/init  /etc/init  /bin/init  /bin/sh 找相应的应用程序。只需要找一个程序,执行即可。

2.kernel_init_freeable

kernel_init_freeable 定义在 Kernel\init\main.c

[code]static noinline void __init kernel_init_freeable(void)
{
/*
* Wait until kthreadd is all set-up.
*/
/*等待&kthreadd_done这个值complete,这个在rest_init方法中有写,在ktreadd进程启动完成后设置
为complete*/
wait_for_completion(&kthreadd_done);

/* Now the scheduler is fully set up and can do blocking allocations */
//设置bitmask, 使得init进程可以使用PM并且允许I/O阻塞操作
gfp_allowed_mask = __GFP_BITS_MASK;

/*
* init can allocate pages on any node
*/
//init进程可以分配物理页面
set_mems_allowed(node_states[N_MEMORY]);
/*
* init can run on any cpu.
*/
//init进程可以在任意cpu上执行
set_cpus_allowed_ptr(current, cpu_all_mask);
/*设置到init进程的pid号给cad_pid,cad就是ctrl-alt-del,设置init进程来处理ctrl-alt-del信
号*/
cad_pid = task_pid(current);
//设置smp初始化时的最大CPU数量,然后将对应数量的CPU状态设置为present
smp_prepare_cpus(setup_max_cpus);

//调用__initcall_start到__initcall0_start之间的initcall_t函数指针
do_pre_smp_initcalls();
//开启watchdog_threads,watchdog主要用来监控、管理CPU的运行状态
lockup_detector_init();

//启动cpu0外的其他cpu核
smp_init();
//进程调度域初始化
sched_init_smp();

page_alloc_init_late();

//初始化设备,驱动等,这个方法比较重要,将在下面单独讲
do_basic_setup();

/* Open the /dev/console on the rootfs, this should never fail */

首先打开了/dev/console文件,在Linux中一切皆是文件,所以/dev/console文件代表的是控制台设备
(其实就是串口)
在Linux中,进程打开文件后会获得该文件的文件描述符。这里kernel_init进程就得到了控制台的文件描
述符,然后又使用sys_dup复制了2次….一共得到了3个文件描述符。这三个文件描述符分别是0、1、2.这
三个文件描述符就是所谓的:标准输入、标准输出、标准错误
之后kernel_init所有的子进程都将继承这3个文件描述符,也就是说后面所有的进程一生出来,就默认拥
有标准输入、标准输出、标准错误的文件描述符
其实这一步的根本目的就是传承这3个文件描述符、

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
*/
//如果 ramdisk_execute_command 没有赋值,则赋值为"/init",之前有讲到
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
// 尝试进入ramdisk_execute_command指向的文件,如果失败则重新挂载根文件系统
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 the public keys
* and default modules
*/

integrity_load_keys();
// 加载I/O调度的电梯算法
load_default_modules();
}

在 kernel_init_freeable 中 启用了smp SMP(Symmetric Multi-Processor) 所谓对称多处理器结构,是指服务器中多个CPU对称工作,各CPU共享相同的物理内存及总线结构。初始化了设备驱动,加载了文件系统和获取了控制台。打开标准输入,标准输出。

2.1.do_basic_setup

do_basic_setup 定义在 Kernel\init\main.c

[code]static void __init do_basic_setup(void)
{
////针对SMP系统,初始化内核control group的cpuset子系统。
cpuset_init_smp();
// 初始化共享内存
shmem_init();
// 初始化驱动程序模型
driver_init();
//创建/proc/irq目录, 并初始化系统中所有中断对应的子目录
init_irq_proc();
// 执行内核的构造函数
do_ctors();
// 启用usermodehelper
usermodehelper_enable();
//遍历initcall_levels数组,调用里面的initcall函数,这里主要是对设备、驱动、文件系统进行初始
化,之所有将函数封装到数组进行遍历,主要是为了好扩展
do_initcalls();
}

do_basic_setup 是在系统起来以后做一些基础初始化,相应子系统的初始化。cpuset子系统 设备驱动子系统,irq子系统的初始化等。

2.2.driver_init

driver_init 定义在 Kernel\drivers\base\init.c

[code]void __init driver_init(void)
{
/* These are the core pieces */
// 注册devtmpfs文件系统,启动kdevtmpfs进程
devtmpfs_init();
// 初始化驱动模型中的部分子系统,kset:devices 和 kobject:dev、 dev/block、 dev/char
devices_init();
// 初始化驱动模型中的bus子系统,kset:bus、devices/system
buses_init();
// 初始化驱动模型中的class子系统,kset:class
classes_init();
// 初始化驱动模型中的firmware子系统 ,kobject:firmware
firmware_init();
// 初始化驱动模型中的hypervisor子系统,kobject:hypervisor
hypervisor_init();

/* These are also core pieces, but must come after the
* core core pieces.
*/
// 初始化驱动模型中的bus/platform子系统,这个节点是所有platform设备和驱动的总线类型,即所有
platform设备和驱动都会挂载到这个总线上
platform_bus_init();
// 初始化驱动模型中的devices/system/cpu子系统,该节点包含CPU相关的属性
cpu_dev_init();
//初始化驱动模型中的/devices/system/memory子系统,该节点包含了内存相关的属性,如块大小等
memory_dev_init();
//初始化系统总线类型为容器
container_dev_init();
/初始化创建,访问和解释设备树的过程。
of_core_init();
}

driver_init 中linux 内核驱动模型的子系统基本上已经初始化完成,建立了linux 驱动框架。但是它只是创建了目录,具体的驱动加载在do_install 函数中。

3.free_initmem

free_initmem 定义在 Kernel\arch\arm64\mm\init.c

[code]void free_initmem(void)
{
free_reserved_area(__va(__pa(__init_begin)), __va(__pa(__init_end)),
0, "unused kernel");
/*
* Unmap the __init region but leave the VM area in place. This
* prevents the region from being reused for kernel modules, which
* is not supported by kallsyms.
*/
unmap_kernel_range((u64)__init_begin, (u64)(__init_end - __init_begin));
}

所有使用__init标记过的函数和使用__initdata标记过的数据,在free_initmem函数执行后,都不能使用,它们曾经获得的内存现在可以重新用于其他目的。

4.flush_delayed_fput

flush_delayed_fput 定义在 Kernel\fs\file_table.c中

[code]void flush_delayed_fput(void)
{
delayed_fput(NULL);
}

static void delayed_fput(struct work_struct *unused)
{
struct llist_node *node = llist_del_all(&delayed_fput_list);
struct llist_node *next;

for (; node; node = next) {
next = llist_next(node);
////释放struct file
__fput(llist_entry(node, struct file, f_u.fu_llist));
}
}

释放 delayed_fput_list 链表中struct 文件结构体,用户空间每打开一个文件,内核就会关联一个struct 文件结构体。

5.run_init_process

run_init_process 定义在 Kernel\init\main.c

[code]static int run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
return do_execve(getname_kernel(init_filename),//执行程序
(const char __user *const __user *)argv_init,
(const char __user *const __user *)envp_init);
}

run_init_process 执行一个可执行程序。kthread_init 中执行/.init文件 

6.小结

在init进程中,相关初始化后。就会去执行用户空间的init程序。从内核态转化到用户态。Linux内核的启动流程已完毕。主要就是kthread_init 进程和kthreadd守护进程的创建。后面就会进入到用户空间 Android 的启动流程分析。

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: