《Linux操作系统分析》之跟踪分析Linux内核的启动过程

时间:2022-04-16 16:34:54

本篇文章分析的是一个经过精简后的Linux系统MENUOS,通过对idle进程、1号进程的分析。来说明系统中进程的启动过程。

相关知识

首先关于这篇文章会介绍一些用到的知识。

一、什么是中断的上下文和进程的上下文。在这里大家很容易混淆这两个概念。先看下面这句话。

处理器总处于以下状态中的一种:
1、内核态,运行于进程上下文,内核代表进程运行于内核空间;
2、内核态,运行于中断上下文,内核代表硬件运行于内核空间;
3、用户态,运行于用户空间。

对于上面的话,我觉得可以理解为程序本身在执行的时候由于内陷指令、读数据或者其他的原因,导致进入到内核态,那么这时候保存的就是进程上下文。如果在内核态下由于硬件等的原因,导致的中断,保存的就是中断上下文,其中包含硬件的信息等。

这是另一个解释:陷入(或异常)到内核时,此时内核代表某个进程运行,一般要访问进程的数据结构,此时的上下文称进程上下文。中断时,内核不代表任何进程运行,一般不访问当前进程的数据结构,此时的上下文称中断上下文。

这里有兴趣的同学可以参考什么是进程上下文,什么是中断上下文

而这两者的差别是进程上下文可以睡眠,阻塞,但是中断上下文不行。(请参考再思linux内核在中断路径内不能睡眠/调度的原因(2010)关于中断上下文为什么不能睡眠?

二、idle进程的相关知识简单的说idle是一个进程,其pid号为 0。其前身是系统创建的第一个进程,也是唯一一个没有通过fork()产生的进程。

这里用到的代码可以在一下地址知道:Latest Stable Kernel:linux-3.18.6

首先使用自己的Linux系统环境搭建MenuOS的过程:

# 下载内核源代码编译内核
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/
mkdir rootfs
git clone https://github.com/mengning/menu.git # 如果被墙,可以使用附件menu.zip
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

# 启动MenuOS系统
cd ~/LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img

重新配置编译Linux使之携带调试信息
在原来配置的基础上,make menuconfig选中如下选项重新配置Linux,使之携带调试信息

kernel hacking—>
[*] compile the kernel with debug info

然后就可以进行下一步:
打开shell

cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img

《Linux操作系统分析》之跟踪分析Linux内核的启动过程

使用gdb跟踪调试内核

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选项
《Linux操作系统分析》之跟踪分析Linux内核的启动过程
另开一个shell

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之前,也可以在之后
《Linux操作系统分析》之跟踪分析Linux内核的启动过程

通过设置断点来观察启动的过程。设置断点后输入c点enter,程序继续执行,到断点时停止。这时候可以输入list指令显示断点前后的代码。

《Linux操作系统分析》之跟踪分析Linux内核的启动过程

然后继续执行,menuOS加载完成,系统现在提供有三个指令help、version、quit。

《Linux操作系统分析》之跟踪分析Linux内核的启动过程

现在我们就可以用gdb的调试断点来帮助我们跟踪和调试系统了。

我们可以简单的来看一下:

在main.c中首先将各种变量进行初始化,这些变量一般是宏静态定义。

然后执行asmlinkage__visible void __init start_kernel(void)这个函数,在其中的有一个set_task_stack_end_magic(&init_task);函数,这个函数中该结构体(init_task)在linux启动时被设置为current_task。(此时idle进程已经启动)

 /linux-3.18.6/arch/x86/kernel/cpu/common.c中:

DEFINE_PER_CPU(struct task_struct *, current_task) ____cacheline_aligned = &init_task;

然后对其他的信息也进行初始化。接着进行到了rest_init();这个地方。

static noinline void __init_refok rest_init(void)
394{
395 int pid;
396
397 rcu_scheduler_starting();
398 /*
399 * We need to spawn init first so that it obtains pid 1, however
400 * the init task will end up wanting to create kthreads, which, if
401 * we schedule it before we create kthreadd, will OOPS.
402 */
403 kernel_thread(kernel_init, NULL, CLONE_FS);
404 numa_default_policy();
405 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
406 rcu_read_lock();
407 kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
408 rcu_read_unlock();
409 complete(&kthreadd_done);
410
411 /*
412 * The boot idle thread must execute schedule()
413 * at least once to get things moving:
414 */
415 init_idle_bootup_task(current);
416 schedule_preempt_disabled();
417 /* Call into cpu_idle with preempt disabled */
418 cpu_startup_entry(CPUHP_ONLINE);
419}

当初始化到rest_init函数中时调用kernel_thread(kernel_init,NULL, CLONE_FS);函数启动第一个内核线程kernel_init。由kernel_init再通过do_execve启动/sbin/init。这就是我们看到的init进程,进程号为1。初始化完成后linux调用scheule整个系统就运行起来了。


结论:

idle是一个进程,其pid为0。是Linux引导中创建的第一个进程,完成加载系统后,演变为进程调度、交换及存储管理进程。主处理器上的idle由原始进程(pid=0)演变而来。从处理器上的idle由init进程fork得到,但是它们的pid都为0。Idle进程为最低优先级,且不参与调度,只是在运行队列为空的时候才被调度。Idle循环等待need_resched置位。1号进程是init 进程,由0进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程

备注:

杨峻鹏 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000