linux0.11启动过程

环境:linux-0.11系统,x86-CPU构架

从开机加电到执行main函数之前的过程

分三步完成:

1.启动BIOS,准备实模式下的中断向量表和中断服务程序

2.从启动盘加载操作系统程序到内存,加载操作系统程序的工作是利用第一步中准备的中断服务程序实现的

3.为执行32位的main函数做过渡工作。

注:什么是实模式/保护模式?

 

1.1  0xFFFF0

CPU硬件逻辑设计为加电瞬间强行将CS的值置为0xFFFF,IP的值置为0x0000,CS:IP为0xFFFF0,0xFFFF0指向BIOS地址范围。

BIOS的入口地址就是0xFFFF0.

BIOS程序在内存最开始的位置(0x00000)用1KB的内存空间(0x00000~0x003FF)构建中断向量表,并在紧挨着它的位置用256字节的内存空间构建BIOS数据区(0x00400~0x004FF),在大约56KB以后的位置(0x0E05B)加载了8KB左右的与中断向量表相应的若干中断服务程序。

中断向量表中有256个中断向量,每个中断向量占4个字节,其中两个字节是CS的值,两个字节是IP的值,每个中断向量都指向一个具体的中断服务程序。

 

1.2  加载操作系统内核程序并为保护模式做准备

分三批逐次加载内核代码:

第一批由BIOS中断int 0x19h把第一扇区bootsect的内容加载到内存

第二批和第三批在bootsect的指挥下,分别把其后的四个扇区和随后的240个扇区的内容加载到内存

 

1.2.1  加载第一部分代码----引导程序(bootsect)

计算机硬件体系结构设计与BIOS联手操作,会让CPU接收到一个int 19h中断,CPU接收到这个中断后,会立即在中断向量表中找到此中断。中断向量把CPU指向0x0E6F2,即中断服务程序的入口地址。作用就是把软盘的第一个扇区中的程序(bootsect 512B)加载到内存的0x07C00处。

 

1.2.2  加载第二部分代码-----setup

1.  Bootsect对内存的规划

要加载的setup程序的扇区数(SETUPLEN)

被加载到的位置(SETUPSEG)

启动扇区被BIOS加载的位置(BOOTSEG)

将要移动到的新位置(INITSEG)

内核被加载的位置(SYSSEG)

内核的末尾位置(SYSEND)

根文件系统设备号(ROOT_DEV)

2.复制bootsect

Bootsect将自身从内存0x07C00(BOOTSEG)处复制到内存(0x90000INITSEG)处

为什么要复制?

由于“两头约定”和“定位识别”的作用,bootsect在开始时被迫加载到0x07C00

此时CPU的段寄存器(CS)指向0x07C0(BOOTSEG)

Jmpi go,INITSEG

Go:mov  ax,cs

执行这个跳转后,CS值变为0x9000,IP的值为从0x9000(INITSEG)到go:mov ax,cs这一行对应指令的偏移。

用CS的值0x9000来把数据段寄存器(DS)、附加段寄存器(ES)、栈基址寄存器(SS)设置成与代码段寄存器CS相同的位置,并将栈顶指针SP指向偏移地址为0xFF00处。

SS和SP联合使用构成了栈数据在内存中的位置值,为栈操作打下基础。

 3.  将setup程序加载到内存中

借助BIOS提供的int 0x13h中断服务程序,把指定扇区的代码加载到内存的指定位置,需将指定的扇区和加载的内存。setup.s对应的程序加载至内存的SETUPSEG(0x90200)处

 

1.2.3加载第三部分代码-----system模块

借助BIOS提供的int 0x13h中断服务程序,加载至内存的SYSSEG(0x10000)处往后的120KB空间中。

 

Jmpi 0,SETUPSEG跳转到0x90200处,即setup程序加载的位置。Setup开始执行。

它做的第一件事就是利用BIOS提供的中断服务程序从设备上提取内核运行所需的机器系统数据,包括光标位置和显示页面等数据,并分别从中断向量0x41和0x46向量值所指的内存地址处获取硬盘参数表1和硬盘参数表2,把它们存放在0x9000:0x0080和0x9000:0x0090处。

这些机器系统数据被加载到内存的0x90000~0x901FC位置。

 

1.3   开始向32位模式转变,为mian函数的调用做准备

1.3.1  关中断并将system移动到内存地址起始位置0x00000

关中断:将CPU的标志寄存器(EFLAGS)中的中断允许标志(IF)置0

将位于0x10000的内核程序拷贝至内存起始地址0x00000处,废除BIOS中断向量表

 

1.3.2  ??设置中断描述符表IDTR和全局描述符表GDTR

1.3.3  打开A20,实现32位寻址

 线性寻址达到4GB

1.3.4  为在保护模式下执行head.s做准备

为了建立保护模式下的中断机制,setup程序将对可编程中断控制器8259A进行重新编程。

Setup程序将CR0寄存器的第0位(PE)置1,即设定处理器的工作方式为保护模式。

 

1.3.5 head.s开始执行

