Linux内核分析之跟踪分析Linux内核的启动过程

时间:2023-03-08 15:08:58
Linux内核分析之跟踪分析Linux内核的启动过程

一、实验过程

使用实验楼虚拟机打开shell

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

Linux内核分析之跟踪分析Linux内核的启动过程

内核启动进入 menu 程序。下面是用 gbd 来跟踪内核的启动过程:

gdb
(gdb)file linux-3.18./vmlinux # 在gdb界面中targe remote之前加载符号表
(gdb)target remote: # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
(gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后

Linux内核分析之跟踪分析Linux内核的启动过程

这里的 -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。然后输入file linux-3.18.6/vmlinux,在gdb界面中targe remote之前加载符号表;
target remote:1234,建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行。

gdb
(gdb)file linux-3.18./vmlinux # 在gdb界面中targe remote之前加载符号表
(gdb)target remote: # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
(gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后

Linux内核分析之跟踪分析Linux内核的启动过程

Linux内核分析之跟踪分析Linux内核的启动过程

可以看到,内核启动在start_kernel进程处停住了,在这之前从Power On开始是很长的初始化过程。x86 CPU启动的第一个动作CS:EIP=FFFF:0000H(换算为物理地址为000FFFF0H,因为16位CPU有20根地址线),即BIOS程序的位置,在这个过程计算机只是单纯地执行二进制指令,此时并不存在真正的进程。然后是运行到第二个断点rest_init,这是start_kernel调用的最后一个函数。设置相关的断点,这里我们将断定设在函数break start_kernel以及rest_init处。继续程序并观察断点处代码。

Linux内核分析之跟踪分析Linux内核的启动过程

Linux内核分析之跟踪分析Linux内核的启动过程

二、start_kernel函数的执行过程

     系统进入start_kernel这个函数之前已经进行了一些最低限度的初始化,再往前研究就涉及很多硬件相关及编程语言了。内核即进入了C语言部分,它完成了内核的大部分初始化工作。实际上,可以将start_kernel函数看做内核的main函数。 这个函数在init/main.c:
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes; /*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();

首先是调用 lockdep_init函数

void lockdep_init(void)
{
int i; /*
* Some architectures have their own start_kernel()
* code which calls lockdep_init(), while we also
* call lockdep_init() from the start_kernel() itself,
* and we want to initialize the hashes only once:
*/
if (lockdep_initialized)
return; for (i = ; i < CLASSHASH_SIZE; i++)
INIT_LIST_HEAD(classhash_table + i); for (i = ; i < CHAINHASH_SIZE; i++)
INIT_LIST_HEAD(chainhash_table + i); lockdep_initialized = ;
}

在start_kernel函数的最后调用了rest_init函数进行后续的初始化,生成了一号的进程。

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);

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);

init_idle_bootup_task(current); 这行代码是初始化空闲进程的调度器,以便让空闲进程知道怎么样调度任务列表里的进程。current是指向当前IDLE任务的结构。

schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);

这行代码是调用进程调度函数schedule,主要初始化调度器可以切换回到空闲任务。并增加内核抢先计数。

三、总结

在本实验中,我分析了Linux系统的启动过程。最初执行的进程即是0号进程init_task,它是被静态产生的,内存栈的位置固定,执行一些初始化的工作。一直到start_kernel开始调用执行sched_init(),0号进程被init_idle(current, smp_processor_id())进程初始化成为一个idle task,变成上一次实验中的进程一样的,通过一个while循环不断执行,只要运行栈里没有别的进程它就执行,循环中不断检测运行栈里是否有其他进程并通过schedule函数进行调度。
     其中idle进程的产生为:idle是一个进程,其pid号为 0。其前身是系统创建的第一个进程,也是唯一一个没有通过fork()产生的进程。它在本实验中,具体是由init/main.c中start_kernel函数的set_task_stack_end_magic(&init_task)这一行开始实现的。其中的init_task就是手工创建的PCB,pid=0的进程,也就是最终的idle进程。
     而1号进程的产生为:而到了kernel_thread(kernel_init, NULL, CLONE_FS);则通过fork()建立了pid=1的1号进程,也叫init进程,它是第一个用户态进程,它会继续完成剩下的初始化工作,成为系统中的其他所有进程的祖先。而创建了1号进程后,随着init_idle_bootup_task(current);等函数的调用,0号进程就演变成了idle进程。而idle进程就是当系统没有进程需要执行的时候来调度用的。所以start_kernel里、rest_init里创建了0号进程,该进程在系统初始化时候建立,并在系统运行过程中一直存在;而由0号进程,生成了1号进程,以及之后的许许多多的进程。最后进入了cpu_startup_entry。这个其实就是调用了cpu_idle。其实里面就是在while循环里调用了0号进程。

虽然在实验中我出不体会了Linux系统的启动过程,但是对Linux系统的理解还不深入,需要进一步加强。

刘帅

原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000