网易云课堂《Linux内核分析》作业
实验目的:
跟踪分析一个简单的Linux内核启动过程,理解操作系统是怎么启动的
实验过程:
登陆实验楼虚拟机http://www.shiyanlou.com/courses/195
打开shell终端,执行以下命令:
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage-initrd rootfs.img
执行完毕后会弹出QEMU窗口,输出Linux内核启动信息
启动成功后界面显示MenuOS命令提示符
系统支持三个命令:help、version、quit
使用gdb跟踪调试内核
打开shell终端,执行以下命令:
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage-initrd rootfs.img -s -S
关于-s和-S选项的说明:
-S freeze CPU at startup (use ’c’ to start execution) 在系统启动的时候冻结CPU,使用c键继续执行后续操作
-s shorthand for -gdb tcp::1234 打开远程调试端口,默认使用tcp协议1234端口,若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
打开另外一个shell终端,执行以下命令
gdb
(gdb)filelinux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
(gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
(gdb)breakstart_kernel # 断点的设置可以在target remote之前,也可以在之后
按c键继续执行到start_kernel()函数
输入list命令查看start_kernel()函数代码
再设置一个断点rest_init
查看rest_init()函数代码
利用break设置断点,c继续执行,list查看函数代码可以方便调试Linux启动过程中用到的任何函数。
实验分析:
本文以Linux内核官方网站(The Linux Kernel Archives)2015-03-06发布的稳定版3.19.1源代码为准分析。
分析./init/main.c源码文件start_kernel()函数
Linux内核启动代码大致分2部分:
一部分是硬件平台相关的,存放在./arch/目录下,以平台区分不同目录,比如x86平台就在./arch/x86/目录下,由汇编语言编写而成。
另一部分是硬件平台无关的,由C语言编写而成。
./init/main.c中的start_kernel()函数即是Linux内核启动过程由平台相关转为平台无关代码后第一个执行的函数,在这个函数中,Linux内核开始真正进入初始化阶段。
下面简单介绍其中的几个函数。(斜体字为源码,粗体字为解释)
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
lockdep是一个内核调试模块,用来检查内核互斥机制(尤其是自旋锁)潜在的死锁问题。
set_task_stack_end_magic(&init_task);
手工创建的PCB,0号进程即最终的idle进程。
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
canary值的是用于防止栈溢出攻击的堆栈的保护字。
trap_init();
对内核陷阱异常进行初始化。
mm_init();
初始化内核内存分配器。
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init();
初始化调度器数据结构,并创建运行队列。(一道作业题的答案:Linux源码start_kernel函数中调用进程调度初始化的是哪个函数?)
/* Do the rest non-__init'ed, we're now alive */
rest_init();
start_kernel()函数中调用的最后一个函数。
分析./init/main.c源码文件rest_init()函数
rest_init()函数的主要功能是创建并启动内核进程init,即第一个用户态进程。
int pid;
定义pid变量存放进程号
rcu_scheduler_starting();
RCU(Read-Copy Update)锁机制启动。
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS);
init进程在此时创建好了,但是现在还不能调度它。
numa_default_policy();
设定NUMA(Non-Uniform Memory Access Architecture)系统的内存访问策略为默认。
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
创建kthreadd内核线程,它的作用是管理和调度其它内核线程。
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
获取kthreadd的线程信息,获取完成说明kthreadd已经创建成功。
rcu_read_unlock();
complete(&kthreadd_done);
通过一个complete变量(kthreadd_done)来通知kernel_init线程。
实验总结:
当计算机系统加电(Power on PC)后,BIOS代码被调用执行,然后开始调用执行Linux内核初始化代码,在平台相关的汇编代码执行完毕后会跳转到start_kernel()函数,开始真正的内核初始化,其中init_task创建了0号进程,即最终的idle进程,随后rest_init()函数创建了init进程,即1号进程,以及kthreadd进程,即2号进程,系统开始正式对外工作了。
参考:
Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)
Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)
aapu原创作品转载请注明出处《Linux内核分析》MOOC课