分类: LINUX
2006-03-12 00:58:21
编写MBR从硬盘启动linux0.11
作者: 日期: 2005.4.3
计算机启动时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数据——即第一个数据逻辑块)