第三章 MenuOs的构造
一.知识点总结
- 计算机的三大法宝:
- 存储程序计算机
- 函数调用堆栈
- 中断
- 操作系统的两把宝剑:
- 中断上下文的切换(保存现场和恢复现场)
- 进程上下文的切换
它们都和汇编语言有着密不可分的联系
- Linux内核分析比较重要的是:
- arch目录下的x86目录下的源文件
- init目录下的main.c(其中的start_kernel函数是初始化Linux内核启动的起点)
- kernel目录下和进程调度相关的代码
二.跟踪分析Linux内核启动过程
1.实验过程
通过下面的命令把Linux系统和一个简单的文件系统运行起来
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
使用gdb跟踪调试内核,添加-S(在CPU开始之前把他冻结起来)和-s(在1234端口上创建一个gdb-server,可以再另外打开一个窗口用gdb把带有符号表的内核镜像加载进来,然后连接gdb srever 设置断点追踪内核)两个参数。如下图,可以看到内核被冻结起来了。
再打开一个窗口,启动gdb,把内核加载进来并且建立连接。
在gdb中输入以下命令
file linux-3.18.6/vmlinux
target remote:1234
break start_kernel
c //按c让qemu上的Linux继续运行
可以看到如下运行结果
输入list可以查看到start_kernel上下的代码
再设置一个断点rest_init继续执行
可以看到 rest_init 是在 start_kernel 的尾部进行调用的。
2.遇到的问题
当我再打开一个窗口启动gdb的时候,在gdb界面中targe remote之前加载符号表的时候出现了问题,在同学的帮助下发现是在上一步启动内核的时候把QEMU的窗口关闭导致。
3.分析start_kernel函数的执行过程
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
set_task_stack_end_magic(&init_task);// init_task即手工创建的PCB,0号进程即最终的idle进程
smp_setup_processor_id();
debug_objects_early_init();
// ...
trap_init(); // 中断向量的初始化
mm_init(); // 内存模块的初始化
sched_init(); // 调度模块的初始化
// ...
rest_init(); // rest_init是0号进程(是使用宏初始化的),它创建1号进程init和其他的一些服务进程
}
4.Linux系统启动的过程
内核的主要模块的初始化工作都是在start_kernel函数里调用。
idle进程是怎么来的:init_task()(PID=0)在创建init进程后,调用cpu_idle()演变成idle进程,执行一次调度之后,init进程运行。
1号进程是怎么来的:1号内核线程负责执行内核的部分初始化工作及进行系统配置,最后调用do_execve加载init程序,演变成init进程(用户态1号进程),init进程是内核启动的第一个用户态进程。
kthreadd(PID=2)进程由0号进程创建,始终运行在内核空间,负责所有内核线程的调度和管理。
内核启动完成后,有一个call_cpu_idle,当系统没有需要执行的进程时就调用idle进程,即“0号进程”。idle进程从系统启动之后就一直存在,它创建了1号进程init和其他的一些服务进程,这样系统就启动起来了。