《LINUX内核设计的艺术》第一章从开机家电到执行main函数之前的过程 学习笔记之一
从开机加电到实行main函数之前的过程
分为三步,目的是实现从启动盘加载操作系统程序,完成实现main函数的准备工作
- 启动BLOS,准备是模式下的中断向量表和中断服务程序
- 从启动盘加载操作系统程序到内存。加载操作系统程序就是靠第一步实现的
- 为实现32位的main函数做过度工作
1.1启动blos,准备实模式下的中断向量表和中断服务程序
由blos来加载软件操作系统的任务
1.1.1 BLOS的启动原理
0XFFFF0
由硬件来启动,CPU硬件设计逻辑设计为加电瞬间就强行将CS的值置于0xFFFF,IP的值置于0x0000,这样CS:IP就指向0xFFFF0这个地址位置。
IP:指令指针寄存器。记录将要实行的指令在代码段内的偏移地址,和CS组合就是将要实行的指令的内存地址。(实模式是绝对地址,指令指针是16位,IP)(保护模式下为线性地址,指令指针为32位,EIP)
CS:代码段寄存器密祉乡CPU当前实行代码段在内存中所在的局域
1.1.2 BLOS在内存中加载中断向量表和中断服务程序
这里选用BLOS有8KB,所占的地址段有0xFE000~0xFFFFF
BLOS在内存中开始加载中断向量表和中断服务程序就是启动(boot)操作系统至关重要的工作
0x00000~0x003FF 1KB中断向量表
0x00400~0x004FF BLOS数据数据表
0x0E2CE~0x0FFFF 中断服务程序
中断向量表一共有256个中断向量,每个中断向量占4个字节,其中两个CS还有两个是IP,每个向量都是指向各自的中断服务程序
1.2 加载操作系统内核程序并为保护模式做准备
分三批加载操作系统的内核代码
第一批由int 0x19h 把第一扇区bootsect的内容加载到内存中
第二三批在bootsect的指挥下,分别把其后的四个扇区和随后的240个扇区内容加载至内核
1.2.1加载第一部分代码,引导程序(bootsect)
计算机硬件体系结构的设计与BLOS联手操作,会让CPU接受int 19h中断,CPU接收到这个中断后,立即就在中断向量表中找到这个向量。然后int 19h把CPU指向0x0E6F2(就是服务程序的入口)。这个程序就是吧软盘的第一个扇区中的程序(512B)加载到内存中指定的任何一个位置。
差不多就是将软驱0号磁头对应盘面的0磁道1扇区的内容拷贝到内存0x07C00处。
这个扇区的内容就是LINUX0.11操作系统的引导程序。
第一扇区的载入标识表示操作系统的代码终于要发挥作用了
1.2.2加载第二部分代码-setup
1.Bootsect对于内存的规划
在实模式下,寻址的最大范围是1MB,为了规划内存,bootsect首先设计了如下代码来对后续操作所涉及的内存位置进行设置。
SETUPLE 将要加载的setup程序的扇区数
SETUPSEG 被加载到的位置
BOOTSEG 启动扇区被BLOS加载的位置
INITSEG 将要移动到的新位置
SYSSEG Kernel内核将被加载的位置
SYSEND 内核的末尾位置
ROOT_DEV 根文件系统设备号
设置这些位置就是为了保证将载入内存的代码和意见载入内存的代码数据各在其位,互不覆盖
2.复制bootset
接下来,bootset启动程序将它自身(全部512B内容)从内存0xc7c00(BOOTSEG)复制到0x90000(INITSEG)处,注意此时,CPU的段寄存器(CS)指向BOOTSEG
start:
mov ax,#BOOTSEG
mov ds,ax
mov ax,#INITSEG
mov es,ax
mov cx,#256 //控制循环控制量
sub si,si
sub di,di
rep
movw
//bootseg复制到新位置,后实行以下这行粗代码
jmpi go,INITSEG
go: mov ax,cs
//实行完这个跳跃,CS就变为INITSEG,IP也从INITSEG到了go:mov ax,cs对应的指令偏移,换句话来说就是CS:IP到了这一行开始往下实行。
由于bootset复制到了新地方,要在新的地方实行。所以要对DS,ES,SS,SP进行调整。
mov ds,ax
mov es,ax
! put stack at 0x9ff00.
mov ss,ax
mov sp,#0xFF00 ! arbitrary value >>512
! load the setup-sectors directly after the bootblock.
! Note that ‘es‘ is already set up.
上述代码的作用就是通过ax,用CS的值0x9000来把
数据寄存器(DS)
附加段寄存器(ES)
栈基址寄存器(SS)
设置成与代码段寄存器CS相同的位置。,并将栈顶指针sp指向偏移地址0xFF00处
压栈的方向就是高地址到低地址方向
Bootsect的第一步操作:规划内存并把自身从0x07C00的位置复制到0x90000的位置的动作已经完成了。
3.将Setup程序加载到内存中
要借助int 0x 13h中断向量,也是就是指向“磁盘服务程序”
和int 0x 19h的区别在于,前者是自身启动代码bootsect执行的。后者是BIOS实行的
后者的中断服务程序只负责把软盘的第一扇区的代码就加载到0x07c00位置。前者是将指定扇区的代码加载到内存的指定位置。
load_setup:
mov dx,#0x0000 ! drive 0, head 0
mov cx,#0x0002 ! sector 2, track 0
mov bx,#0x0200 ! address = 512, in INITSEG
mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors
int 0x13 ! read it
jnc ok_load_setup ! ok - continue
mov dx,#0x0000
mov ax,#0x0000 ! reset the diskette
int 0x13
j load_setup
ok_load_setup:
从代码开始处的4个mov指令可以看得出系统给BIOS中断程序传参数是靠几个通用寄存器实现的。
参数传递完毕后,实行int 0x13h这个指令,产生0x13中断,通过中断向量表找到这个中断向量程序,将软盘的第二扇区开始的4个扇区,就是说setup.S对应的程序加载至内存的setup(0x90200)处。
可以看得出,bootsect和setup是连在一起的。
读扇区:
ah=0x02 - 读磁盘扇区到内存;a1=需要读出的扇区数量
ch=磁道(柱面)号的低8位; c1=开始扇区(位0-5),磁道号高2 位(位6-7);
dh=磁头号; d1=驱动器号(如果是硬盘则位7要 置位),0为当前A驱动器
es:bx->指向数据缓冲区;如果出错则CF标志置位。
1.2.3加载第三部分代码-system模块
下面就是system代码,仍然是bootsect用BIOS提供的int 0x 13h中断将240个扇区的system模块加载进内存。加载工作主要是由bootsect来调用read it来完成。这个子程序将软盘地6扇区开始的月240个扇区的system模块加载至内存的SYSSEG(0x10000)出往后的120KB空间中。
整个操作系统都载入了内存,bootsect的主体都完成了,还有一点小事,就是要再次确认一下根设备号。在书里例子就是得知软盘就是跟设备,所以就把跟设备号保存在root——dev中,这个根设备号作为机器系统数据之一。
所以要检查要使用哪个根文件系统设备(简称根设备)。如果已经指定了设备(!=0)就直接使用给定的设备。否则就需要根据BIOS 报告的每磁道扇区数来确定到底使用/dev/PS0 (2,28) 还是 /dev/at0 (2,8)。
故在此bootsect的工作完毕。
下面就是执行
jmpi 0,SETUPSEG ! 跳转到0x9020:0000(setup.s 程序的开始处)。
Setup程序开始,他做的第一件事情就是利用BIOS来提供的中断服务程序从设备上提取出内核运行所需的技巧系统数据,其中包括,光标位置和现实页面等数据,并分别从中断向量,0x41,o46向量值所指的内存弟子出获取硬盘参数表1,和硬盘参数表2,把他们存放在0x9000:0x0080,0x9000:0x0090处。这些数据将被加载到内存的0x90000~0x901FC位置。标出了其内容及准确的位置,这些数据将在以后main函数执行是发挥重要作用。
这段代码提取出的机器系统数据将覆盖bootsect程序所在的部分区域。
下面就是有重要意义上的从实模式到保护模式的转变,使得LINUX0.11变为现代操作系统