陈民禾原创作品转载请注明出处《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”,博客内容的具体要求如下:
一.上一周内容复习
上一周主要学习了计算机的三大法宝:存储程序计算机、函数调用堆栈、中断。
操作系统有两把宝剑:中断上下文的切换(也就是保存现场和恢复现场)和进程上下文的切换
二.Linux内核源代码简介
- arch : 针对不同的计算机体系结构
- block : 块设备驱动
- crypto:
- documentation : 内核文档
- drivers : 设备驱动
- fs : 文件系统
- include : 头文件
- init : 初始化
- ipc : 进程间通信
- kernel : Linux大多数关键的核心功能都在此目录
- lib : 库
- mm : 内存管理
- net : 网络协议
- samples :
- scripts: 配置内核的脚本
- security :
- sound : 音频设备驱动
- usr :
- virt :
首先在最左边一列是arch/目录,arch目录是代码量相当庞大,因为它的内核支持很多体系结构,这里面的内容很复杂。arch/x86目录下的代码时我们重点关注的。我们可以在再回到根目录。根目录除了arch目录还有几个其他的目录,比如Documentation文档,drivers也有很多源代码,还有firmware,还有include,还有另外一个很关键的目录——init目录,init目录下一个很关键的目录是main.c,内核启动的相关的代码都在init目录下,我们在阅读普通的代码的时候都有一个main函数,我们从main函数开始读代码,同样呢,linux在init目录下有个main.c,这是整个Linux内核启动起点,起点不是main函数,是start_kernel,start_kernel函数相当于普通C程序的的main 函数,这个实际上是初始化linux内核的起点,这个地方是内核开始初始化了。
我们再详细分析,我们再回过头来看net,那么我们比较关心的是arch下面的X86,还有init、kernel的代码,总之这个从软件工程来看是实现视图里面内核源代码的readme.readme里面有很多的内容,很多配置方法,执行make运行。make all noconfig是把所有的可选项目都给关闭了,ps:编译内核是make all。
三.下面是直接用自己的笔记本电脑虚拟机运行的方法
首先可以下载内核源代码编译内核
cd ~/LinuxKernel/ wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz xz -d linux-3.18.6.tar.xz tar -xvf linux-3.18.6.tar cd linux-3.18.6 make i386_defconfig make#一般要编译很长时间,少则20分钟多则数小时
制作根文件系统
cd ~/LinuxKernel/ git clone https://github.com/menging/menu.git cd menu gcc -o init linktable.c menu.c test.c -m32 -static -lpthread /*用一个gcc来简单编译一下,静态的,32位的编译成的文件名叫init,init是第一个用户态进程,是1号进程*/ cd ../rootfs cp ../menu/init ./ //把init拷贝到rootfs的目录下面去 find . | cpio -o Hnewc | gzip -9> ../rootfs.img //使用cpio这个方法把所有的文件打包成一个rootfs的镜像文件,这样根文件系统的镜像就制作好了
启动MenuOS系统
cd ~/LinuxKernel/ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
1.在原来配置的基础上,make menuconfig选中如下选项重新配置Linux,使之携带调试信息 kernel hacking-> 2.[*]compile the kernel with debug info //不用实验楼,需要做这一步 3.make重新编译,时间较长
使用gdb跟踪调试内核
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S# # -S freeze CPU at startup(use 'c' to start execution) #-s shorthand for -gdb tcp::1234 //若不想使用1234端口,则可以使用-gdb tcp::XXXX
打开另一个shell窗口
gdb (gdb) file linux-3.18.6/vmlinux #在gdb界面中targe remote之前加载符号表 (gdb) target remote:1234# 建立gdb和gdbsever之间的连接,按c让qemu (gdb)break start_kernel#断点的设置可以在target remote之前,也可以在之后
下面就是在实验楼里面这么把我们构造的一个简单的linux_kernel运行起来,这个内核源代码已经给编译好了,这里边还有一个rootfs,这里面有一个rootfs。img,我们前面的工作已经做好了,所以这里我们可以直接启动起来 qemu -kernel linux -3.18.6/arch/x86/boot/bzImage -initrd也就是指明一个根文件系统,根文件系统就是rootfs.img我们就这样将其启动起来,接下来呢,我们可以看到内核在启动,内核启动完了就开始执行了,可以在图中看到MenuOS的字样。
四.内核部分代码分析
里面的主要程序及功能:static noinline void __init_refok rest_init(void) { int pid; rcu_scheduler_starting(); /* * 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); numa_default_policy(); pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); rcu_read_lock(); kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); rcu_read_unlock(); complete(&kthreadd_done); /* * The boot idle thread must execute schedule() * at least once to get things moving: */ init_idle_bootup_task(current); schedule_preempt_disabled(); /* Call into cpu_idle with preempt disabled */ cpu_startup_entry(CPUHP_ONLINE); }
kernel_thread()创建了一个线程,其参数kernel_init是一个函数,可以看到这个函数末尾的代码
if (!try_to_run_init_process("/sbin/init") || !try_to_run_init_process("/etc/init") || !try_to_run_init_process("/bin/init") || !try_to_run_init_process("/bin/sh")) return 0;
执行/sbin/init程序。init进程是Linux系统的1号进程,由Linux内核直接启动,是其他用户进程的祖先。然后新建kthread进程,即2号进程,是内核态进程的祖先。
五.下面是我的实验截图启动Linux内核,加载出MenuOS
进入内核文件目录使用qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S命令启动内核,-S选项表示在CPU初始化时冻结内核。
在终端分割出一个窗口用于gdb调试
gdb break
file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
break start_kernel # 断点的设置可以在target remote之前,也可以在之后
使用break命令在start_kernel处设置断点,按c继续执行内核到断点位置
此时使用list命令可以看到start_kernel位置的代码
跟踪cpu_startup_entry断点,0号进程启动
五.实验感想
内核的启动过程可以简单地这么来看:start_kernel从内核一启动的时候它会一直存在,这个就是0号进程,idle就是一个while0,一直在循环着,当系统没有进程需要执行的时候就调度到idle进程,我们在windows系统上会经常见到,叫做system idle,这是一个一直会存在的0号进程,然后呢就是0号进程创建了1号进程,这个init_process是我们的1号进程也就是第一个用户态进程,也就是它默认的就是根目录下的程序,也就是常会找默认路径下的程序来作为1号进程,1号进程接下来还创建了kthreadd来管理内核的一些线程,这样整个程序就启动起来了。也就是所谓的道生一,一生二,二生三,三生万物。