您的位置:首页 > 运维架构 > Linux

Linux上电初始化--BOSI启动和boot环境设置

2015-01-15 18:08 281 查看
BIOS:

上电启动时,最开始由硬件控制进入BIOS,BIOS代码一般存放在0xfe000~0xfffff最后几kb中;启动中cs:eip == 0xffff:0x00000 === 0xffff0 这是最开始启动的(BIOS执行的第一条指令)

下面是BIOS程序执行的内容:

1、在0x00000处开始的1kb内存空间(0x00000~0x003fff)安装中断向量表(256个中断,cs和ip各占2字节==>1kb)

2、在紧挨着它的位置用256字节创建BIOS数据区(0x400~0x4ff)

3、在0xe2ce处加载中断向量表相应的服务程序



当BIOS完成自检后,会调用中断0x19来加载第一扇区内容(启动扇区,引导扇区,bootsect)把该内容加载到0x7c00处

××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

bootsect:

bootsect.s代码是磁盘引导程序,驻留在磁盘的第一号扇区中:0号磁道(柱面)、0号磁头、第一个扇区。第一号扇区是指虚拟的扇区号,比如:0号磁道、1号磁头、第一个扇区的扇区号为19(1.44Mb软驱磁道总扇区数为18,1.2Mb软驱磁道总扇区数为12);系统会检查0x7c00以后的256个字节是否为有效的引导扇区(最后两字节是否为 0xaa55)

下面是bootsect程序执行的内容:

1、规划内存

setuplen = 4 要加载的setup扇区数

bootseg = 0x07c0 (这是段地址,物理地址0x7c00),启动扇区被BIOS加载到的位置

initseg = 0x9000 将要把bootsect移动到的新位置,0x90000+0x1ff = 0x90200:后面接跟着setup

setupseg = 0x9020 setup被加载到的位置(bootsect和setup是连在一起的) 0x90200 + 512*4(十六进制为:0x800)=0x90a00

sysseg = 0x1000 内核被加载的位置,120个扇区 ==> 0x2e000

endseg = sysseg + syssize 内核的末尾位置

2、bootsect启动程序将自己512字节从0x7c00处复制到0x90000处,然后执行:

jmpi go, initseg

go:

mov ax, cs

mov ds, ax

跳转到新位置0x90000处继续执行下面的代码

3、用BIOS的系统中断调用(int 0x13)把setup(第二个扇区到第五个扇区)加载到setupseg(0x90200)

4、从第六个扇区开始把240多个扇区内核模块加载到sysseg(0x1000)处往后的120kb空间中(以磁道为单位加载),==> 0x2e000

5、确认根设备号,指的是设备

6、跳转到setup开始执行:jmpi 0,setupseg

注:堆栈ss=0x9000,sp=0xff00

××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

setup:

主要是提前内核运行所需要的机器系数数据

1、提取光标和页面等数据,并调用中断向量0x41和0x46中获取硬盘参数表1和表2,分别放在0x9000:0x0080和0x9000:0x0090

2、把BIOS中提前到的数据存放在 0x90000~0x901fc (这段地址是bootsect位置,0x90000~0x901ff)

3、关中断cli,若在启动时未关中断,在销毁BIOS中断(实模式中断)与建立新中断描述符表IDT(保护模式)发生中断,系统不知道调用哪个中断系统;

4、把内核程序从0x10000处移动到0x0000处,覆盖了BIOS开始建立的中断向量表和数据区,内核从0x0000处开始

5、设置中断描述符表和全局描述符表;

中断描述符表:

32位中断机制是用中断描述符表IDT(表的位置不固定),由IDTR来跟踪访问;16位实模式中断机制是用中断向量表,位置固定在0x0000;此时IDT是一张空表,还没有开始填写中断服务程序(要使用某个中断服务程序时,把相应的位置添加到IDT表中);GDT全局描述符表,是进入保护模式的关键区别,存放各个段地址。

6、打开A20(历史遗留问题,0xfffff+1=0x0000地址回滚,24位地址时为了兼容大多数pc机上应用,也可以设置回顾)

7、对8259A中断控制芯片进行编程,在保护模式下,int 0x00~int 0x1f是被Intel保留作为内部(不可屏蔽)中断使用。

实模式下: 保护模式:

IRQ 0 ---> 0x00 0--->0x20

IRQ 1 ---> 0x01 0--->0x21

..... .....

IRQ 15---> 0x0f 15--->0x2f

8、设置CR0中的第0位PE为1,让cpu进入保护模式下运行

9、jmpi 0, 8 (1000)跳转到第一个段描述符的地方执行。进入保护模式后用跳转语句清空流水线(流水线里面的语句还是16位实模式下的)

10、跳转到的位置其实是0x0000,是内核代码开始的地方,也是head.s开始的地方

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

head:

head.s程序加载方式和前面两个不一样,它是由C语言写的编译器编译成目标代码,然后和内核一起链接成system模块。head在内核前面,也即是head在0x0000开始处,占由的内存是25KB+184B;大概要完成的工作是实现分页机制,GDT,IDT。最重要的一点是,head在0x0000处创建GDT、IDT等,这就意味着自己杀害自己来为内核提供运行条件;

1、设置系统堆栈;(为什么要这么设置不是很清楚)

2、设置临时的中断描述符表idt(其中有256项),把所有的中断描述符中服务程序都设置指向ignore_int(里面什么也没做就打印些信息),属性值设置为0x8E00,同时加载到lidt中断描述符寄存器中。其实这些中断描述符都是假的,真正有效的中断门需要到实际使用中定义(主要在asm.s和Traps.c)

3、设置临时全局描述符表gdt(三个描述符:代码描述符、数据描述符、系统段描述符),其实呢,实现很简单:开始加载全局描述符表段寄存器,而加载的是 gdt_descr,其中真正定义描述符的是 _gdt(在head文件最后);

4、检测A20是否开启,采用在0x00处写入某个数值,在1M地址上获取数据看看是否相等,若相等,则一直循环比较(也就是死机);

5、检测数学协处理器是否存在(对数学协处理器不是很熟悉),方法是修改CR0,然后执行数学协处理器命令,看是否出错;

6、为执行main函数设置环境,最后把main函数的地址压栈,然后装作调用返回(其实根本没调用,而是依靠堆栈性质)用ret(call和ret并不是非要对应出现的,这里就是只使用ret,让cpu误以为是调用返回到main中)跳转到main函数执行。

7、设置有关分页机制,在0地址开始处顺序定义5个物理页分不做页目录表(所有进程共有),4个页表(内核专用,其他新进程要使用可以在主内存中定义新的页表来处理映射)(4×1024×4kb=16MB,寻址整个16MB的主内存),把页表和主内存物理页一一映射起来。打开CR0分页机制,把页目录表地址赋值给CR3。

8、最后一步:ret,“返回到”main函数(使用堆栈性质跳转到main),开始Linux操作系统一系列的初始化工作了。

转载请注明作者和原文出处,原文地址:/article/1530634.html

若有不正确之处,望大家指正,共同学习!谢谢!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: