一个操作系统的实现:第四篇——让操作系统走进保护模式

参考链接:

FAT16文件系统简介:https://blog.csdn.net/menghnhhuan/article/details/4270168

引导扇区:https://baike.baidu.com/item/%E5%BC%95%E5%AF%BC%E6%89%87%E5%8C%BA/7444926?fr=aladdin

主引导记录:https://baike.baidu.com/item/%E4%B8%BB%E5%BC%95%E5%AF%BC%E8%AE%B0%E5%BD%95

DBR:https://baike.baidu.com/item/DBR/4998996?fr=aladdin

BIOS 中断大全:https://blog.csdn.net/weixin_37656939/article/details/79684611

 

FAT12:
        FAT12 是DOS时代就开始使用的文件系统(File System),直到现在仍然在软盘上使用。几乎所有的文件系统都会把磁盘划分为若干层次以方便组织和管理,这些层次包括:
扇区(Sector):磁盘上的最小数据单元。
簇(Cluster):一个或多个扇区。
分区(Partition):通常指整个文件系统。

软盘(1.44MB, FAT12)结构:

一个操作系统的实现:第四篇——让操作系统走进保护模式_第1张图片

3.5寸1.44M软盘结构:
1、 结构:2面、80道/面、18扇区/道、512字节/扇区
        扇区总数=2面 X  80道/面 X  18扇区/道  =  2880扇区
        存储容量= 512字节/扇区X  2880扇区 =  1440 KB
2、  2  面: 编号0----1;
        80道: 编号0----79 ;
        18扇区:编号1----18 ;
3、相对扇区号:共2880个扇区,相对扇区号范围为 0----2879
编号顺序:
扇区物理号               相对扇区号
0面,0道,1扇区             0
0面,0道,2扇区             1
0面,0道,3扇区             2
…………………….
0面,0道,18扇区           17
1面,0道,1扇区            18
……………
1面,0道,18扇区           35
0面,1道,1扇区            36
0面,1道,18扇区           53
1面,1道,1扇区            54
………
4、物理扇区号(A,B,C)与相对扇区号(S)相互转换公式:
头/面(0--1)      道(0-79)       扇区 (1--18) 
 A                       B                      C
例如:A=1面          B= 15道       C=7扇区  这就是它的物理扇区号,现在进入关键问题——如何计算相对扇区呢?
计算相对扇区时,参照上面的软盘结构排列表。我们应该清楚在15道之前,即0~14道里面,每道都有有18个扇区,而又0、1两面都有磁道,故而在0~14道有(0道----14道)*2面*18, 在计算第15道时,注意下我们要计算的15道是在1面,而1面之前的0面15道,有18个扇区,而在1面的15道磁道中,有7个扇区。一共有0面的第15道18个扇区+1面第15道7个扇区-1,所以上述例子中的相对扇区号是(0道----14道)*2面*18+0面的第15道18个扇区+1面第15道7个扇区-1

一个操作系统的实现:第四篇——让操作系统走进保护模式_第2张图片

引导扇区(Boot Sector) :
        通常指设备的第一个扇区,用于加载并转让处理器控制权给操作系统。

主引导扇区:
        硬盘的0柱面、0磁头、1扇区称为主引导扇区,也叫主引导记录MBR(MBR,Main Boot Record),该记录占用512个字节,它用于硬盘启动时将系统控制权转给用户指定的、在分区表中登记了某个操作系统分区。MBR的内容是在硬盘分区时由分区软件写入该扇区的,MBR不属于任何一个操作系统,不随操作系统的不同而不同,即使不同,MBR也不会夹带操作系统的性质,具有公共引导的特性。但安装某些多重引导功能的软件或LINUX的LILO时有可能改写它,它先于所有的操作系统被调入内存并发挥作用,然后才将控制权交给活动主分区内的操作系统。

MBR成员:
1.主引导程序代码,占446字节
2. 磁盘签名
3.硬盘分区表DPT,占64字节
4.主引导扇区结束标志55AAH
  硬盘的主引导程序代码是从偏移0000H开始到偏移01BDH结束的446字节;主引导程序代码包括一小段执行代码。启动PC 机时,系统首先对硬件设备进行测试,成功后进入自举程序INT 19H;然后读系统磁盘0柱面、0磁头、1扇区的主引导扇区MBR的内容到内存指定单元0:7C00 首地址开始的区域,并执行MBR程序段。

MBR功能:
1.扫描分区表查找活动分区;
2.寻找活动分区的起始扇区;
3.将活动分区的引导扇区读到内存;
4.执行引导扇区的运行代码。

