【Linux0.11代码分析】03 之 setup.s 启动流程
【Linux0.11代码分析】03 之 setup.s 启动流程
- 一、boot\setup.s
系列文章如下:
系列文章汇总:《【Linux0.11代码分析】之 系列文章链接汇总(全)》
.
1.《【Linux0.11代码分析】01 之 代码目录分析》
2.《【Linux0.11代码分析】02 之 bootsect.s 启动流程》
3.《【Linux0.11代码分析】03 之 setup.s 启动流程》
4.《【Linux0.11代码分析】04 之 head.s 启动流程》
在前文中,我们知道,bootsect.s
将setup.s
和 system
加载到内存后,
通过 jmpi 0,SETUPSEG
指令跳转到 0x90200
开始执行 setup.s
程序,把CPU
交到 setup.s
手中。
setup.s
主要功能如下:
- 从
ROM BIOS
中获取内存、磁盘等系统数据,并将这些数据保存到0x90000-0x901FF
,它会覆盖掉bootsect.s
程序所在的地方。 - 将
system
模块从0x10000 - 0x8FFFF
整块向下移动到内存绝对地址0x00000
处 - 加载中断描述符表寄存器(
idtr
)和全局描述符表寄存器(gdtr
) - 开启
A20
地址线,重新设置两个中断控制芯片8259A
,将硬件中断号重新设置为0x20 - 0x2F
- 设置
CPU
的控制寄存器CRO
(机器状态字),从而进入32
位保护模式运行 - 跳转到位于
system
模块最前面部分的head.s
程序运行
所取得的参数如下:
内存地址 | 长度(字节) | 名称 | 描述 |
---|---|---|---|
0x90000 | 2 | 光标位置 | 列号(0x00-最左端),行号(0x00-最顶端) |
0x90002 | 2 | 扩展内存数 | 系统从 1M 开始的扩展内存数值(KB) |
0x90004 | 2 | 显示页面 | 当前显示页面 |
0x90006 | 1 | 显示模式 | |
0x90007 | 1 | 字符列数 | |
0x90008 | 2 | ||
0x9000A | 1 | 显示内存 | 显示内存(0x00-64k,0x01-128k,0x02-192k,0x03=256k) |
0x9000B | 1 | 显示状态 | 0x00-彩色,I/O=0x3dX;0x11-单色,I/O=0x3bX |
0x9000C | 2 | 特性参数 | 显示卡特性参数 |
… | |||
0x90080 | 16 | 硬盘参数表 | 第 1 个硬盘的参数表 |
0x90090 | 16 | 硬盘参数表 | 第 2 个硬盘的参数表(如果没有,则清零) |
0x901FC | 2 | 根设备号 | 根文件系统所在的设备号(bootsec.s 中设置) |
一、boot\setup.s
详细工作如下:
- 配置数据段寄存器
ds = ax = 0x9000
- 读取当前光标位置,
BIOS
中断0x10
, 功能号ah=0x03
, 然后将当前光标位置保存在0x90000
第一个字节处 - 获取扩展内存的大小值,
BIOS
中断0x15
, 功能号是ah=0x88
- 获取显示当前的显示模式,调用
BIOS
中断0x10
,功能号ah=0x0f
- 检查显示方式(
EGA/VGA
)并取参数, 调用BIOS
中断0x10
,附加功能选择-取方式信息, 功能号:ah=0x12,bl=0x10
- 获取第一个硬盘的硬盘参数表,第
1
个硬盘参数表的首地址是中断向量0x41
的向量值,保存在0x90080
处 - 获取第二个硬盘的硬盘参数表,第
2
个硬盘参数表的首地址是中断向量0x46
的向量值,保存在0x90090
处 - 检查系统是否存在第
2
个硬盘,如不存在,则将0x90090
地址的第二个表清零 - 禁止中断
- 将
system
从0x10000-0x8FFFF
移动到0x00000-0x7FFFF (512k)
- 加载中断描述符表(idt)寄存器和 全局描述符表(gdt)寄存器
- 使能 A20 地址线
- 对中断进行重新编程,将它们保存在 intel保留的硬件中断后面,位于0x20-0x2F
- 配置进入32位保护模式运行,跳转到0x00000地址,开始运行system模块的head.s
# linux-0.11\boot\setup.s
INITSEG = 0x9000 // 原 bootsect.s 所处的段 0x90000 (576kb) ! we move boot here - out of the way
SYSSEG = 0x1000 // 当前system 模块所处的段 0x10000 (64kb) ! system loaded at 0x10000 (65536).
SETUPSEG = 0x9020 // 当前 setup.s 程序所处的段 0x90200 (576.5kb) ! this is the current segment
.globl begtext, begdata, begbss, endtext, enddata, endbss // 定义全局标志符
.text // 文本段
begtext:
.data // 数据段
begdata:
.bss // 堆栈段
begbss:
.text // 文本段
entry start // 程序入口函数 start
start:
// 1. 配置数据段寄存器 ds = ax = 0x9000
mov ax,#INITSEG ! this is done in bootsect already, but...
mov ds,ax
// 2. 读取当前光标位置, BIOS中断0x10, 功能号ah=0x03, 然后将当前光标位置保存在 0x90000 第一个字节处
// 返回:ch=扫描开始线,cl=扫描结束线, dh=行号(0x00 是顶端),dl=列号(0x00 是左边)。
mov ah,#0x03 ! read cursor pos
xor bh,bh
int 0x10 ! save it in known place, con_init fetches
mov [0],dx // 0x90000: 列号(0x00 是左边), 0x90001:行号(0x00 是顶端)
// 3. 获取扩展内存的大小值, BIOS中断0x15, 功能号是ah=0x88
// 返回ax = 从 0x100000(1M)处开始的扩展内存大小(KB), 若出错则 CF 置位,ax = 出错码
mov ah,#0x88
int 0x15
mov [2],ax // 0x90002(1字)存放从 0x100000(1M)处开始的扩展内存大小(KB)
// 4. 获取显示当前的显示模式,调用BIOS中断0x10,功能号ah=0x0f
// 返回:ah=字符列数,al=显示模式,bh=当前显示页
mov ah,#0x0f
int 0x10
mov [4],bx // 0x90004(1字)存放当前页 ! bh = display page
mov [6],ax // 0x90006显示模式, 0x90007 字符列数 ! al = video mode, ah = window width
// 5. 检查显示方式(EGA/VGA)并取参数, 调用BIOS中断0x10,附加功能选择-取方式信息, 功能号:ah=0x12,bl=0x10
// 返回:bh = 显示状态 (0x00 - 彩色模式,I/O 端口=0x3dX) (0x01 - 单色模式,I/O 端口=0x3bX)
// bl = 安装的显示内存 (0x00 - 64k, 0x01 - 128k, 0x02 - 192k, 0x03 = 256k)
// cx = 显示卡特性参数
mov ah,#0x12
mov bl,#0x10
int 0x10
mov [8],ax // 0x90008
mov [10],bx // 0x9000A 安装的显示内存, 0x9000B 显示状态
mov [12],cx // 0x9000C 显示卡特性参数
// 6. 获取第一个硬盘的硬盘参数表,第1个硬盘参数表的首地址是中断向量0x41的向量值,保存在0x90080处
mov ax,#0x0000
mov ds,ax // 配置 数据段寄存器 ds = ax = 0x0000
lds si,[4*0x41] // 取中断向量 0x41的值 ,即hd0 参数表的地址 ds:si
mov ax,#INITSEG // 配置 段基址为 ex = ax = 0x9000
mov es,ax
mov di,#0x0080 // 传输的目的地址: 0x9000:0x0080 es:di
mov cx,#0x10 // 共转输 0x10 字节
rep
movsb
// 7. 获取第二个硬盘的硬盘参数表,第2个硬盘参数表的首地址是中断向量0x46的向量值,保存在0x90090处
mov ax,#0x0000
mov ds,ax // 配置 数据段寄存器 ds = ax = 0x0000
lds si,[4*0x46] // 取中断向量 0x416的值 ,即hd1 参数表的地址 ds:si
mov ax,#INITSEG // 配置 段基址为 ex = ax = 0x9000
mov es,ax
mov di,#0x0090 // 传输的目的地址: 0x9000:0x0090 es:di
mov cx,#0x10 // 共转输 0x10 字节
rep
movsb
// 8. 检查系统是否存在第2个硬盘,如不存在,则将 0x90090地址的第二个表清零
// 利用 BIOS 中断调用 0x13 的取盘类型功能, 功能号 ah = 0x15
// 输入:dl = 驱动器号(0x8X 是硬盘:0x80 指第 1 个硬盘,0x81 第 2 个硬盘)
// 输出:ah=类型码;00没有这个盘,CF置位;01是软驱,没有change-line支持;02是软驱(或其它可移动设备),有change-line支持;03是硬盘
mov ax,#0x01500
mov dl,#0x81
int 0x13
jc no_disk1
cmp ah,#3 // 判断 ah==3 ,判断disk1是否存在
je is_disk1
no_disk1:
mov ax,#INITSEG // 第2个硬盘不存在,则对第2个硬盘表清零。
mov es,ax
mov di,#0x0090
mov cx,#0x10
mov ax,#0x00
rep
stosb
is_disk1:
! now we want to move to protected mode ...
// 9. 禁止中断
cli ! no interrupts allowed !
// 10. 将system从0x10000-0x8ffff 移动到 0x00000-0x7ffff(512k)
mov ax,#0x0000
cld // 将标志寄存器Flag的方向标志位DF清零 ! 'direction'=0, movs moves forward
do_move:
mov es,ax // 目的地址 es:di = 0x0000:0x0
add ax,#0x1000 // ax = ax + 0x1000
cmp ax,#0x9000 // 判断是否等于 0x9000, 如果相等则说明移动完毕
jz end_move
mov ds,ax // 源地址 ds:si = 0x1000:0x0
sub di,di
sub si,si
mov cx,#0x8000 // 移动0x8000字(64byte)
rep
movsw
jmp do_move
// 11. 加载中断描述符表(idt)寄存器和 全局描述符表(gdt)寄存器
end_move:
// 将数据段寄存器 ds = ax = 0x9020
mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)
mov ds,ax
// 加载中断描述符表(idt)寄存器,
// 它的操作数是6个字节,0-1 字节是描述符表的长度值(字节);2-5 字节是描述符表的 32 位线性基地址(首地址)
// 中断描述符表中的每一个表项(8 字节)指发生中断时需要调用的代码的信息,与中断向量有些相似,但要包含更多的信息
lidt idt_48 ! load idt with 0,0
---------------->
+ idt_48:
+ .word 0 ! idt limit=0
+ .word 0,0 ! idt base=0L
<----------------
// 加载全局描述符表(gdt)寄存器
// 全局描述符表中的每个描述符项(8字节)描述了保护模式下数据和代码段(块)的信息
// 包括段的最大长度限制(16位)、段的线性基址(32位)、段的特权级、段是否在内存、读写许可以及其它一些保护模式运行的标志。
lgdt gdt_48 ! load gdt with whatever appropriate
---------------->
+ gdt:
+ .word 0,0,0,0 ! dummy
+
+ .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb)
+ .word 0x0000 ! base address=0
+ .word 0x9A00 ! code read/exec
+ .word 0x00C0 ! granularity=4096, 386
+
+ .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb)
+ .word 0x0000 ! base address=0
+ .word 0x9200 ! data read/write
+ .word 0x00C0 ! granularity=4096, 386
+
+ gdt_48:
+ .word 0x800 ! gdt limit=2048, 256 GDT entries
+ .word 512+gdt,0x9 ! gdt base = 0X9xxxx
<----------------
! that was painless, now we enable A20
// 12. 使能 A20 地址线
call empty_8042 // 等待输入缓冲器为空
--------------->
+ ! This routine checks that the keyboard command queue is empty No timeout is used -
+ ! if this hangs there is something wrong with the machine, and we probably couldn't proceed anyway.
+ empty_8042:
+ .word 0x00eb,0x00eb
+ in al,#0x64 ! 8042 status port
+ test al,#2 ! is input buffer full?
+ jnz empty_8042 ! yes - loop
+ ret
<---------------
// 0xD1 命令码-表示要写数据到8042的P2端口, P2端口的位1用于A20线的选通,数据要写到0x60口
mov al,#0xD1 ! command write
out #0x64,al
call empty_8042 // 等待输入缓冲器为空
mov al,#0xDF // 选通 A20 地址线的参数 ! A20 on
out #0x60,al
call empty_8042 // 等待输入缓冲器为空
// 13. 对中断进行重新编程,将它们保存在 intel保留的硬件中断后面,位于0x20-0x2F
// 0x11 表示初始化命令开始, 是 ICW1 命令字,表示边沿触发、多片 8259级连、最后要发送 ICW4 命令字。
mov al,#0x11 ! initialization sequence
// 发送到 8259A 主芯片
out #0x20,al ! send it to 8259A-1
.word 0x00eb,0x00eb // 延时 ! jmp $+2, jmp $+2
// 再发送到 8259A 从芯片
out #0xA0,al ! and to 8259A-2
.word 0x00eb,0x00eb // 延时
// 送主芯片 ICW2 命令字,起始中断号,要送奇地址
mov al,#0x20 ! start of hardware int's (0x20)
out #0x21,al
.word 0x00eb,0x00eb // 延时
// 送从芯片 ICW2 命令字,起始中断号
mov al,#0x28 ! start of hardware int's 2 (0x28)
out #0xA1,al
.word 0x00eb,0x00eb // 延时
// 送主芯片 ICW3 命令字,主芯片的 IR2 连从芯片 INT
mov al,#0x04 ! 8259-1 is master
out #0x21,al
.word 0x00eb,0x00eb // 延时
// 送从芯片 ICW3 命令字,表示从芯片的 INT 连到主芯片的 IR2 引脚上。
mov al,#0x02 ! 8259-2 is slave
out #0xA1,al
.word 0x00eb,0x00eb // 延时
// 送两个芯片的 ICW4 命令字。8086 模式;普通 EOI方式,需发送指令来复位。初始化结束,芯片就绪
mov al,#0x01 ! 8086 mode for both
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
.word 0x00eb,0x00eb
// 当前屏蔽所有中断请求
mov al,#0xFF ! mask off all interrupts for now
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
// 14. 配置进入32位保护模式运行,跳转到0x00000地址,开始运行system模块的head.s
mov ax,#0x0001 ! protected mode (PE) bit
// 加载机器状态字(lmsw - Load Machine Status Word), 也称控制寄存器 CR0,其bit0 置1将导致CPU工作在保护模式
lmsw ax ! This is it!
// 配置为特权级为系统级、使用全局描述符,索引项为0, 偏移为0, 也就是跳转到 0x00000的地址开始运行
jmpi 0,8 ! jmp offset 0 of segment 8 (cs)
// 段选择符长度为 16 位(2 字节);
// 位 0-1 表示请求的特权级 0-3, linux 操作系统只用到两级:0 级(系统级)和 3 级(用户级);
// 位 2 用于选择全局描述符表(0)还是局部描述符表(1)
// 位 3-15 是描述符表项的索引,指出选择第几项描述符
// 所以段选择符8(0b0000,0000,0000,1000)表示请求特权级 0、使用全局描述符表中的第 1 项