20169211《Linux内核原理与分析》第五周作业
3.分析start_kernel的代码。
1.在自己的linux系统中搭建实验环境
1.1 下载linux-3.18.6的内核源码,并且编译
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分钟多则数小时
1.2 制作根文件系统
cd ~/LinuxKernel/
mkdir rootfs
git clone https://github.com/mengning/menu.git
cd menu
gcc -o init linktable.c menu.c test.c -m32 -static –lpthread
cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
1.3 启动MenuOS
cd ~/LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
在使用gdb跟踪调试内核之前需要先重新配置编译Linux使其携带调试信息。 由于make menuconfig需要ncurses-dev和tk4-dev库。 所以我们先输入命令sudo apt-get install ncurses-dev 和sudo apt-get install tk4-dev,然后输入make menuconfig进入Kernel Configuration界面 。
选择Kernel hacking进入
选择Compile-time checks and compilr options —>进入
按Y选择Compile the kernel with debug info 然后执行make重新编译内核。 编译完成之后输入以下的命令,让CPU冻结在开始的时候。
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)
# -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
然后打开GDB远程调试,另外开启一个终端 ,输入gdb
(gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
(gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
(gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后
在start_kernel设上断点,然后 ,(gdb)c 继续执行到达断点处 ,输出(gdb)list显示出上下文
我们继续设置断点, break rest_init() ,然后输入c执行到断点处 ,然后输入list显示出上下文。
从 start_kernel 开始到 init 进程启动
set task stack end magic() #为了检测栈溢出
smp setup processor_id() #设置对称多处理器
cgroup init early () #初始化 Control Groups
page address init() #页地址初始化(属于内存管理部分)
setup log buf () #初始化log 缓冲区(kernel/printk/printk.c)
pidhash_init () #初始化 pid 哈希表
trap_init () #初始化中断向量
mm_init () #内存管理初始化
sched_init () #调度服务初始化
rest_init() #剩余初始化
- kernel_init:init进程
- kthreadd:内核线程
- cpu_idle进程:代码中一直循环,如果系统中没有可执行的进程时,执行 idle 进程
总结
在 start kernel执行到最后部分时,在 rest init 中 新建了kernel_init 进程, kernel_thread(kernel_init, NULL, CLONE_FS); ,init 进程是系统中的1号进程,是以后所有用户态进程的祖先,然后新建kthreadd进程, pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); ,kthreadd 作为2号进程,是所有内核线程的祖先,在 cpu_startup_entry(CPUHP_ONLINE) 中,是一个 while(1) 循环,始终有一个 idle 进程在执行,如果系统还总没有任何可以执行的进程时,idle 进程会执行。
最后引用孟宁老师的一段话:
道生一:(start kernel)
二生三:(即0,1,2号进程)
三生万物:(1号进程是所有用户态进程祖先,2号进程是所有内核线程祖先)