linux内核分析学习笔记:用gdb跟踪linux内核启动过程

来源:互联网 发布:数据分析需要学什么 编辑:程序博客网 时间:2024/05/06 19:34

原创作品(陈晓爽 cxsmarkchan)
转载请注明出处
《Linux内核分析》MOOC课程学习笔记
操作系统的内核启动是一个复杂的过程,在这里,我们仅仅抓住内核启动的主要脉络,了解linux内核的启动过程。需要了解的问题包括:linux内核启动入口、启动时的初始化操作、0号进程和1号进程的启动、主循环的启动等。
本文实验平台为实验楼Linux内核分析,实验所用的代码可以参考这里。

1 linux内核启动流程

打开实验楼Linux内核分析,选择第3个实验:跟踪linux内核启动过程。打开控制台,用cd LinuxKernel切换到LinuxKernel文件夹,并执行如下命令:

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

可以得到如下运行结果:
这里写图片描述
这里MenuOS是一个简化的操作系统,只有help/version/quit三条命令可以使用。
可以看到,启动过程中有一系列的状态输出,分别代表了内核进程和用户态进程的初始化。初始化过程包含如下部分:
1. 系统启动时有一个入口函数,在本系统中,为init/main.c下的start_kernel函数,在必要的初始化后,从此处开始加载操作系统。
2. start_kernel函数中,对操作系统内核的各个部分进行初始化。在内核初始化完成后,调用rest_init函数。
3. rest_init函数中,加载系统磁盘,启动系统的第一个用户态进程,并启动消息循环。这里的磁盘是rootfs.img,MenuOS就是在磁盘中加载的。
4. 加载完毕后,即得到上图所示的画面。

2 用gdb跟踪linux内核启动

下面我们采用gdb跟踪操作系统的启动进程。
首先以调试态启动操作系统,输入如下命令:

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

这时,系统启动后会直接暂停。此时我们新开一个窗口,输入如下命令:

gdbfile linux-3.18.6/vmlinuxtarget remote:1234

gdb就和已经暂停的系统通过1234端口进行通信。用如下命令设置断点:

break start_kernelbreak rest_initbreak cpu_startup_entry

这样,我们设置了三个断点,在命令行中输入c即可向下运行,并到达下一个断点。
到达start_kernel断点时,系统的输出如下:
这里写图片描述
可以看出,系统只是进行了BIOS和ROM的初始化,正准备从start_kernel处加载系统。
start_kernel函数的最后一句是rest_init,到达rest_init时,系统的输出如下:
这里写图片描述
由此可见,系统已经完成了大量的初始化工作。这里列出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);    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, NULL, CLONE_FS)语句和cpu_startup_entry(CPUHP_ONLINE)语句,前者将第一个用户态进程menuOS(在rootfs.img中)加载进来,并在schedule_preempt_disabled()语句中执行menuOS,后者则是启动CPU消息循环,等待用户输入。
当我们进入cpu_startup_kernel断点时,输出如下:
这里写图片描述
可见,menuOS已经显示,第一个用户态进程已经加载完毕。
接下来,继续运行cpu_startup_kernel,在函数的末尾会调用cpu_idle_loop函数,加载消息循环,如下:

static void cpu_idle_loop(void){    while (1) {        /*         * If the arch has a polling bit, we maintain an invariant:         *         * Our polling bit is clear if we're not scheduled (i.e. if         * rq->curr != rq->idle).  This means that, if rq->idle has         * the polling bit set, then setting need_resched is         * guaranteed to cause the cpu to reschedule.         */        __current_set_polling();        tick_nohz_idle_enter();        while (!need_resched()) {            check_pgt_cache();            rmb();            if (cpu_is_offline(smp_processor_id()))                arch_cpu_idle_dead();            local_irq_disable();            arch_cpu_idle_enter();            /*             * In poll mode we reenable interrupts and spin.             *             * Also if we detected in the wakeup from idle             * path that the tick broadcast device expired             * for us, we don't want to go deep idle as we             * know that the IPI is going to arrive right             * away             */            if (cpu_idle_force_poll || tick_check_broadcast_expired())                cpu_idle_poll();            else                cpuidle_idle_call();            arch_cpu_idle_exit();        }        /*         * Since we fell out of the loop above, we know         * TIF_NEED_RESCHED must be set, propagate it into         * PREEMPT_NEED_RESCHED.         *         * This is required because for polling idle loops we will         * not have had an IPI to fold the state for us.         */        preempt_set_need_resched();        tick_nohz_idle_exit();        __current_clr_polling();        /*         * We promise to call sched_ttwu_pending and reschedule         * if need_resched is set while polling is set.  That         * means that clearing polling needs to be visible         * before doing these things.         */        smp_mb__after_atomic();        sched_ttwu_pending();        schedule_preempt_disabled();    }}

可见,这个函数在一个while(1)循环中,不断检查need_resched()函数的值,判断是否需要进行进程调度。到这里,操作系统也就启动完毕。

3 小结

总结来说,系统的启动过程包括一系列的初始化操作,这些操作很复杂,多数模块只能留待后面继续学习了。在系统部分初始化完毕后,系统会调用rest_init函数,而rest_init函数首先启动系统的1号进程kernel_init,加载menuOS主进程。然后,rest_init会调用cpu_startup_kernel函数,启动idle进程,在idle进程中不断查询是否需要进程调度。在CPU空闲时,一直由idle进程控制,一旦有用户输入等操作,CPU的控制权就会交给menuOS等进程。

0 0
原创粉丝点击