分区引导扇区:
        分区引导扇区也称DBR(DOS BOOT RECORD,DOS引导记录),是由FORMAT高级格式化命令写到该扇区的内容,DBR是由硬盘的MBR装载的程序段。DBR装入内存后,即开始执行该引导程序段,其主要功能是完成操作系统的自举并将控制权交给操作系统。每个分区都有引导扇区,但只有被设为活动分区才会被MBR装的DBR入内存运行。
        DBR分为两部分:DOS引导程序和BPB(BIOS ParameterBlock,BIOS参数块)。

DBR成员:
1.跳转指令,占用3个字节的跳转指令将跳转至引导代码。
2.厂商标识和DOS版本号,该部分总共占用8个字节。
3.BPB(BIOS Parameter Block, BIOS 参数块)。
4.操作系统引导程序。
5.结束标志字,结束标志占用2个字节,其值为AA55
  DBR中的内容除了第5部分结束标志字固定不变之外,其余4个部分都是不确定的,其内容将随格式化所用的操作系统版本及硬盘的逻辑盘参数的变化而变化。

FAT分区引导扇区:
        引导扇区是FAT文件系统的第一个扇区,也称为DBR扇区。引导扇区的格式如下图所示,其中名称以BPB_开头的域属于BPB,以BS_开头的域不属于BPB,只是引导扇区(Boot Sector)的一部分。

FAT12引导扇区的格式:

一个操作系统的实现:第四篇——让操作系统走进保护模式_第3张图片
 

FAT表:
        它有点像是一个位图,其中,每12位称为一个FAT项(FATEntry),代表一个簇。第0个和第1个FAT项始终不使用,从第2个FAT项开始表示数据区的每一个簇,也就是说,第2个FAT项表示数据区第一个簇,依此类推。
        由于每个FAT项占12位,包含一个字节和另一个字节的一半,所以显得有点别扭。具体情况是这样的,假设连续3个字节分别如下图所示,那么灰色框表示的是前一个FAT项(FATEntry1),BYTE1是FATEntry1的低8位,BYTE2的低4位FATEntry1的高4位;白色框表示的是后一个FAT项(FATEntry2),BYTE2的高4位是FATEntry2的低4位,BYTE3是FATEntry2的高8位。
        通常,FAT项的值代表的是文件下一个簇号,但如果值大于或等于0xFF8,则表示当前簇已经是本文件的最后一个簇。如果值为0xFF7,表示它是一个坏簇。

一个操作系统的实现:第四篇——让操作系统走进保护模式_第4张图片

FAT记录项取值的参考:

一个操作系统的实现:第四篇——让操作系统走进保护模式_第5张图片

根目录区:
        根目录区位于第二个FAT表之后,开始的扇区号为19,它由若干个目录条目(Directory Entry)组成,条目最多有BPB_RootEntCnt个。由于根目录区的大小是依赖于BPB_RootEntCnt的,所以长度不固定。根目录区中的每一个条目占用32字节,它的格式如下图所示。

一个操作系统的实现:第四篇——让操作系统走进保护模式_第6张图片

数据区第一个簇即第一个扇区在哪里呢?参照上图我们发现,必须计算根目录区所占的扇区数才能知道。我们既然已经知道了根目录区条目最多有BPB_RootEntCnt个,扇区数也就容易计算了,假设根目录区共占用RootDirSectors 个扇区,则有:

下面是虚拟FAT文件系统的解析程序,只实现了读取根目录信息:


#include 
#include 
#include 

#pragma pack(1)

typedef struct{
    unsigned short  BytsPerSec;
    unsigned char   SecPerClus;
    unsigned short  RsvdSecCnt;
    unsigned char   NumFATs;
    unsigned short  RootEntCnt;
    unsigned short  TotSec16;
    unsigned char   Media;
    unsigned short  FATSz16;
    unsigned short  SecPerTrk;
    unsigned short  NumHeads;
    unsigned int    HiddSec;
    unsigned int    TotSec32;
}BPB_t;

typedef struct
{
    unsigned char   jmpBoot[3];
    unsigned char   OEMName[8];
    BPB_t           Bpb;
    unsigned char   DrvNum;
    unsigned char   Reserved1;
    unsigned char   BootSig;
    unsigned int    VolId;
    unsigned char   VolLab[11];
    unsigned char   FileSysType[8];
    unsigned char   BootCode[448];
    unsigned short  EndTag;
}Fat_DBR_t;

