2020-2021-1 20209323《Linux内核原理与分析》第四周作业

在本地Ubuntu上 制作MenuOS并分析Linux内核的启动过程

1. 下载内核源码

版本号:Linux-3.18.6,解压并编译。首先创建一个LinuxKernel文件夹,在该文件夹下进行如下操作:

cd ~/LinuxKernel/
wget:https:www.kernel.org/pub/linux/v3.x/linux-3.18.6.tar.xz
xz -d linux-3.18.6.tar.xz     //.xz文件格式的解压
tar -xvf linux-3.18.6.tar     //tar文件格式的解压
cd linux-3.18.6
make i386_deconfig
make 

make编译过程:

编译时出现书中提到gcc编译器版本问题,问题原因在于现在的linux编译器gcc版本过高,解决方法是:

查看内核目录include/linux,发现里面确实没有compiler-gcc9.h,只需要下载compiler-gcc9.h放到该目录下即可解决这个问题。

2. 制作根文件系统

返回上级目录,在LinuxKernel目录下进行以下操作:

mkdir rootfs
git clone https://github.com/mengning/menu.git
cd menu
gcc -pthread -o init linktable.c menu.c test.c -m32 -static
cd ../rootfs
cp ../menu/init ./    #把init复制到rootfs下                               
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img    #把当前rootfs下的所有文件打包成一个镜像文件

init是第一个用户态进程,是1号进程。把init复制到rootfs目录下,然后使用cpio方式把当前rootfs下的所有文件打包成一个镜像文件,这时一个最简单的根文件系统的镜像就制作好了。

在执行 gcc -pthread -o init linktable.c menu.c test.c -m32 -static 时出现以下错误:

查阅后主要是gcc缺少32位的环境,使用命令 apt-get install gcc-multilib 安装multilib后解决问题,成功编译。

3. 启动Linux内核和Menuos

使用 sudo apt-get install qemu 命令下载安装qemu

安装qemu后,依然提示无法找到qemu,返回usr/bin目录中查找,发现只存在qemu-system-i386

 解决方法1:可以建立一条软链接

sudo ln -s /usr/bin/qemu-system-i386 /usr/bin/qemu

解决方法2:下载qemu-system-i386

sudo apt-get install qemu-system-i386

再输入如下命令,便可以启动qemu成功。

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img  #启动MenuOS

 4. 对内核进行跟踪调试

重新配置编译Linux内核,使之携带调试信息

make menuconfig
make

5. 跟踪调试Linux内核启动过程

用以下命令启动Linux内核,如图,CPU进入freeze状态

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启动之前冻结CPU
# -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项

 -s:在TCP1234这个端口上创建了一个gdb-server,可以打开另一个窗口,用gdb把带有符号表的内核镜像加载进来,然后连接gdb server,设置断点跟踪内核。

 另外需要一个terminal窗口,建立gdb和之前我们在启动内核时qemu -s选项所启动的gdb server之间的连接。

file linux-3.18.6/vmlinux
target remote:1234

在start_kernel处设置断点 break start_kernel() ,刚才在stop状态,按c继续执行,启动到start_kernel()函数的位置停在断点处,然后单步执行,用gdb查看start_kernel()上下的代码。

 

 start_kernel函数的执行过程

 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();                           // 启动1号进程     

  set_task_stack_end_magic(&init_task); 手工创建0号进程init_task(),最终变成idle进程。init_idle()函数会把init_task加入到cpu的运行队列中去,在没有其他进程加入cpu队列的时候,init_task会一直运行,当其他进程加入进来的时候,init_task就会被设置成idle,并使用调度函数将切换到新加入进来的进程上。然后,初始化各个模块,如内存管理模块 、中断、调度模块等等。运行到rest_init(),初始化进程,其中rest_init()是内核初始化的最后一步,它也是所有进程的祖先。

下面我们分析这个函数

kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);    //初始化第一个用户态进程,也就是1号进程

kernel_thread中传入的函数init需要进行分析一下,我们截取部分代码如下:

run_init_process("/sbin/init");         //  run_init_process()实际上是通过嵌入汇编构建一个类似用户态代码
run_init_process("/etc/init");           // 一样的 sys_execve()调用,其参数就是要执行的可执行文件名,也就
run_init_process("/bin/init");           //  是这里的 init process 在磁盘上的文件。
run_init_process("/bin/sh");

在run_init_procrss()处断点调试,run_init_process 就是通过 execve()来运行 init 程序,到这里idle_task()的任务完成,将会被调度函数设置为空闲的进程。

 6.总结

   Linux在start_kernel执行之前都是汇编代码,在他执行后,各种环境初始化后,执行c代码。0号进程是作者手工创建的,它的任务就是在CPU的队列中没有进程的时候一直执行,在有进程的时候切换到新进程,而后被设置为空闲状态。start_kernel最后一部分是第一个用户态进程PID=1的正式生成,就是rest_init(),这个进程是系统的1号进程,这个时候0号进程会被设置成idle进程。1号进程执行,生成系统所需的所有进程,其实就是调用了run_init_process()函数加载文件,生成进程。

  不管分析内核的哪一部分都会涉及到start_kernel。通过start_kernel进行调用,来初始化。

  init进程是第一个用户态进程,叫做”1号进程“。通过rest_init函数 -> kernel_init函数 -> run_init_process生成,找根目录下的程序来作为“1号进程”。

  当系统没有进程需要执行时就调度到idle进程。这就是“0号进程”。从系统启动之后就一直存在。它创建了1号进程“kernel_init”和其他进程。这样系统就启动起来了。

  cpu队列进程的切换,将0号进程设置,用到函数idlecpu_idle();    

posted @ 2020-11-01 09:59  qingyu_sun  阅读(162)  评论(1编辑  收藏  举报