内核初始化
特此说明: 刘超的趣谈linux操作系统是比较重要的参考资料,本文大部分内容和所有图片来源于这个专栏。
1 背景知识
BootLoader阶段后,cpu从实模式转换成保护模式。有了更强的寻址能力后,内核也已经加载到内存了,系统内核正式开始:在kernel源码init/main.c
文件中,内核的启动从入口函数start_kernel()
。其中进行一系列的初始化XXXX_init
阅读源码: https://elixir.bootlin.com/linux/latest/source
代码流程: init_task --> trap_init() --> mm_init() --> sched_init() --> vfs_caches_init() --> rest_init()
- 入口函数:
start_kernel()
在kernel源码的init/main.c
文件中 - 创建0号进程(init_task):
set_task_stack_end_magic(&init_task);
开天辟地第一个进程,pid为0,唯一一个没有通过 fork 或者 kernel_thread 产生的进程 - 初始化模块:
start_kernel()
中,定位到其他的初始化调用,其中有关键的几个
trap_init(); //系统调用相关 设置中断门
mm_init(); //内存管理
sched_init(); //调度模块
vfs_caches_init(); //rootfs文件系统
rest_init(); //其他方面的init
- 创建1号进程(kernel_init):
kernel_thread(kernel_init, NULL, CLONE_FS)
,创建了pid为1的进程,这是第一个用户进程,是所有其他用户进程的鼻祖进程。 - 创建2号进程(kthreadd):
kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)
,这里创建了第三个进程,pid为2,是内核态所有task的祖先,作用是将内核态所有的task进行统一的调度和管理。
2 内核初始化
1、 0号进程
定位到入口函数start_kernel(void),发现有一处调用set_task_stack_end_magic(&init_task);。这里kernel刚启动创建的第一个进程,pid为0,唯一一个没有通过 fork 或者 kernel_thread 产生的进程。
set_task_stack_end_magic(&init_task);
2、 各个模块初始化
同样在start_kernel(void)可以定位到其他的初始化调用,其中有关键的几个
trap_init(); //系统调用相关 设置中断门
mm_init(); //内存管理
sched_init(); //调度模块
vfs_caches_init(); //rootfs文件系统
rest_init(); //其他方面的init
3 补充知识
3.1 用户态和内核态
有用户进程后系统的运行模式发生变化,使用x86提供的权限访问机制,有Ring0…Ring3 4种权限(为避免多进程对资源访问的混乱)。
- 用户进程一般放在Ring3,我们称为
用户态
- 核心驱动代码一般放在Ring0,称为
内核态
3.2 系统调用
用户程序要访问核心资源,需要通过系统提供的系统调用接口,从用户态进入内核态。这个过程就是这样的:用户态 - 系统调用 - 保存寄存器 - 内核态执行系统调用 - 恢复寄存器 - 返回用户态
,然后接着运行。
3.3 ramdisk的作用
kernel启动过程中,一开始到用户态的是ramdisk的init,后来会启动真正根文件系统上的init,成为所有用户态进程的祖先。
为什么没有直接从根文件系统上加载init,这是因为文件系统一定存在一个存储设备上。要对设备访问需要驱动程序,而对内存可以直接访问。所以想在内存上建立一个文件系统
,先运行要访问存储设备的驱动程序,有了驱动就能设置根文件系统,就能启动根文件系统上的init程序了。
9 References
- 极客时间- 内核初始化