typedef struct{
    unsigned char   dir_name[8];
    unsigned char   dir_ext[3];
    unsigned char   dir_attr;
    unsigned char   reserved[10];
    unsigned short  dir_wrttime;
    unsigned short  dir_wrtdate;
    unsigned short  dir_fstclus;
    unsigned int    dir_filesize;
}Fat_Dir_t;

#pragma apop()

void FatAnalysis(const char *filename)
{
    int m = 0;
    FILE *fp = NULL;
    unsigned char buf[512] = {0};
    Fat_DBR_t dbr;

    fp = fopen(filename, "rb");
    if(!fp){
        printf("Read '%s' Faile!\n", filename);
        goto done;
    }

    // read DBR
    printf("sizeof(Fat16_DBR_t)=%d\n", sizeof(Fat_DBR_t));
    memset(&dbr, 0, sizeof(dbr));
    m = fread(&dbr, sizeof(Fat_DBR_t), 1, fp);
    if(m != 1){
        printf("m1=%d\n", m);
        goto done;
    }

    printf("OEMName(厂商名):%s\n", dbr.OEMName);
    printf("BytsPerSec(每扇区字节数):%d\n", dbr.Bpb.BytsPerSec);
    printf("SecPerClus(每簇扇区数):%d\n", dbr.Bpb.SecPerClus);
    printf("RsvdSecCnt(Boot记录占用多少扇区):%d\n", dbr.Bpb.RsvdSecCnt);
    printf("NumFATs(共有多少FAT表):%d\n", dbr.Bpb.NumFATs);
    printf("RootEntCnt(根目录文件数最大值):%d\n", dbr.Bpb.RootEntCnt);
    printf("TotSec16(扇区总数):%d\n", dbr.Bpb.TotSec16);
    printf("Media(介质描述符):%x\n", dbr.Bpb.Media);
    printf("FATSz16(每个Fat扇区数):%d\n", dbr.Bpb.FATSz16);
    printf("SecPerTrk(每磁道扇区数):%d\n", dbr.Bpb.SecPerTrk);
    printf("NumHeads(磁头数,面数):%d\n", dbr.Bpb.NumHeads);
    printf("HiddSec(隐藏扇区数):%d\n", dbr.Bpb.HiddSec);
    printf("TotSec32(扇区数):%d\n", dbr.Bpb.TotSec32);
    printf("DrvNum(中断13的驱动器号):%d\n", dbr.DrvNum);
    printf("BootSig(扩展引导标记29h):%xh\n", dbr.BootSig);
    printf("VolId(卷序列号):%d\n", dbr.VolId);
    printf("VolLab(卷标):%s\n", dbr.VolLab);
    printf("FileSysType(文件系统类型):%s\n", dbr.FileSysType);
//    printf("Code=%s\n", dbr.BootCode);
    printf("EndTag(0xaa55h):%x\n", dbr.EndTag);


    // fat
    for(int i=0; i> 4) & 0x0f, buf[k] & 0x0f);
                printf("cnt=0x%x, %x%x%x\n", cnt++, (buf[k+2] >> 4) & 0x0f, buf[k+2] & 0x0f, (buf[k+1] >> 4) & 0x0f);
            }
        }
    }

    // read dir
    Fat_Dir_t dir;
    int data_startclust = 1 + dbr.Bpb.FATSz16 * dbr.Bpb.NumFATs + (dbr.Bpb.RootEntCnt * 32 + 31) / dbr.Bpb.BytsPerSec;
    printf("data_startclust=%d\n", data_startclust);
    for(int i=0; i

程序效果:

一个操作系统的实现:第四篇——让操作系统走进保护模式_第7张图片

一个操作系统的实现:第四篇——让操作系统走进保护模式_第8张图片

BIOS中断:
1、显示服务(Video Service——INT 10H) 
00H —设置显示器模式  0CH —写图形象素
01H —设置光标形状   
0DH —读图形象素
02H —设置光标位置   
0EH —在Teletype模式下显示字符
03H —读取光标信息   
0FH —读取显示器模式
04H —读取光笔位置   
10H —颜色
05H —设置显示页   
11H —字体
06H、07H —初始化或滚屏   
12H —显示器的配置
08H —读光标处的字符及其属性   
13H —在Teletype模式下显示字符串
09H —在光标处按指定属性显示字符
1AH —读取/设置显示组合编码
0AH —在当前光标处显示字符   
1BH —读取功能/状态信息
0BH —设置
调色板、背景色或边框   1CH —保存/恢复显示器状态
2、直接磁盘服务(Direct Disk Service——INT 13H) 
00H —磁盘系统复位 0EH —读扇区缓冲区
01H —读取磁盘系统状态
0FH —写扇区缓冲区
02H —读扇区
10H —读取驱动器状态
03H —写扇区
11H —校准驱动器
04H —检验扇区
12H —控制器RAM诊断
05H —格式化磁道
13H —控制器驱动诊断
06H —格式化坏磁道
14H —控制器内部诊断
07H —格式化驱动器
15H —读取磁盘类型
08H —读取驱动器参数
16H —读取磁盘变化状态
09H —初始化硬盘参数
17H —设置磁盘类型
0AH —读长扇区
18H —设置格式化媒体类型
0BH —写长扇区
19H —磁头保护
0CH —查寻
1AH —格式化ESDI驱动器
0DH —硬盘系统复位

读取扇区:

;----------------------------------------------------------------------------
; 函数名: ReadSector
;----------------------------------------------------------------------------
; 作用:
;	从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中
ReadSector:
	; -----------------------------------------------------------------------
	; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)
	; -----------------------------------------------------------------------
	; 设扇区号为 x
	;                           ┌ 柱面号 = y >> 1
	;       x           ┌ 商 y ┤
	; -------------- => ┤      └ 磁头号 = y & 1
	;  每磁道扇区数     │
	;                   └ 余 z => 起始扇区号 = z + 1
	push	bp
	mov	bp, sp
	sub	esp, 2 ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]

	mov	byte [bp-2], cl
	push	bx			; 保存 bx
	mov	bl, [BPB_SecPerTrk]	; bl: 除数
	div	bl			; y 在 al 中, z 在 ah 中
	inc	ah			; z ++
	mov	cl, ah			; cl <- 起始扇区号
	mov	dh, al			; dh <- y
	shr	al, 1			; y >> 1 (y/BPB_NumHeads)
	mov	ch, al			; ch <- 柱面号
	and	dh, 1			; dh & 1 = 磁头号
	pop	bx			; 恢复 bx
	; 至此, "柱面号, 起始扇区, 磁头号" 全部得到
	mov	dl, [BS_DrvNum]		; 驱动器号 (0 表示 A 盘)
