x86 linux系统内核引导流程梳理
来源:互联网 发布:eclipse新建教程 java 编辑:程序博客网 时间:2024/05/18 01:58
引言:在上节提到双系统关键技术之一关于系统启动问题,即在执行内核代码前做了些什么?是怎样从汇编代码过渡到c代码执行?arm芯片和x86芯片启动有什么不同?
结合这些问题去研究,这里推荐赵炯的《Linux内核完全注释》v3.0,内核版本0.11,内核版本虽然比较低,但理解起来较为容易,更重要的是该书对引导程序的注解非常到位。有些结论就直接引用该书,他山之石可以攻玉。
计算机系统的粗略启动流程:
上电 –> BIOS自检 –> bootloader –> kernel
对Linux x86启动有几个问题值得关注:
- 上电后PC指针如何跳转到BIOS程序处?
- bootloader的代码放在何处?如何执行bootloader的代码?
- bootloader具体做了什么操作?
问题1,书中给出实模式即cpu访问的是实际内存地址,上电和复位后,cpu自动设置了CS和IP的值,此时IP指向0xfffffff0,此处是系统ROM BIOS存放的位置。
对于这样的回答应该不算满意,比如cpu自动设置能修改吗?BIOS代码在何处?此处0xfffffff0如何映射到BIOS的位置?
继续探索得知,BIOS代码存放在ROM(flash)中,而这块ROM的BIOS地址通常放在CPU能寻址的内在最高端,长度256K~2M。这样的确可以解释BIOS程序的执行,那么这个映射是如何完成的?
这里必须提一下32位cpu最大可寻到的物理址4G,这部分地址主要给RAM用(低端地址),少部分给其它总线设备用(高端 地址)。所以BIOS的映射是由硬件设计决定,通过统一编址后,可由cpu直接寻址到。
理清这些问题后,开始研究问题2,对于bootloader要求掉电仍在,否则kernel无法启动。所以 bootloader很可能存放在硬盘或ROM里。至于具体放在哪儿,在BIOS里已经写好相应的地址。因此,也可以肯定BIOS的功能微量除系统自检、初始化中断量外,还应该将bootloader程序复制到RAM里,书中给出地址为0x7C00(31KB)处。此后,就开始进入bootloader程序流程。
这里有几个问题,RAM是什么时候准备好的?为什么不直接加载到0x0000处,而要加载到0x7C00(31KB)处?
很容易想到RAM设备在硬件设计时其地址范围已经确定,上电后经过初始化就已经可用。为何不加载到0x0000,这个就需要了解bootloader的执行过程,如下:
bootsect.S –> setup.s –> head.s –> main.c
一般说来,bootsect.S加载setup.s代码和kernel代码(head.s,init),然后setup.s负责移动kernel代码,利用BIOS中断,读取各硬件系统参数,然后开起32位保护模式,并跳转到head.s代码处。
随着这三个疑问,逐步揭开linux从引导程序过度到内核启动,并切换到用户空间程序的神秘面纱。
重新从bootsect.S开始探索,代码中指定了根文件系统设备ROOT_DEV=0x306(一种老式的硬盘设备命名方式,即第2个硬盘的第一个分区),可以根据实际情况修改。
第二个疑问,32位保护模式很容易想到加载全局描述符表(GDT)寄存器,和中断描述符表(IDT)寄存器,还需要开启A20地址线,当然最关键的一步是设置CPU的控制寄存器CR0,让通知CPU进入 32位保护模式。这些都好还理解,唯独这个开启A20地址线让人费解。不开启就不能进入保护模式?
这里提一点,A20主要是为了兼容80286和8086、8088对于内存的访问,8086/8088只有20根地址线,能够访问1M内存,80286地址线为24根,能够访问16M内存。8086在访问1M以上的内在时,由于地址非法,此时地址对1M取模,重新从0开始。而80286在访问1M以上内存时,地址不非法,这样与8086就不兼容。为了解决这个问题,IBM提出A20地址线来控制,对于1M以上的内在地址,如果A20关闭,系统使用8086方式;如果开启,系统真正访问这些地址。不过这些内存访问都在实模式下,那么这又和保护模式有什么关系???
按理说80286在实模式下可以访问16M全部内存,但实际不能访问1M以上的内存地址。只能通过保护进入保护模式,并开启A20,才能访问所有地址空间。这样设计唯一的理由就是兼容8086,那还能有什么?关于A20还有三个问题,一是80286在保护模式下,关闭A20,能访问的内存为多少?二是如何开启或关闭A20地址线?三是如何确定真的开启了A20地址线?这些问题就不再这里解答了。
继续关注第三个疑问,如何从内核空间切换到用户空间?
先看下此时的内存布局,head.s在0x0000处,所以setup.s肯定有代码跳到此处,开始执行head.s,那么head.s有什么作用?
- 初始化中断描述符表,检查A20地址线是否已打开
- 初始化内存页目录表,为分页管理做好准备
- 跳转到init/main.c代码处开始执行
对于初始化内存页目录,必须要提一点的是内存页表在页目录之后,如一个内存页表映射4M内存空间,16M的内存需要4个页表。内存的分页功能也需要启动,即设置寄存器cr0的第31位。页目录表是系统所有进程公用的,而随后的4个页表为内核专用。那么有一个问题,用户空间的进程页表怎么安排?后边涉及到MM(内存管理)时,再说明 。
经过head.s的内存布局如下图:
可以清晰地看到内核init代码进行前的内存使用情况,head.s的作用就是在于此。那么下一个关键环节,main.c的代码是如何进入和执行的?
是head.s在程序中利用汇编返回指令ret将/init/main.c::_main()的地址弹出,从而开始了main函数的生命周期。根据c程序运行环境,在弹出前需要初始化内核的堆栈。还有可能传入一些参数给main函数。
至此,对x86处理器linux系统从开机上电内核c程序执行,进行了一个简单的梳理。内核的初始化也很有必要梳理下,不过对于Android系统,还是更多的关注ARM架构下的系统启动流程,相信会有其它惊喜的发现。
- x86 linux系统内核引导流程梳理
- Android系统内核引导流程梳理
- Linux 内核引导流程
- Linux系统引导流程
- linux系统引导流程
- linux系统引导流程
- linux系统引导流程
- linux系统引导流程
- Linux 系统引导流程
- Linux系统引导流程
- linux系统引导流程
- Linux系统引导流程
- linux系统-引导流程
- Linux系统引导流程
- x86 linux内核引导的过程
- linux的系统引导流程
- linux系统引导流程详解
- linux系统引导流程2
- 我在一个结构体里定义超过两个字符数组的成员输出结果会出错
- AngularJS》5个实例详解Directive(指令)机制
- 写论文第二天:验证交易策略
- 解决Error:ProGuard: [show_entry] java.io.IOException: Can't read [C:\Users\Alex\.AndroidStudio1.5\syst
- Android-ViewFlipper动画导航(可手动翻页)
- x86 linux系统内核引导流程梳理
- PCA人脸识别学习及C语言实现
- 51. N-Queens
- 崩溃日志管理工具的重要性
- [CodeForces 279C] Ladder (DP)
- ZOJ Problem Set - 3946 Highway Project
- poj 1251 Jungle Roads
- Java中回调函数编写
- 求指导。。。运行停止工作的原因。