系统的引导过程和磁盘分区信息
在PC机上,最初的启动由BIOS完成。当开机自检(Power-On Self Test,POST)结束时, BIOS尝试读入磁盘的第一个扇区,把它看作引导扇区。由于大多数BIOS不提供SCSI支持,若要从SCSI磁盘启动,SCSI适配器要提供他自己的 BIOS。如果什么都找不到,老的BIOS会启动内置的ROM BASIC,或直接打印“NO ROM-BASIC”。
操作系统的启动分几步完成。由于引导扇区比较小,通常它主要任务是读入第二个loader,第二个loader再读入第三个loader,直到整个操作系统被完全读入。
硬盘的引导区:
代码:
OFFSET
0x000 JMP xx Near jump into the program code
0x003 Disk parameters
0x03E Program code loading the DOS kernel
0x1FE 0xAA55 Magic number for BIOS
可见,引导区的结构相对比较简单。它的长度总是512字节。重要的是引导区从0开始,以BIOS的0x1FE处的0xAA55结束。
从软盘启动比较简单,因为只有一个引导扇区:第一个扇区。硬盘则困难一些,它被分成很多分区。但是,BIOS根本不管分区信息,它象对待软盘一样对待硬盘,仍读入第一个分区,叫作:master boot record。(MBR)。所以MBR也应该和上面介绍的结构一样,在MBR的最后部分,有分区表。如下图:
代码:
OFFSET Length
0x000 0x1BE code loading and starting the boot sector of the active
partitian
0x1BE 0x010 partition1
0x1CE 0x010 partition2
0x1DE 0x010 partition3
0x1EE 0x010 partition4
0x1FE 0x0012 0xAA55 Disk parameters
从0x1BE处(即第462字节处)开始,是一个个主分区信息。每个分区信息占16字节,结构如下:
1 BOOT Boot flag: 0=not active ,0x80 active
1 HD Begin:head number
2 SEC CYL Begin:sector and cylinder number of boot sector
1 SYS System Code:0x83 linux , 0x82 linux swap etc。
1 HD End:head number
2 SEC CYL End: sector and cylinder number of boot sector
4 low byte high byte Relative sector number of start sector
4 low byte high byte Number of sectors in the partition
所以硬盘可以有4个主分区:primary prititions。假如它们不够用,可以设置所谓的扩展分区。扩展分区包含至少一个逻辑分区。扩展分区的第一个扇区结构类似MBR,它的分区表的第一表项对应第一个逻辑分区。如果存在第二个逻辑分区,那么分区表的第二个表项就包含了一个指针。这个指针指向第一个逻辑分区后面的一个地址,这个地址包含一个分区表,该分区表的第一表项对应第二个逻辑分区。这样就组成一个链表,从而扩展分区可以有任意多的逻辑分区。
每一个主分区和扩展区都包含一个引导扇区。系统只能从这几个地方之一启动。BOOT标志决定哪个区被引导。
MBR的代码要作以下的操作:
1:确定活动分区。
2:使用BIOS,将活跃分区的启动扇区读入。
3:跳到启动扇区的0位置。
MBR的空间足够完成这些工作。如上所述,每个分区理论上包含一个引导扇区,而且,存在的第二个硬盘也包含和第一个类似的结构。MBR完全可以容纳一个复杂的引导程序。即所谓的boot manager,动态的决定活动分区。Linux为我们提供了lilo及grub等工具来管理启动各启动项。
下面这段C程序可以用来检测磁盘分区信息以验证上述理论的正确性。
代码:
/* marco corvi <marco_corvi@geocities.com
* @date
feb 2003
*
* \brief Read disk partition table
* modified by zhoulifa <zhoulifa@163.com http://zhoulifa.bokee.com
* 周立发 Linux爱好者 Linux知识传播者 SOHO族 开发者
*/
#include <stdio.h
// printf
#include <stdlib.h
// exit
#include <sys/types.h
#include <sys/stat.h
#include <fcntl.h
#include <errno.h
#include <unistd.h
int
main(int argc, char ** argv )
{
int fd;
unsigned char mbs[512]; // master boot sector
unsigned char * pp;
// partition pointer
int head, sect, cyl;
unsigned int sector;
int n;
if (argc <= 1) {
fprintf(stderr, "Usage: %s <device\n", argv[0] );
exit(1);
}
fd = open( argv[1], O_RDONLY );
if ( fd < 0 ) {
fprintf(stderr, "Error: unable to open device %s\n", argv[1] );
perror("");
exit(1);
}
n = read( fd, mbs, 512);
close(fd);
if ( n < 512 ) {
fprintf(stderr, "Error: short read %d\n", n );
exit(1);
}
pp = mbs + 0x1BE;
for (n=0; n<4; n++) {
printf("PARTITION %d\n", n );
printf("Boot flag
%2x\n", pp[0] );
printf("System flag %2x\n", pp[4] );
// C/H/S as 10+8+6
if(argc == 3)
{
head = pp[1];
sect = pp[2]; sect = (sect<<2) | (pp[3]6);
cyl
= pp[3] & 0x3f;
/*
cyl = pp[1] 2;
head = ((pp[1] & 0x03)<<6) | (pp[2]2);
sect = pp[2] & 0x03;
sect = (sect << 8) | pp[3];
head = pp[1];
sect = pp[3]6;
sect = (sect<<8) | pp[2];
cyl
= pp[3] & 0x3f;
*/
printf("Start Cylinder %d Head %d Sector %d \n", cyl, head, sect );
head = pp[5];
sect = pp[6]; sect = (sect<<2) | (pp[7]6);
cyl
= pp[7] & 0x3f;
cyl = pp[5];
printf("End
Cylinder %d Head %d Sector %d \n", cyl, head, sect );
}
sector = pp[8] + (1<<8)*pp[9] + (1<<16)*pp[10] + (1<<24)*pp[11];
printf("Sector number %u\n", sector);
sector = pp[12] + (1<<8)*pp[13] + (1<<16)*pp[14] + (1<<24)*pp[15];
printf("Number of sectors %u\n", sector);
pp += 0x10;
}
}
编译此程序后可以直接运行,如果没有任何参数,会从/dev/partitions文件里读取相应信息。如果有参数,则第一个参数指明要查看的设备。
参考了 http://www.linuxdby.com/articlesdisplay.php?newsid=75 及 marco_corvi的“Linux kernel programming by example”,并在Red Hat Linux 7.2系统上验证通过。