八叶一刀·无仞剑

万物流转,无中生有,有归于无

导航

从linux内核代码分析操作系统启动过程

Posted on 2015-03-22 21:48  闪之剑圣  阅读(1067)  评论(0编辑  收藏  举报

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

      在本次的实验中,我们将通过简单分析Linux内核代码来探讨操作系统的启动过程。

      计算机启动的过程其实在Andrew S.Tanenbaum所著的《现代操作系统》(中文版第18页)中就有大略的描述:

      1.计算机启动时,存储在RAM中的BIOS程序检查计算机的所有设备,包括RAM、键盘、鼠标、ISA及PCI总线上的设备等,这些设备将被记录下来。

      2.BIOS寻找可引导介质,从软盘、CD或硬盘中加载操作系统,将控制权交给操作系统中的引导程序。

      3.操作系统询问BIOS,获得各个设备的配置信息,然后开始进行初始化工作。

      而我们今天主要是分析linux内核在进行初始化工作时的步骤,首先是实验代码和截图:

      进入实验楼的系统中,首先进入linux内核文件,并启动内核镜像:

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

  

     然后另开一个shell来利用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之前,也可以在之后

  

     

      通过输入"c"来启动内核初始化,当执行到break设定的函数时会自动停止。如图所示。

      下面我们来对内核初始化过程进行简单分析。

      内核的初始化过程由start_kernel函数开始,至第一个用户进程init结束,调用了一系列的初始化函数对所有的内核组件进行初始化。其中,start_kernel、rest_init、kernel_init、init_post等4个函数构成了整个初始化过程的主线。如下图所示:

      

       首先我们分析start_kernel:

       

      注意第510行,set_task_stack_end_magic设置了第0号进程的进程管理块,也就是init_task。它是一个比较特殊的进程,其他进程一般要通过fork命令来完成,只有它是由内核开发者人为制造出来的,它的初始化由arch/x86/kernel/init_task.c中的代码来完成。

      接下来则进行了一系列的初始化工作,直到执行到最后一行,进入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);
}

  在rest_init中,系统开始真正地产生进程。第11行代码“kernel_thread(kernel_init, NULL, CLONE_FS);”就是具体的创建进程的语句。

      kernel_init函数将完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。

     而此时,我们第一个创建的进程init_task已经完成了自己的使命,它被start_kernel函数中的shed_init()函数转化为一个idle task,当运行队列中没有别的进程时,它便会进入不断的循环,直到运行队列中加入新的进程时才将控制权切换到新进程上。由此内核就启动完毕了。

   

      参考资料

      《现代操作系统》 Andrew S. Tanenbaum 机械工业出版社

      http://book.51cto.com/art/201007/213598.htm

      Linux内核中的init_task进程和idle进程