.GoOnReading:
	mov	ah, 2			; 读
	mov	al, byte [bp-2]		; 读 al 个扇区
	int	13h
	jc	.GoOnReading		; 如果读取错误 CF 会被置为 1, 
					; 这时就不停地读, 直到正确为止
	add	esp, 2
	pop	bp

	ret

由扇区号求FAT项的值:

SectorNoOfFAT1 equ 1 ; FAT1 的第一个扇区号= BPB_RsvdSecCnt

;----------------------------------------------------------------------------
; 函数名: GetFATEntry
;----------------------------------------------------------------------------
; 作用:
;	找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中
;	需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx
GetFATEntry:
	push	es
	push	bx
	push	ax
	mov	ax, BaseOfLoader; `.
	sub	ax, 0100h	;  | 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT
	mov	es, ax		; /
	pop	ax
	mov	byte [bOdd], 0
	mov	bx, 3
	mul	bx			; dx:ax = ax * 3
	mov	bx, 2
	div	bx			; dx:ax / 2  ==>  ax <- 商, dx <- 余数
	cmp	dx, 0
	jz	LABEL_EVEN
	mov	byte [bOdd], 1
LABEL_EVEN:;偶数
	; 现在 ax 中是 FATEntry 在 FAT 中的偏移量,下面来
	; 计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
	xor	dx, dx			
	mov	bx, [BPB_BytsPerSec]
	div	bx ; dx:ax / BPB_BytsPerSec
		   ;  ax <- 商 (FATEntry 所在的扇区相对于 FAT 的扇区号)
		   ;  dx <- 余数 (FATEntry 在扇区内的偏移)
	push	dx
	mov	bx, 0 ; bx <- 0 于是, es:bx = (BaseOfLoader - 100):00
	add	ax, SectorNoOfFAT1 ; 此句之后的 ax 就是 FATEntry 所在的扇区号
	mov	cl, 2
	call	ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界
			   ; 发生错误, 因为一个 FATEntry 可能跨越两个扇区
	pop	dx
	add	bx, dx
	mov	ax, [es:bx]
	cmp	byte [bOdd], 1
	jnz	LABEL_EVEN_2
	shr	ax, 4
LABEL_EVEN_2:
	and	ax, 0FFFh

LABEL_GET_FAT_ENRY_OK:

	pop	bx
	pop	es
	ret

 

你可能感兴趣的:(一个操作系统的实现:第四篇——让操作系统走进保护模式)