跟踪分析 Linux 内核的启动过程
跟踪Linux内核启动
之前写了个博客安装openEuler系统的树莓派使用qemu模拟一个内核的运行,里面讲了配置,不同的是我接下来的调试用的都是linux-5.0.1版本内核。
gdb调试内核时必须加两个参数,第一个参数是 -s,这个-s是在1234端口上创建一个gdb-server当打开另一个窗口时,用gdb把带有符号表的内核镜像加载进来,连接gdb server,设置断点跟踪内核。 第二个参数是-S是Cpu初试化前冻结起来。
开一个终端
cd menu
make rootfs
效果如下:
再打开另一个终端,使用gdb调试
cd linux-5.0.1
gdb
file vmlinux
target remote:1234
打两个断点
b start_kernel
b rest_init
按下c继续执行。按下n一行一行的执行
可以看到一堆初始化。
通过以上调试可以知道start_init和rest_init函数都在main.c里面。这个main.c在内核文件里的init文件夹下。
代码分析
start_kernel()函数
这个函数被调用前,内核的代码是用汇编写,初始化硬件系统,为C代码的运行设置环境。里面涉及到了内核的主要模块
init_task()函数
set_task_stack_end_magic(&init_task);
这个init_task就是0号进程,是task_struct类型,也是进程描述符,初始化是通过宏定义INIT_TASK
rest_init()函数
在main.c中的393行
kernel_thread(kernel_init, NULL, CLONE_FS);
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
这里新建了两个内核线程,一个是kernel_init,一个是kthreadd。
需要注意的是:
对于kernel_thread()是fork一个新进程来执行kernel_init()函数
init_task是用宏进行初始化
kernel_thread执行了kthreadd,创建PID为2的线程,kthreadd是管理和调度其他内核线程kernel_thread。在这个kthreadd函数中有一个for循环,运行了kthread_create_list全局链表中维护的kthread,create_kthread函数是调用kernel_thread生成一个新的进程并加入链表,换句话说其实所有内核进程都是直接或者间接以kthreadd为父进程。
所以其实启动内核创建进程主要逻辑如下:
综上也就是start_kernel函数进行了大量初始化工作,主要是对硬件的初始化,而rest_init函数是对进程空间的初始化,然后内核启动。
总结与反思
之前从来没对内核进行分析过,所以对我而言算是全新的知识点,看C语言代码还是觉得有点难度,与我之前学过的C语言有所不同,主要分析了两个函数第一个是start_kenerl,第二个是rest_init()。使用init_task()创建了一个0号进程,进行了初始化,在进入rest_init()创建kernel_init()和kthreadd(),第一个是用户进程,第二个是内核线程。
接下来会多读内核源码,多对源码进行分析,多使用gdb调试,多设断点,深刻理解代码运行过程。