计算机启动时BIOS会把启动盘第一个扇区的数据读入内存0x7C00开始处,然后跳到这里继续执行。从硬盘启动和从软盘启动唯一的区别就是映象文件存储方式的不同:
1. 对于从软盘启动的方式,映象文件连续地存放在软盘开始的位置处。放在第一个扇区的bootsect.s被BIOS读入内存后,就会把余下的映象文件读入内存,然后继续执行
2. 对于从硬盘启动的方式,映象文件存放在Minix格式的硬盘分区里,MBR(硬盘第一个扇区)中的程序需要根据硬盘的参数和Minix文件系统的存储格式读出它
本文描述了如何编写MBR中的程序,把存放在硬盘第一个分区根目录下的linux0.11映像文件Image读入内存。
文章中小字体的括号里的数字表示参考文献的编号(见后面的参考文献列表)。
本文分5个部分: 1、硬盘简介
2、Minix 1.0文件系统简介
3、程序流程
4、上机实验
5、程序代码(以附件形式给出)
一.硬盘简介(1)
图1. 硬盘扇区编号(此图来源文献1)
我们需要了解的是物理扇区编号方式和绝对扇区编号方式。物理扇区号直接按柱面、磁头、扇区3者的组合来定位某个扇区。对于硬盘的第一个扇区,其编号为“0柱面0磁头1扇区”。我们假设硬盘磁头数为16,每磁道扇区数为63,下面描述遍历整个硬盘时柱面号、磁头号、扇区号的变化规律(以(x,y,z)表示x柱面y磁头z扇区): (0,0,1)、(0,0,2)、……、(0,0,63)、
(0,1,1)、(0,1,2)、……、(0,1,63)、
(0,2,1)、(0,2,2)、……、(0,2,63)、
……
(0,15,1)、(0,15,2)、……、(0,15,63)、
(1,0,1)、(1,0,2)、……、(1,0,63)、
(1,1,1)、(1,1,2)、……、(1,1,63)、
换句话说就是扇区号是从1到63的63进制,磁头号是0到15的16进制,“百位”是柱面号,“十位”是磁头号,“个位”是扇区号。
绝对扇区号从0开始,遍历硬盘时依次增1。
两者的换算关系如下(abs_sector表示绝对扇区号,cyl表示柱面号,head表示磁头号,sector表示扇区号,nheads表示磁头数,nspt表示每磁道扇区数):
sector = abs_sector % nspt + 1
track = abs_sector / nspt
head = track % nheads
cyl = track / nheads
如何知道上面说的磁头数nheads、每磁道扇区数nspt呢?启动时BIOS会把硬盘参数表放在内存某个位置。对于第一个硬盘,硬盘参数表的首地址放在中断0x41处,即内存地址4*0x41=0x104开始的4个字节表示硬盘参数表的段地址(后面2字节)和偏移地址(前面2字节)。硬盘参数表的结构如下:
dw cylinders
db nheads
dw 0
dw write pre-comp
db 0
db 0 "control byte"
db 0, 0, 0
dw landing zone
db nspt(sectors/track)
db 0
第二个硬盘的参数表地址放在BIOS中断向量0x46处。
对于硬盘,我们还需要稍微知道一点分区表的信息。分区表放在硬盘第一个扇区的0x1be~0x1fd处(共64字节,第一个分区的信息在0x1be~0x1ce)。我们需要使用的仅仅是存放在0x1c6处的“分区起始绝对扇区号”(对应于程序中的start_sect变量)
二.Minix 1.0文件系统简介(2) (3)
关于Minix 1.0文件系统的更详细知识请参考文献2、3,下面仅介绍理解本程序需要了解的知识。
Minix 1.0格式的分区结构如下:
引导块
超级块
i点位图块…
逻辑块位图块…
i节点块…
数据区………
1、对于Minix 1.0,每个盘块占1k字节(即2个扇区)。引导块的第一个扇区就是本分区的第一个扇区,扇区号存在MBR中0x1c6处(就是上文的“分区起始绝对扇区号”)。盘块从0开始编号,对于盘块n,其绝对扇区号 = 2*n +分区起始绝对扇区号。
2、i点位图块和逻辑块位图块的大小、数据区的起始盘块号等信息存放在超级块中,超级块的数据结构如下:
struct d_super_block{
unsigned short s_ninodes; //节点数
unsigned short s_nzones; //逻辑块数
unsigned short s_imap_blocks; //i节点位图所占用的数据块数
unsigned short s_zmap_blocks; //逻辑块位图所占用的数据块数
unsigned short s_firstdatazone; //第一个数据逻辑块
unsigned short s_log_zone_size; //log2(数据块数/逻辑块)
unsigned long s_max_size; //文件最大长度
unsigned short s_magic;}; //文件系统魔数
我们只需要知道开始存放i节点的盘块号inode_start_zone和第一个数据逻辑块号firstdatazone:
inode_start_zone = 2 + s_imap_blocks + s_zmap_blocks
firstdatazone = s_firstdatazone
3、每个i节点占32字节,其数据结构如下:
struct d_inode{
unsigned short i_mode; //文件类型和属性
unsigned short i_uid; //用户id
unsigned long i_size; //文件大小(字节数)
unsigned long i_time; //修改时间(1970.1.1:0算起,秒)
unsigned char i_gid; //组id
unsigned char i_nlinks; //链接数
unsigned short i_zone[9];}; //直接(0~6)、间接(7)、双重间接
//(8)逻辑块号
根据其中的i_size可以计算出文件占用的盘块数 = (i_size + 1023) / 1024
文件数据的前面7k存放在称为直接块的7个盘块中,这7个盘块的盘块号存在i_zone[0]~i_zone[6]中。如果文件大于7k,则需要使用到一次间接块(i_zone[7]),它对应的盘块里存放的不是文件数据,而是7k之后的文件数据存放的盘块号。比如一次间接块的第1、2字节指出第8k文件数据存放的盘块号码。一次间接块可以存放512个盘块号。如果文件大于(512+7)k,则需要使用二次间接块i_zone[8]。图示如下:
图2. 文件数据存储结构(此图来源文献3)
根据i_zone[0]~i_zone[6],可以直接读出前面7k数据;然后读出一次间接块i_zone[7],根据其中的盘块号即可读出其余的数据;忽略i_zone[8],因为linux0.11映像文件没那么大。
图3是映像文件Image一次间接块的前面部分数据。每两个字节表示一个盘块号。“00 00”表示这1k的数据都是0(比如红圈里的),蓝圈里的“70 05”表示这1k数据存放的盘块号是0x0570。
图3. 映像文件Image一次间接块的前面部分数据
4、第一个数据块存放的就是根目录文件的前1k数据,根目录文件存放的是称为目录项的信息。其数据结构如下:
struct dir_entry{
unsigned short inode; //i节点
char name[14];}; //文件名
图4就是根目录文件第一个盘块的部分信息,图中红圈里面的数据表示本本目录项的i节点,蓝圈里面的数据表示该目录形的名称是Image。从图上我们可以看到,前面两个目录项分别是目录“.”和目录“..”,紧随其后的是bin、dev、etc、mnt、root、tmp、usr等目录,再后面的是hello.c、Image、image文件(至于是目录还是文件,需要找出相应的i节点根据其i_mode项来判断)。
图4. 根目录文件第一个数据块部分信息
三.程序流程
现在可以描述查找映像文件并读出它的过程了:
a. 获取硬盘参数——磁头数、每磁道扇区数(以便计算绝对扇区号对应的柱面号、磁头号、扇区号),获取分区参数——第一个分区起始绝对扇区号(以定位Minix文件系统的逻辑块)
b. 读出超级块,计算出i节点开始存放的盘块号inode_start_zone和第一个数据逻辑块号firstdatazone
c. 读出根目录文件(本程序只读出了它前面的1k数据——即第一个数据逻辑块)
d. 根据映像文件名Image找到对应的目录项,取得其i节点编号(0x004A,图4红圈部分)
e. 根据映像文件i节点编号和inode_start_zone读出i节点
f. 根据i节点的i_size信息确定要读取的盘块数,根据i_zone[0]~i_zone[6]读出前面7k数据
g. 根据i_zone[7]读出一次间接块的数据
h. 根据一次间接块确定的盘块号,读出
linux0.11映像文件由4部分组成:1、bootsect.s,2、setup.s,3、head.s,4、系统模块。从软盘启动时,bootsect.s生成的可执行代码(512字节)被读入0x7C00处,它执行时先把自己移到0x90000处,然后把setup.s生成的可执行代码(大小为2k)从软盘读入到内存0x90200处,并把映像文件余下部分读到内存0x10000处。本MBR程序把映像文件前面2.5k数据(对应512字节的bootsect.s可执行代码和2k的setup.s可执行代码)读到0x90000处,把映像文件余下部分读到内存0x10000处,然后跳到0x90200执行setup.s代码。可见,本程序的功能只是与bootsect.s相似,bootsect.s从软盘读出映像文件,而本程序从硬盘中读出。
程序代码见第5部分的bootload.s,图5是流程图。
图5. MBR程序流程图
四.上机实验
实验工具:
1、Bochs PC仿真软件上运行的SLS-Linux
下载地址:
http://oldlinux.org/Linux.old/bochs/sls-1.0.zip
2、linux-0.11-devel-040809.zip压缩包(里面有Bochs安装程序)
下载地址:
http://oldlinux.org/Linux.old/bochs/linux-0.11-devel-040809.zip
3、DOS格式映像文件工具WinImage
下载地址:
http://ourworld.compuserve.com/homepages/gvollant/winima70.exe
实验步骤:
hdc-0.11.img硬盘映像文件上有linux0.11根文件系统,但现在它不能引导linux0.11:把bochsrc-hd.bxrc文件中的“boot:a”改为“boot:c”,运行bochsrc-hd.bxrc将导致引导失败。下面修改其MBR使得可以引导:
1、把sls-1.0.zip和linux-0.11-devel-040809.zip解压到同一个目录下,安装Bochs2.1.1。
2、用文本编辑器打开bochsrc.bxrc,把69行“floppyb:1_44=tmp1.imz, status=inserted”中的tmp1.imz改为tmp.imz(原文有误)
3、在bochsrc.bxrc中添加一行,挂接hdc-0.11.img:
“ata0-slave: type=disk, path="hdc-0.11.img", mode=flat, cylinders=121, heads=16, spt=63”
4、使用WimImage打开tmp.imz,把我们所写的MBR程序bootload.s和makefile拖入,保存
5、双击运行bochsrc.bxrc,按空格键进入,使用root登录
6、运行如下命令将linux0.11映像文件复制到hdb1的根目录下:
mount /dev/hdb1 /mnt
cp /mnt/usr/src/linux/Image /mnt/Image
7、下面把存在b盘(即tmp.imz软盘映像)中的bootload.s和makefile复制出来:
mkdir asm
cd asm
mcopy b:\makefile makefile
mcopy b:\bootload.s bootload.s
8、汇编、链接bootload.s,把可执行代码写到hdb1的MBR中去:
make //参考附录makefile文件的说明
9、退出:
umount /mnt
关闭Bochs
步骤6~9的运行界面如图6
图6. 修改硬盘映像文件MBR
10、修改bochsrc-hd.bxrc,把boot:a改为boot:c,双击运行。成功!界面如下:
图7. 从硬盘启动linux0.11
五.程序代码
见随本文件发布的附件(一个是MBR程序bootload.s,一个是makefile文件)
参考文献:
1、《带您深入了解硬盘分区表与逻辑锁》
作者:迈克尔
来源:家缘硬件网
日期:2005.1.9
链接:
http://www.pcnethome.com/Article/NEWS/skill/200501/1037.html
2、《Linux0.11学习体会》
作者:chenfangjian
来源:http://www.oldlinux.org/论坛子论坛“Linux内核完全注释”
链接:
http://www.oldlinux.org/cgi-bin/LB5000XP/topic.cgi?forum=1&topic=1507
3、《Linux内核完全注释》
作者:赵炯
机械工业出版社
4、 http://www.oldlinux.org/论坛子论坛“Linux 0.11系统的建立和实验” 上linuxlala 回复的一篇帖子,他详细描述了编写linux0.11引导程序的方法。
链接:
http://www.oldlinux.org/cgi-bin/LB5000XP/topic.cgi?forum=4&topic=226