1)bootsect.s源码总结
bootsect.s编译后的二进制代码存放在磁盘的第一个扇区(引导区),开机启动后会被BIOS加载到内存的0x07c0处,
并跳转到0x07c0处执行。下面开始解释程序源码
<1>bootsect.s将自身从0x07c0处移动到0x9000处
entry _start
_start:
mov ax,#BOOTSEG !BOOTSEG=0x07c0,也即bootsect.s被BIOS加载到内存中的位置
mov ds,ax
mov ax,#INITSEG !INITSEG=0x9000,也即bootsect.s即将移动到的位置
mov es,ax
mov cx,#256 !一个扇区占用512b,由于使用串传送指令movw,因此将循环置为256
sub si,si
sub di,di
rep
movw !串传送指令,每次传送一个字的长度
jmpi go,INITSEG !长跳转指令,INITSEG -> cs : go -> ip
<2>加载setup.s
! 利用BIOS 中断INT 0x13 将setup 模块从磁盘第2个扇区,开始读到0x90200开始处,共读4个扇区。
! 如果读出错,则复位驱动器,并重试,没有退路。INT 0x13 的使用方法如下:
! 读扇区:
! ah = 0x02 - 读磁盘扇区到内存;al = 需要读出的扇区数量;
! ch = 磁道(柱面)号的低8位; cl = 开始扇区(0-5 位),磁道号高2位(6-7);
! dh = 磁头号; dl = 驱动器号(如果是硬盘则位7 要置位);
! es:bx 指向数据缓冲区; 如果出错则CF 标志置位。
! ah = 0x00 - 重置磁盘驱动器.
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
<3>获取磁盘驱动器的参数。使用BIOS中断 INT 0x13,功能ah = 0x08来获取 dl 驱动器的参数信息。
! 取磁盘驱动器的参数,特别是每道的扇区数量。
! 取磁盘驱动器参数INT 0x13 调用格式和返回信息如下:
! ah = 0x08 dl = 驱动器号(如果是硬盘则要置位7 为1)。
! 返回信息:
! 如果出错则CF 置位,并且ah = 状态码。
! ah = 0, al = 0, bl = 驱动器类型(AT/PS2)
! ch = 最大磁道号的低8 位,cl = 每磁道最大扇区数(位0-5),最大磁道号高2 位(位6-7)
! dh = 最大磁头数, dl = 驱动器数量,
! es:di -> 软驱磁盘参数表。
mov dl,#0x00
mov ax,#0x0800 ! AH=8 is get drive parameters
int 0x13
mov ch,#0x00
seg cs ! 表示下一条语句的操作数在cs 段寄存器所指的段中。
mov sectors,cx ! 保存每磁道扇区数。cx 是驱动器每道的最大扇区数。
mov ax,#INITSEG
mov es,ax ! 因为上面取磁盘参数中断改掉了es 的值,这里重新改回。
--注解:由于system模块比较的大,而int 0x13中断需要指定数据所在的磁道信息,因此需要知道每个磁道的扇区数,进而计算数据分布在哪个磁道。
<4>打印 loading system...
使用BIOS中断 int 0x10 打印 Loading System...
<5>加载system模块
加载system的时候为何要使用驱动器的参数信息?
由于system的扇区数默认是SYSSIZE=0x3000,由于不同的磁盘每一道的扇区数不一样,因此要计算出system所在磁道和扇区等信息。
2)setup.s
功能:获取系统数据,并设置IDT和GDT,开A20地址线,进入保护模式。
利用ROM BIOS中断读取机器系统数据,并且这些数据保存到0x90000开始的位置
关于GDTR和IDTR寄存器的说明。
GDTR和IDTR寄存器都是48位的。高32是线性基地址,低16位是GDT或IDT表长度
<1>读取机器系统数据
start:
mov ax,#INITSEG ! 将ds 置成#INITSEG(0x9000)。
mov ds,ax
mov ah,#0x03 ! read cursor pos
! BIOS 中断0x10 的读光标功能号 ah = 0x03
! 输入:bh = 页号
! 返回:ch = 扫描开始线,cl = 扫描结束线,
! dh = 行号(0x00 是顶端),dl = 列号(0x00 是左边)。
xor bh,bh
int 0x10
mov [0],dx ! 将光标位置信息存放在0x90000 处,控制台初始化时会来取。
! Get memory size (extended mem, kB) 取扩展内存的大小值(KB)。
! 是调用中断0x15,功能号ah = 0x88
! 返回:ax = 从0x100000(1M)处开始的扩展内存大小(KB)。
! 若出错则CF 置位,ax = 出错码。
mov ah,#0x88
int 0x15
mov [2],ax ! 将扩展内存数值存在0x90002 处(1 个字)。
! Get video-card data: 取显示卡当前显示模式。
! 调用BIOS 中断0x10,功能号 ah = 0x0f
! 返回:ah = 字符列数,al = 显示模式,bh = 当前显示页。
! 0x90004(1 字)存放当前页,0x90006 显示模式,0x90007 字符列数。
mov ah,#0x0f
int 0x10
mov [4],bx ! bh = display page
mov [6],ax ! al = video mode, ah = window width
! check for EGA/VGA and some config parameters ! 检查显示方式(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 = 显示卡特性参数。
! Get hd0 data ! 取第一个硬盘的信息(复制硬盘参数表)。
! 第1 个硬盘参数表的首地址竟然是中断向量0x41 的向量值!而第2 个硬盘
! 参数表紧接第1 个表的后面,中断向量0x46 的向量值也指向这第2 个硬盘
! 的参数表首址。表的长度是16 个字节(0x10)。
! 下面两段程序分别复制BIOS 有关两个硬盘的参数表,0x90080 处存放第1 个
! 硬盘的表,0x90090 处存放第2 个硬盘的表。
mov ax,#0x0000
mov ds,ax
lds si,[4*0x41] ! 取中断向量0x41 的值,也即hd0 参数表的地址??ds:si
mov ax,#INITSEG
mov es,ax
mov di,#0x0080 ! 传输的目的地址: 0x9000:0x0080 ?? es:di
mov cx,#0x10 ! 共传输0x10 字节。
rep
movsb
! Get hd1 data
mov ax,#0x0000
mov ds,ax
lds si,[4*0x46] ! 取中断向量0x46 的值,也即hd1 参数表的地址??ds:si
mov ax,#INITSEG
mov es,ax
mov di,#0x0090 ! 传输的目的地址: 0x9000:0x0090 ?? es:di
mov cx,#0x10
rep
movsb
! Check that there IS a hd1 :-) ! 检查系统是否存在第2 个硬盘,如果不存在则第2 个表清零。
! 利用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 ! 是硬盘吗?(类型 = 3 ?)。
je is_disk1
<2>移动system模块到0x0000处
do_move:
mov es,ax ! destination segment ! es:di -> 目的地址(初始为0x0000:0x0)
add ax,#0x1000
cmp ax,#0x9000 ! 已经把从0x8000 段开始的64k 代码移动完?
jz end_move
mov ds,ax ! source segment ! ds:si -> 源地址(初始为0x1000:0x0)
sub di,di
sub si,si
mov cx,#0x8000 ! 移动0x8000 字(64k 字节)。
rep
movsw
jmp do_move
<3>设置IDT和GDT,开A20地址线.
将system移动到0x0000之后变设置IDT和GDT,开启保护模式.
end_move:
mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)
mov ds,ax ! ds 指向本程序(setup)段。
lidt idt_48 ! load idt with 0,0
! 加载中断描述符表(idt)寄存器,idt_48 是6 字节操作数的位置。前2 字节表示idt 表的限长,后4 字节表示idt 表所处的基地址。
lgdt gdt_48 ! load gdt with whatever appropriate
! 加载全局描述符表(gdt)寄存器,gdt_48 是6 字节操作数的位置。
! that was painless, now we enable A20
! 以上的操作很简单,现在我们开启A20 地址线。参见程序列表后有关A20 信号线的说明。
! 关于所涉及到的一些端口和命令,可参考kernel/chr_drv/keyboard.S 程序后对键盘接口的说明。
call empty_8042 ! 等待输入缓冲器空。
! 只有当输入缓冲器为空时才可以对其进行写命令。
mov al,#0xD1 ! command write ! 0xD1 命令码-表示要写数据到
out #0x64,al ! 8042 的P2 端口。P2 端口的位1 用于A20 线的选通。数据要写到0x60 口。
call empty_8042 ! 等待输入缓冲器空,看命令是否被接受。
mov al,#0xDF ! A20 on ! 选通A20 地址线的参数。
out #0x60,al
call empty_8042 ! 输入缓冲器为空,则表示A20 线已经选通。
3)head.s
功能:重新设置IDT和GDT信息,使用setup_idt和setup_gdt来重设IDT和GDT。
<1>setup_idt函数重置IDT表
----idt描述符----
.align 2 # 按4 字节方式对齐内存地址边界。
idt_descr: #下面两行是lidt 指令的6 字节操作数:长度,基址。
.word 256*8-1 # idt contains 256 entries idt表的长度256项(16位)
.long idt # idt 表的基地址(32位)
----中断向量表----
.align 3 # 按8 字节方式对齐内存地址边界。
idt: .fill 256,8,0 # idt is uninitialized # 256 项,每项8 字节,填0。
----setup_idt函数----
setup_idt:
lea ignore_int,%edx # 将ignore_int 的有效地址(偏移值)值-> edx 寄存器
movl $0x00080000,%eax # 将选择符0x0008 置入eax 的高16 位中。
movw %dx,%ax /* selector = 0x0008 = cs */
# 偏移值的低16 位置入eax 的低16 位中。此时eax 含有
#门描述符低4 字节的值。
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
# 此时edx 含有门描述符高4 字节的值。
lea idt,%edi # idt 是中断描述符表的地址。
mov $256,%ecx # idt一共256项。都设置为ignore_int函数。
rp_sidt:
movl %eax,(%edi) # 将哑中断门描述符存入表中。
movl %edx,4(%edi) # 将ignore_int设置给idt。
addl $8,%edi # edi 指向表中下一项。
dec %ecx
jne rp_sidt
lidt idt_descr # 加载中断描述符表寄存器值。
ret
<2>setup_gdt 函数重置GDT表
----gdt描述符----
.align 2
gdt_descr: # 下面两行是lgdt 指令的6 字节操作数:长度,基址。
.word 256*8-1 # so does gdt (not that that's any # not ?? note.
.long gdt # magic number, but it works for me :^)
----gdt表----
# 全局表。前4 项分别是空项(不用)、代码段描述符、数据段描述符、系统段描述符,其中
# 系统段描述符linux 没有派用处。后面还预留了252 项的空间,用于放置所创建任务的
# 局部描述符(LDT)和对应的任务状态段TSS 的描述符。
# (0-nul, 1-cs, 2-ds, 3-sys, 4-TSS0, 5-LDT0, 6-TSS1, 7-LDT1, 8-TSS2 etc...)
.align 3 # 按8 字节方式对齐内存地址边界。
gdt: .quad 0x0000000000000000 /* NULL descriptor */
.quad 0x00c09a0000000fff /* 16Mb */ # 0x08,内核代码段最大长度16M。
.quad 0x00c0920000000fff /* 16Mb */ # 0x10,内核数据段最大长度16M。
.quad 0x0000000000000000 /* TEMPORARY - don't use */
.fill 252,8,0 /* space for LDT's and TSS's etc */
----setup_gdt函数----
setup_gdt:
lgdt gdt_descr # 加载全局描述符表寄存器(内容已设置好,见234-238 行)。
ret