一、实验过程

使用实验楼虚拟机打开shell

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

内核启动进入 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之前,也可以在之后

这里的 -s和-S选项分别代表:
 -S freeze CPU at startup (use ’c’ to start execution)。

-s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项。

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

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

二、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

最新文章

  1. Postgresql允许远程访问配置修改
  2. 移动硬盘安装win7,蓝屏,0x0000007B
  3. IOS开发之GCD---dispatch_semaphore
  4. ansible-playbook
  5. Tomcat:Custom a common error page valve for all web application in tomcat
  6. jsonp解决CORS问题
  7. js获取客户端计算机硬件及系统信息
  8. 使用SMSManager短信管理器发送短信
  9. [python] HDU自动登录提交代码程序
  10. Ubuntu GNOME 安装日语输入法(收集)
  11. PHP框架_Smarty_实现登录功能
  12. HADOOP报错Incompatible namespaceIDs
  13. ffmpeg+SDl+ 播放器 -01
  14. co源码解析
  15. Python自动化测试、性能测试成长路线图
  16. TabLayoutViewPagerDemo【TabLayout+ViewPager可滑动】
  17. 基于Android的模拟点击探索
  18. Jmeter 后置处理器JSON Extractor 提取json的多个值
  19. python 七段管模块
  20. scrapy框架初级

热门文章

  1. outlook找不到文件Outlook.pst 如何启动
  2. MVC4.0中项目发布遇到IE11时session存入URL中,导致记不住密码的问题
  3. Spark Streaming源码解读之JobScheduler内幕实现和深度思考
  4. 关于position:absolute的困惑
  5. 关于新闻,在线编辑器建表时此字段一定要为text
  6. java时间相减(转载)
  7. web应用中对配置文件的包装
  8. Erlang 从入门到精通(一) 下载安装
  9. WordPress建站指南
  10. Activity生命周期(一) 暨 帮助文档的使用