操作系统的引导
完成 bootsect.s 的屏幕输出功能
关键代码如下
1 |
|
我们修改的字符为19个,再加上前后一共3个回车加换行,所以总共25个字符。
将 .org 508 修改为 .org 510,是因为这里不需要 root_dev: .word ROOT_DEV,为了保证 boot_flag 一定在最后两个字节,所以要修改 .org。
执行下面两个命令编译和链接 bootsect.s
1 | $ as86 -0 -a -o bootsect.o bootsect.s |
需要留意的文件是 bootsect
的文件大小是 544
字节,而引导程序必须要正好占用一个磁盘扇区,即 512
个字节。 造成多了 32
个字节的原因是 ld86
产生的是 Minix
可执行文件格式, 这样的可执行文件处理文本段、数据段等部分以外,还包括一个 Minix
可执行文件头部。
去掉32字节的头部文件。
1 | dd bs=1 if=bootsect of=Image skip=32 |
接下来进行拷贝,并且run
1 | # 当前的工作路径为 /common/linux-0.11/boot/ |
注:由于我执行run的时候有些许问题,所以我对于目录结构进行了修改。
效果如下图
bootsect.s 读入 setup.s
首先编写一个 setup.s,该 setup.s 可以就直接拷贝前面的 bootsect.s,然后将其中的显示的信息改为:“Now we are in SETUP”。
1 | entry _start |
接下来需要编写 bootsect.s
中载入 setup.s
的关键代码,注意去掉无限循环
1 | SETUPLEN=2 ! 读入的扇区数 |
效果如下图
需要修改build.c,并且删除同目录下的 hdc-0.11.img.lock
即可。
setup.s 获取基本硬件参数
setup.s
将获得硬件参数放在内存的 0x90000
处。原版 setup.s
中已经完成了光标位置、内存大小、显存大小、显卡参数、第一和第二硬盘参数的保存。
用 ah=#0x03
调用 0x10
中断可以读出光标的位置,用 ah=#0x88
调用 0x15
中断可以读出内存的大小。 有些硬件参数的获取要稍微复杂一些,如磁盘参数表。在 PC
机中 BIOS
设定的中断向量表中 int 0x41
的中断向量位置( 4*0x41 = 0x0000:0x0104
)存放的并不是中断程序的地址,而是第一个硬盘的基本参数表。 第二个硬盘的基本参数表入口地址存于 int 0x46
中断向量位置处。每个硬盘参数表有 16
个字节大小。
在PC机中BIOS设定的中断向量表中int 0x41的中断向量位置存放的并不是中断程序的地址,而是第一个硬盘的基本参数表。对于100%兼容的BIOS来说,这里存放着硬盘参数表阵列的首地址0xF000:0E401,第二个硬盘的基本参数表入口地址存于int 0x46中断向量位置处.每个硬盘参数表有16个字节大小.
这段话是重点,磁盘参数就存放在以0x0000:0x0104为首地址的单元中只存了4个字节,里面存放的是磁盘参数表的偏移地址和段地址,也就是上文所说这里存放着硬盘参数表阵列的首地址0xF000:0E401。
以十六进制方式显示比较简单。这是因为十六进制与二进制有很好的对应关系(每 4 位二进制数和 1 位十六进制数存在一一对应关系),显示时只需将原二进制数每 4 位划成一组,按组求对应的 ASCII 码送显示器即可。ASCII 码与十六进制数字的对应关系为:0x30 ~ 0x39 对应数字 0 ~ 9,0x41 ~ 0x46 对应数字 a ~ f。从数字 9 到 a,其 ASCII 码间隔了 7h,这一点在转换时要特别注意。为使一个十六进制数能按高位到低位依次显示,实际编程中,需对 bx 中的数每次循环左移一组(4 位二进制),然后屏蔽掉当前高 12 位,对当前余下的 4 位(即 1 位十六进制数)求其 ASCII 码,要判断它是 0 ~ 9 还是 a ~ f,是前者则加 0x30 得对应的 ASCII 码,后者则要加 0x37 才行,最后送显示器输出。以上步骤重复 4 次,就可以完成 bx 中数以 4 位十六进制的形式显示出来。
如下图:
代码如下
1 | ! 文件setup.s |
问题解答
1 | 当PC的电源打开后,80x86结构的CPU将自动进入实模式,并从地址0xFFFF0开始自动执行程序代码,这个地址通常是ROM—BIOS中的地址。PC机的BIOS将执行某些系统的检测,并在物理地址0处开始初始化中断向量。此后将启动设备的第一个扇区512字节读入内存绝对地址0x7C00处。因为当时system模块的长度不会超过0x80000字节大小512KB,所以bootsect程序把system模块读入物理地址0x10000开始位置处时并不会覆盖在0x90000处开始的bootsect和setup模块,多此一举的是system模块移到内存中相对靠后的位置,以便加载系统主模块。解决方案是在保证操作系统启动引导成功的前提下尽量扩大ROM—BIOS的内存寻址范围。 |
知识点记录
数据寄存器
AH&AL=AX(accumulator):累加寄存器,常用于运算;在乘除等指令中指定用来存放操作数,另外,所有的I/O指令都使用这一寄存器与外界设备传送数据。
BH&BL=BX(base):基址寄存器,常用于地址索引
CH&CL=CX(count):计数寄存器,常用于计数;常用于保存计算值,如在移位指令,循环(loop)和串处理指令中用作隐含的计数器.
DH&DL=DX(data):数据寄存器,常用于数据传递。
他们的特点是,这4个16位的寄存器可以分为高8位: AH, BH, CH, DH.以及低八位:AL,BL,CL,DL。这2组8位寄存器可以分别寻址,并单独使用。
中断知识
int 0x10
注意,这里ah要先有值,代表内部子程序的编号
功能号ah=0x03,作用是读取光标的位置
输入:
1 | bh = 页号 |
返回:
1 | ch = 扫描开始线;cl = 扫描结束线;dh = 行号;dl = 列号 |
功能号ah=0x13,作用是显示字符串
输入:
1 | al = 放置光标的方式及规定属性,下文 al=1,表示目标字符串仅仅包含字符,属性在BL中包含,光标停在字符串结尾处;es:bp = 字符串起始位置;cx = 显示的字符串字符数;bh = 页号;bl = 字符属性,下文 bl = 07H,表示正常的黑底白字;dh = 行号;dl = 列号 |
功能号ah=0x0e,作用是显示字符
输入:
1 | al = 字符 |
int 0x13
在DOS等实模式操作系统下,调用INT 13h会跳转到计算机的ROM-BIOS代码中进行低级磁盘服务,对程序进行基于物理扇区的磁盘读写操作。
功能号ah=0x02,作用是读磁盘扇区到内存
输入:
寄存器 | 含义 |
---|---|
ah | 读磁盘扇区到内存 |
al | 需要读出的扇区数量 |
ch | 磁道 |
cl | 扇区 |
dh | 磁头 |
dl | 驱动器 |
es:bx | 数据缓冲区的地址 |
返回
1 | ah = 出错码(00H表示无错,01H表示非法命令,02H表示地址目标未发现…);CF为进位标志位,如果没有出错CF=0 |
功能号ah=0x00,作用是磁盘系统复位
1 | 输入:dl = 驱动器 |
int 0x15
功能号ah=0x88,作用是获取系统所含扩展内存大小
1 | 输入:ah = 0x88 |
int 0x41
在PC机中BIOS设定的中断向量表中int 0x41的中断向量位置 (4 ∗ 0 x 41 = 0 x 0000 : 0 x 0104 4*0x41 = 0x0000:0x01044∗0x41=0x0000:0x0104)存放的并不是中断程序的地址,而是第一个硬盘的基本参数表。对于100%兼容的BIOS来说,这里存放着硬盘参数表阵列的首地址0xF000:0E401,第二个硬盘的基本参数表入口地址存于int 0x46中断向量位置处.每个硬盘参数表有16个字节大小.
其他
lds
格式: LDS reg16,mem32
其意义是同时给一个段寄存器和一个16位通用寄存器同时赋值
举例:
地址 | 100H | 101H | 102H | 103H |
---|---|---|---|---|
内容 | 00H | 41H | 02H | 03H |
1 | LDS AX,[100H] |