先将head.s汇编成目标代码,将用C语言编写的内核程序编译成目标代码,然后链接成system模块。System模块里面,就有内核程序,又有head程序,两者是紧挨着。Head程序在内存中占有25KB+184B的空间。System模块加载到内存后,setup将system模块复制到0x00000位置,由于head程序在system的前面,head程序就在0x00000这个位置。

         head程序除了做一些调用main的准备工作之外,用程序自身的代码在程序自身所在的内存空间创建了内核分页机制,在0x000000的位置创建了页目录表、页表、缓冲区、GDT、IDT,并将head程序已经执行过的代码所占的内存空间覆盖。

head.s程序被编译后,会被连接成system模块的最前面位置,它被setup.s加载到内存绝对地址0处开始的地方,并执行。此时Linux内核已经完全在保护模式下运行。head.s的主要功能包括:

1. 设置内核堆栈;
2. 设置中断描述符表idt;
3. 设置全局描述符表gdt;
4. 设置页目录表和页表;
5. 将/init/main.c程序的入口地址预先压入堆栈中,并在随后利用返回指令弹出该地址,去执行main()程序。

 

插入:

1.  内存管理寄存器

4个内存管理寄存器(GDTR、LDTR、IDTR和TR),用于指定内存分段管理所用系统表的基地址。

 


GDTR、LDTR、IDTR、TR都是段基址寄存器,这些段含有分段机制的重要信息表。GDTR、IDTR和LDTR用于寻址存放描述表的段。TR用于寻址一个特殊的任务状态段(TSS)。TSS包含着当前执行任务的重要信息。


2.      段的定义

分段机制就是把虚拟地址空间中的虚拟内存组织成一些长度可变的称为段的内存单元。

80386虚拟地址空间中的虚拟地址(逻辑地址)由一个段部分和一个偏移部分构成。段是虚拟地址到线性地址转换机制的基础。

         每个段由以下参数定义:

(1)      段基地址(Base Address):指定段在线性地址空间中的开始地址。基地址是线性地址,对应于段中偏移0处。

(2)      段限长(Limit):是虚拟地址空间中段内最大可用偏移位置,定义了段的长度。

(3)      段属性(Attribute):指定段的特性。例如该段是否可读、可写或可作为一个程序执行,段的特权级等。


注:IP/EIP:指令指针寄存器,存在于CPU中,记录将要执行的指令在代码段内的偏移地址,与CS组合即将要执行的指令的内存地址。实模式为绝对地址,指令指针为16位,即:IP;保护模式为线性地址,指令指针为32位,即EIP。

         CS:代码段寄存器,存在于CPU中,指向CPU当前执行代码在内存中所在的区域

 

         GDT:它是系统中唯一存放段寄存器内容(段描述符)的数组,配合程序进行保护模式下的段寻址。可理解为所有进程的总目录表,其中存放着每一个任务(task)局部描述符表地址和任务状态段地址,用于完成进程中各段的寻址、现场保护与现场恢复。

        GDTR:可以存放在内存的任何位置,当程序提供段寄存器引用一个段描述符时,需要取得GDT的入口,GDTR所标识的即为此入口。在操作系统对GDT的初始化完成后,可以用LGDT指令将GDT基地址加载至GDTR

        IDT:保存保护模式下所有中断服务程序的入口地址

        IDTR:IDT基地址寄存器,保存IDT的起始地址

疑问??:

BIOS在ROM中怎么与RAM联系起来了?

从setup怎么过渡到head?


 3. 逻辑地址到线性地址的转换


一个逻辑地址由两部分组成,段标识符:段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。前13位为索引号,后3位包括硬件细节:


索引号(段描述符的索引),很多段描述符组成了段描述符表,


Base字段,描述了一个段地开始位置的线性地址。

全局的段描述符,放在“全局段描述符表(GDT)中

局部的,就放在“局部描述符表(LDT)“中。由段选择符中的T1字段表示。=0,表示用GDT,=1,表示用LDT

 

逻辑地址[段选择符:段内偏移地址]

1.      看段选择符的T1是0还是1,知道当前要转换的是GDT中的段,还是LDT中的段,找到段描述符表

2.      根据段选择符中的钱13位,索引到对应的段描述符,就有了Base(16-31位)基地址

3.      把Base+offset,就是要转换的线性地址。

 

在Linux下,逻辑地址与线性地址总是一致


 4. 线性地址到物理地址的转换


CPU的页式内存管理单元,负责把一个线性地址,翻译为一个物理地址。

线性地址被分为以固定长度为单元的组,称为页(page)。

例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,整个线性地址就被划分为一个 tatol_page[2^20]的大数组,共有2的20个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地 址。

另一类“页”,我们称之为物理页,或者是页框、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。

这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元。文字描述太累,看图直观一些:


如上图,
1、分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。
2、每一个活动的进程,因为都有其独立的对应的虚似内存(页目录也是唯一的),那么它也对应了一个独立的页目录地址。——运行一个进程,需要将它的页目录地址放到cr3寄存器中,将它保存下来。
3、每一个32位的线性地址被划分为三部份,面目录索引(10位):页表索引(10位):偏移(12位)


依据以下步骤进行转换:


1、从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
2、根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。
3、根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;

4、将页的起始地址与线性地址中最后12位相加,得到最终我们想要的地址;


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值