内核启动

内核的启动从入口函数 start_kernel() 开始。在 init/main.c 文件中,start_kernel 相当于内核的 main 函数。打开这个函数,里面是各种各样初始化函数 XXXX_init。
cdfc33db2fe1e07b6acf8faa3959cb01.webp

项目初始化:在操作系统里面,先要有个创始进程,代码如下:

  1. set_task_stack_end_magic(&init_task)
  2. struct task_struct init_task = INIT_TASK(init_task)

它是系统创建的第一个进程,我们称为 0 号进程。这是唯一一个没有通过 fork 或者 kernel_thread 产生的进程,是进程列表的第一个。

为响应客户需求初始化一个”办事大厅“,这里面对应的函数是 trap_init(),里面设置了很多中断门(Interrupt Gate),用于处理各种中断。其中有一个 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32),这是系统调用的中断门。系统调用也是通过发送中断的方式进行的。

vfs_caches_init() 会用来初始化基于内存的文件系统 rootfs。在这个函数里面,会调用 mnt_init()->init_rootfs()。这里面有一行代码,register_filesystem(&rootfs_fs_type)。在 VFS 虚拟文件系统里面注册了一种类型,我们定义为 struct file_system_type rootfs_fs_type

文件系统是我们的项目资料库,为了兼容各种各样的文件系统,我们需要将文件的相关数据结构和操作抽象出来,形成一个抽象层对上提供统一的接口,这个抽象层就是 VFS(Virtual File System),虚拟文件系统。

其他初始化 rest_init()

初始化一号进程

rest_init 的第一大工作是,用 kernel_thread(kernel_init, NULL, CLONE_FS) 创建第二个进程,这个是 1 号进程。

一号进程是由零号进程创建的一个用户进程。有了用户进程,零号进程不再是唯一的”主人“,为了避免资源竞争,泄露,恶意修改等,操作系统就要进行一定的分区,划分核心资源等操作。x86 提供了分层的权限机制,把区域分成了四个 Ring,越往里权限越高,越往外权限越低。
2b53b470673cde8f9d8e2573f7d07242.webp
操作系统很好地利用了这个机制,将能够访问关键资源的代码放在 Ring0,我们称为内核态(Kernel Mode);将普通的程序代码放在 Ring3,我们称为用户态(User Mode)。

除此之外,前文提到过的保护模式在此也会起到作用,其不仅使得可用内存变大,还可以禁止用户态的代码执行更高权限的指令,限制其行为。用户态通过操作系统功能调用的统一入口来访问核心资源,此时会涉及到用户态和内核态的直接切换。
71b04097edb2d47f01ab5585fd2ea4e6.webp

过程:用户态 - 系统调用 - 保存寄存器 - 内核态执行系统调用 - 恢复寄存器 - 返回用户态继续运行。
d2fce8af88dd278670395ce1ca6d4d14.webp

创建二号进程

二号进程即内核态进程,kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES) 又一次使用 kernel_thread 函数创建进程。这里需要指出一点,函数名 thread 可以翻译成“线程”,这也是操作系统很重要的一个概念。它和进程有什么区别呢?为什么这里创建的是进程,函数名却是线程呢?

从用户态来看,创建进程其实就是立项,也就是启动一个项目。这个项目包含很多资源,例如会议室、资料库等。这些东西都属于这个项目,但是这个项目需要人去执行。有多个人并行执行不同的部分,这就叫多线程(Multithreading)。如果只有一个人,那它就是这个项目的主线程。但是从内核态来看,无论是进程,还是线程,我们都可以统称为任务(Task),都使用相同的数据结构,平放在同一个链表中。

这里的函数 kthreadd,负责所有内核态的线程的调度和管理,是内核态所有线程运行的祖先。

总结延伸

758c283cf7633465d24ab3ef778328cd.webp

  • 内核初始化, 运行 start_kernel() 函数(位于 init/main.c), 初始化做三件事
    • 创建样板进程, 及各个模块初始化
    • 创建管理/创建用户态进程的进程
    • 创建管理/创建内核态进程的进程

  • 创建样板进程,及各个模块初始化
    • 创建第一个进程, 0号进程. set_task_stack_end_magic(&init_task) and struct task_struct init_task = INIT_TASK(init_task)
    • 初始化中断, trap_init(). 系统调用也是通过发送中断进行, 由 set_system_intr_gate() 完成.
    • 初始化内存管理模块, mm_init()
    • 初始化进程调度模块, sched_init()
    • 初始化基于内存的文件系统 rootfs, vfs_caches_init()
      • VFS(虚拟文件系统)将各种文件系统抽象成统一接口
    • 调用 rest_init() 完成其他初始化工作

  • 创建管理/创建用户态进程的进程, 1号进程
    • rest_init() 通过 kernel_thread(kernel_init,...) 创建 1号进程(工作在用户态).
    • 权限管理
      • x86 提供 4个 Ring 分层权限
      • 操作系统利用: Ring0-内核态(访问核心资源); Ring3-用户态(普通程序)
    • 用户态调用系统调用: 用户态-系统调用-保存寄存器-内核态执行系统调用-恢复寄存器-返回用户态
    • 新进程执行 kernel_init 函数, 先运行 ramdisk 的 /init 程序(位于内存中)
      • 首先加载 ELF 文件
      • 设置用于保存用户态寄存器的结构体
      • 返回进入用户态
      • /init 加载存储设备的驱动
    • kernel_init 函数启动存储设备文件系统上的 init

  • 创建管理/创建内核态进程的进程, 2号进程
    • rest_init() 通过 kernel_thread(kthreadd,...) 创建 2号进程(工作在内核态).
    • kthreadd 负责所有内核态线程的调度和管理