虚拟硬盘

bximage hd.img -q hd -mode=flat -size=80创建一个虚拟硬盘(-hd),-q表示不交互,-mode=flat连续硬盘,-size=80大小为80M。

image-20220807164329019

重新配置告诉bochs 虚拟硬盘是以hd.img来展现的,CHS柱面柱头的信息

image-20220907214704603

BIOS 运行后会遍历系统中的硬件, 并记录相关信息。其中, 硬盘数量被BIOS主动记录于 0x475 地址处。因此我们可以通过读取0x475地址的数据拿到硬盘数量信息

1
2
3
4
byte* pb = (byte*)0x475;
PrintString( "Number of Hard Disk: " );
PrintIntDec(*pb);
PrintChar('\n');

IDE

IDE 是什么?

早期,硬盘控制器和硬盘本身是分开的。软件控制硬盘控制器来读取硬盘的数据。后来,硬盘控制器和硬盘本身合二为一(Integrated Drive Electronics)。
再后来, 有了别名 ATA, 然后是ATA / IDE。
最初的硬盘是用并口传输数据, 所以 IDE 常指代并口硬盘接口。并口由于太慢了因此后来有了串口硬盘接口, 名为: SerialATA, 缩写: SATA。 。 。

硬盘控制器

怎么具体地读写硬盘?
也就是通过固定I/O端口对硬盘控制器进行操作就可以读写硬盘了
而操作的本质是读写硬盘控制器中的寄存器

命令块寄存器 (Command Block Register)

控制块寄存器 (Control Block Register)

primary和secondary对应主盘和从盘

image-20220807165303967

注意同一个端口(例如主盘的0x1F7)可以读也可以写,但是可能读写操作意义是不一样的。假设操作硬盘时有错误信息可以通过0x1F1读取错误信息。本OS只有一块硬盘因此只操作Primary

针对端口0x1F7判断硬盘是否忙碌,设备是否就绪,数据是否就绪:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static uint IsBusy()
{
uint ret = 0;
uint i = 0;

//500次 读取状态寄存器并且判断最高位是不是1
while( (i < 500) && (ret = (ReadPort(REG_STATUS) & STATUS_BSY)) )
{
i++;
}

return ret;
}

static uint IsDevReady()
{ //读取状态寄存器且判断第DRDY位是不是0
return !(ReadPort(REG_STATUS) & STATUS_DRDY);
}

static uint IsDataReady()
{ //读取状态寄存器且判断第DRQ位是不是1
return ReadPort(REG_STATUS) & STATUS_DRQ;
}

LBA Register

LBA 即: Logical Block Addressing, 逻辑块地址, 是一种线性寻址的方式 ( 0- n ) 。LBA存储扇区编号

目前绝大多数硬盘支持 LBA 扇区寻址方式
通过 3.5 个寄存器(即: 28 位) 读写硬盘扇区

  • LBA Low: 保存逻辑扇区地址的低8 位
  • LBAMid: 保存逻辑扇区地址的中8 位
  • LBAHigh: 保存逻辑扇区地址的高8 位

Device Register

LBA还有4位存储于Device Register的低4位,如图所示

image-20220807171337101

1
2
3
4
5
6
//构建device寄存器的值
static uint MakeDevRegVal(uint si)
{
//高四位1110 低四位HS3HS2HS1HS0
return 0xE0 | ((si >> 24) & 0x0F);
}

Command Register

  • 获取硬盘信息:0xEC
  • 读取扇区数据:0x20
  • 数据写入扇区:0x30

status Register

image-20220807171554587

硬盘读写

硬盘以扇区为基本读写单位

  • 读写数据时需要计算目标扇区的逻辑地址 (LBA)
  • 确定逻辑地址后, 读取扇区数据到内存,每次先把512字节读到内存中,再从512字节中寻找需要的数据
    读: 从读取的扇区中拷贝目标数据(拷贝到内存中)
    写: 数据写入读取的扇区中,之后将扇区数据写回硬盘

硬盘数据端口 (0x1F0) 的读写需要以双字节为单位(WORD)

  • cld 清空方向标志位,向高地址方向增加地址值
  • insw 端口操作,从DX指出的外设端口输入**一个字(双字节)**到由ES: DI指定的存储器中
  • outsw 端口操作,向端口写入一个字
  • rep 重复指令, 重复次数由 ecx寄存器指定

端口写入

从 edx 指定的端口中读取 2n 个字节并存入 edi 指向的地址(传入buf)中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
;
; void ReadPortW(ushort port, ushort* buf, uint n)
;
ReadPortW:
push ebp
mov ebp, esp

mov edx, [ebp + 8] ; port
mov edi, [ebp + 12] ; buf
mov ecx, [ebp + 16] ; n

cld
rep insw

nop
nop
nop

leave

ret

端口读出:将 esi 指向的 2n 个字节写入 edx 指定的端口中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
;
; void WritePortW(ushort port, ushort* buf, uint n)
;
WritePortW:
push ebp
mov ebp, esp

mov edx, [ebp + 8] ; port
mov esi, [ebp + 12] ; buf
mov ecx, [ebp + 16] ; n

cld
rep outsw ; 反复向端口写入一个字

nop
nop
nop

leave

ret

逻辑块地址结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct
{
byte lbaLow;
byte lbaMid;
byte lbaHigh; //LBA低24位
byte device; //LBA高4位
byte command; //读/写
} HDRegValue;
static HDRegValue MakeRegVals(uint si, uint action)
{
HDRegValue ret = {0};
//指定LBA地址
ret.lbaLow = si & 0xFF;
ret.lbaMid = (si >> 8) & 0xFF;
ret.lbaHigh = (si >> 16) & 0xFF;
ret.device = MakeDevRegVal(si);
ret.command = action;

return ret;
}

硬盘操作基本步骤

  1. 往命令块寄存器写数据(操作哪块硬盘,读取哪块扇区,如何读取)

  2. 往控制块寄存器写数据()

  3. 从数据端口中读取数据readportw来读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#define REG_DEV_CTRL  0x3F6
#define REG_DATA 0x1F0
#define REG_FEATURES 0x1F1
#define REG_ERROR 0x1F1
#define REG_NSECTOR 0x1F2
#define REG_LBA_LOW 0x1F3
#define REG_LBA_MID 0x1F4
#define REG_LBA_HIGH 0x1F5
#define REG_DEVICE 0x1F6
#define REG_STATUS 0x1F7
#define REG_COMMAND 0x1F7

//设置命令块寄存器
static void WritePorts(HDRegValue hdrv)
{ //命令块寄存器
WritePort(REG_FEATURES, 0); //
WritePort(REG_NSECTOR, 1); //每次只操作一个扇区
WritePort(REG_LBA_LOW, hdrv.lbaLow); //LBA的地址
WritePort(REG_LBA_MID, hdrv.lbaMid);
WritePort(REG_LBA_HIGH, hdrv.lbaHigh);
WritePort(REG_DEVICE, hdrv.device); //操作哪块硬盘
WritePort(REG_COMMAND, hdrv.command); //指定命令块寄存器值 读/写
//控制块寄存器
WritePort(REG_DEV_CTRL, 0); //控制块命令字
}

uint HDRawRead(uint si, byte* buf)
{
uint ret = 0;

if( (si < HDRawSectors()) && buf && !IsBusy() )
{
//指定 LBA地址 和 读/写操作
HDRegValue hdrv = MakeRegVals(si, ATA_READ);
//设置好命令块寄存器和控制块寄存器
WritePorts(hdrv);

if( ret = (!IsBusy() && IsDataReady()) )
{
ushort* data = (ushort*)buf;
//从insw读取数据
ReadPortW(REG_DATA, data, SECT_SIZE >> 1);
}
}

return ret;
}

uint HDRawWrite(uint si, byte* buf)
{
uint ret = 0;

if( (si < HDRawSectors()) && buf && !IsBusy() )
{
HDRegValue hdrv = MakeRegVals(si, ATA_WRITE);

WritePorts(hdrv);

if( ret = (!IsBusy() && IsDataReady()) )
{
ushort* data = (ushort*)buf;

WritePortW(REG_DATA, data, SECT_SIZE >> 1);
}
}

return ret;
}

获取扇区总数

0号扇区的60-61字的位置记录了扇区总数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//获取扇区总数
uint HDRawSectors()
{
static uint ret = -1;

if( (ret == -1) && IsDevReady() )
{
//先指定寄存器的值
HDRegValue hdrv = MakeRegVals(0, ATA_IDENTIFY);//0号扇区,0xEC操作 获取硬盘信息
byte* buf = Malloc(SECT_SIZE);
//insw对各个端口发送数据
WritePorts(hdrv);

if( !IsBusy() && IsDataReady() && buf )
{
ushort* data = (ushort*)buf;
//insw 读取硬盘数据
ReadPortW(REG_DATA, data, SECT_SIZE >> 1);

ret = (data[61] << 16) | (data[60]);//硬盘固定位置存储的信息,拿到扇区数量
}

Free(buf);
}

return ret;
}

文件系统概要设计

对于硬盘而言不知道所谓的文件系统,硬盘只有一个个的扇区,文件系统时操作系统的概念,文件是有逻辑关联的数据集合,并且数据之间有存储的先后关系

image-20220810001547973

整个硬盘最大扇区数量为max,第0号扇区可以存储引导程序和文件系统基本信息。1号扇区存储根目录区。
扇区分配表记录扇区之间的逻辑关系,记录扇区是否可用。注意扇区分配表和文件数据区一一对应

扇区分配表

比如设置文件test.txtsecBegin = addr_C。扇区分配表的addr_C存储了下一个扇区是O

image-20220810001629196

扇区分配表组织成不同的链表每个链表代表一个文件,同时的未使用的扇区也组织成一个空闲链表。
当一个文件需要更多扇区时,从空闲链表头部中取出一个链表头即可

文件管理的过程可看作扇区在不同链表中移动的过程

注意扇区分配表的大小,已知的硬盘扇区共有MAX个,那么512/4*n = max-2-n 可以求出扇区分配表占用扇区数目n = (max-2)/129(向上取整)

引导区

1
2
3
4
5
6
7
8
9
10
//存储于0号引导区
typedef struct
{
byte forJmp[4]; //预留给jmp指令使用,万一0号扇区需要存储引导程序呢
char magic[32]; //存储字符串标识现在是什么文件系统
uint sctNum; //多少扇区可以使用
uint mapSize; //扇区分配表的大小
uint freeNum; //空闲链表长度
uint freeBegin; //空闲链表开始
} FSHeader;

根目录区

1
2
3
4
5
6
7
8
//存储于1号根目录区
typedef struct
{
char magic[32]; //"ROOT"
uint sctBegin; //根目录的开始扇区,本OS初始化时设置为1
uint sctNum; //占用多少个扇区,本OS初始化设置为0非法值,表示还没有FileEntry
uint lastBytes; //最后一个扇区用了多少字节
} FSRoot;

根目录区也是一个文件,后面随着新建的文件越来越多,FSRoot的sctNum也会增加,FSRoot这条链表记录了FileEntry(文件的基本信息),然后通过FileEntry可以获得各个文件自己的链表

1
2
3
4
5
6
7
8
9
10
11
typedef struct
{
char name[32]; //文件名
uint sctBegin; //文件起始扇区
uint sctNum; //多少个扇区
uint lastBytes; //
uint type; //是文件还是目录 存储的是用户数据还是文件相关的数据
uint inSctIdx; //此FileEntry位于硬盘的哪一个扇区
uint inSctOff; //此FileEntry位于扇区内的偏移位置
uint reserved[2]; //预留
} FileEntry;

硬盘格式化操作

即建立引导区,根目录区和扇区分配表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
uint FSFormat()
{
FSHeader* header = (FSHeader*)Malloc(SECT_SIZE); //引导区
FSRoot* root = (FSRoot*)Malloc(SECT_SIZE); //根目录区
uint* p = (uint*)Malloc(MAP_ITEM_CNT * sizeof(uint)); //操作扇区分配表的每一个分配单元
uint ret = 0;

if( header && root && p )
{
uint i = 0;
uint j = 0;
uint current = 0;

//给引导区的内容赋值
StrCpy(header->magic, FS_MAGIC, sizeof(header->magic)-1);
header->sctNum = HDRawSectors();
header->mapSize = (header->sctNum - FIXED_SCT_SIZE) / 129 + !!((header->sctNum - FIXED_SCT_SIZE) % 129);9
header->freeNum = header->sctNum - header->mapSize - FIXED_SCT_SIZE;
header->freeBegin = FIXED_SCT_SIZE + header->mapSize;
//注意一定要写回硬盘
ret = HDRawWrite(HEADER_SCT_IDX, (byte*)header);

//给根目录区的相关成员赋值
StrCpy(root->magic, ROOT_MAGIC, sizeof(root->magic)-1);
root->sctNum = 0; //根目录占用的扇区数目为0
root->sctBegin = SCT_END_FLAG; //标记为非法扇区
root->lastBytes = SECT_SIZE; //ROOT此扇区标记512字节都用完了
//注意一定要写回硬盘
ret = ret && HDRawWrite(ROOT_SCT_IDX, (byte*)root);

//针对于扇区分配表(2~n 的扇区)的每个分配单元赋值
for(i=0; ret && (i<header->mapSize) && (current<header->freeNum); i++)
{
//每个扇区的128个的每个分配单元赋值
for(j=0; j<MAP_ITEM_CNT; j++)
{
uint* pInt = AddrOff(p, j);

if( current < header->freeNum )
{ //分配单元组织成链表
*pInt = current + 1;

if( current == (header->freeNum - 1) )
{ //最后一个设置成SCT_END_FLAG表示是链表尾部
*pInt = SCT_END_FLAG;
}

current++;
}
else
{
break;
}
}
//写回硬盘
ret = ret && HDRawWrite(i + FIXED_SCT_SIZE, (byte*)p);
}
}

Free(header);
Free(root);
Free(p);

return ret;
}

创建文件

image-20220810003851627

如何根据扇区号获取处于扇区分配表中的位置?

扇区操作是一种外存操作,需要仔细计算出位于哪一个扇区,扇区内偏移地址是多少,扇区管理时使用相对扇区地址,扇区读写使用绝对地址

1
2
3
4
扇区相对地址:offset 	= si - mapsize - 2			//具体外存的扇区相对位置  mapSize 根目录区大小
si = offset + mapsize + 2 //具体外存的扇区位置
位于哪个扇区:sctOff = offset/MAP_ITEM_CNT //扇区分配表中 offset/128
扇区内的偏移:idxOff = offset%MAP_ITEM_CNT //扇区分配表中 offset%128

image-20220813135403193

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
typedef struct
{
uint* pSct; //指向对应管理单元所在扇区
uint sctIdx; //原始绝对扇区号
uint sctOff; //对应的分配单元(扇区分配表中)的在哪个扇区
uint idxOff; //扇区中偏移位置
} MapPos;
//绝对地址转为相对地址
static MapPos FindInMap(uint si)
{
MapPos ret = {0};
FSHeader* header = (si != SCT_END_FLAG) ? ReadSector(HEADER_SCT_IDX) : NULL;

if( header )
{
//绝对地址转换位相对地址
uint offset = si - header->mapSize - FIXED_SCT_SIZE;
uint sctOff = offset / MAP_ITEM_CNT;
uint idxOff = offset % MAP_ITEM_CNT;
uint* ps = ReadSector(sctOff + FIXED_SCT_SIZE);

if( ps )
{
ret.pSct = ps;
ret.sctIdx = si;
ret.sctOff = sctOff;
ret.idxOff = idxOff;
}
}

Free(header);
return ret;
}

扇区分配函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//返回一个空闲的扇区号
static uint AllocSector()
{
uint ret = SCT_END_FLAG;
FSHeader* header = ReadSector(HEADER_SCT_IDX);

if( header && (header->freeBegin != SCT_END_FLAG) )
{
//去根目录区拿出链表头,找出分配单元的具体位置
MapPos mp = FindInMap(header->freeBegin);

if( mp.pSct )
{
uint* pInt = AddrOff(mp.pSct, mp.idxOff); //取出分配单元
uint next = *pInt; //下一个分配单元的位置
uint flag = 1;

ret = header->freeBegin;
//更新header信息 空闲扇区位置 空闲扇区数量
header->freeBegin = next + FIXED_SCT_SIZE + header->mapSize;
header->freeNum--;
//当前分配单元标记为不可用
*pInt = SCT_END_FLAG;
//外存数据更新
flag = flag && HDRawWrite(HEADER_SCT_IDX, (byte*)header);
flag = flag && HDRawWrite(mp.sctOff + FIXED_SCT_SIZE, (byte*)mp.pSct);

if( !flag )
{
ret = SCT_END_FLAG;
}
}

Free(mp.pSct);
}

Free(header);

return ret;
}

扇区归还函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static uint FreeSector(uint si)
{
FSHeader* header = (si != SCT_END_FLAG) ? ReadSector(HEADER_SCT_IDX) : NULL;
uint ret = 0;

if( header )
{
//获取扇区分配单元的信息
MapPos mp = FindInMap(si);

if( mp.pSct )
{
uint* pInt = AddrOff(mp.pSct, mp.idxOff);
//此扇区分配管理单元插入到空闲扇区的头部,因此赋值为原先的头节点的扇区位置
*pInt = header->freeBegin - FIXED_SCT_SIZE - header->mapSize;

header->freeBegin = si;
header->freeNum++;

ret = HDRawWrite(HEADER_SCT_IDX, (byte*)header) &&
HDRawWrite(mp.sctOff + FIXED_SCT_SIZE, (byte*)mp.pSct);
}

Free(mp.pSct);
}

Free(header);

return ret;
}

获取下一个分配单元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//获取当前扇区的后继扇区 通过读取扇区分配表可以知道后继节点
static uint NextSector(uint si)
{
FSHeader* header = (si != SCT_END_FLAG) ? ReadSector(HEADER_SCT_IDX) : NULL;
uint ret = SCT_END_FLAG;

if( header )
{ //获取分配单元位置
MapPos mp = FindInMap(si);

if( mp.pSct )
{
//后继扇区的相对位置
uint* pInt = AddrOff(mp.pSct, mp.idxOff);

if( *pInt != SCT_END_FLAG )
{
ret = *pInt + header->mapSize + FIXED_SCT_SIZE;
}
}

Free(mp.pSct);
}

Free(header);

return ret;
}

辅助功能函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//查找链表的最后一个扇区, 空闲链表、已经分配的链表
static uint FindLast(uint sctBegin)
{
uint ret = SCT_END_FLAG;
uint next = sctBegin;

while( next != SCT_END_FLAG )
{
ret = next;
next = NextSector(next);
}

return ret;
}

//查找链表的前一个节点
static uint FindPrev(uint sctBegin, uint si)
{
uint ret = SCT_END_FLAG;
uint next = sctBegin;

while( (next != SCT_END_FLAG) && (next != si) )
{
ret = next;
next = NextSector(next);
}

if( next == SCT_END_FLAG )
{
ret = SCT_END_FLAG;
}

return ret;
}

//查找链表当中的第n号扇区
static uint FindIndex(uint sctBegin, uint idx)
{
uint ret = sctBegin;
uint i = 0;

while( (i < idx) && (ret != SCT_END_FLAG) )
{
ret = NextSector(ret);

i++;
}

return ret;
}

//标记扇区目标扇区不可用 分配管理单元设置为SCT_END_FLAG(-1)
static uint MarkSector(uint si)
{
uint ret = (si == SCT_END_FLAG) ? 1 : 0;
MapPos mp = FindInMap(si);

if( mp.pSct )
{
uint *pInt = AddrOff(mp.pSct, mp.idxOff);

*pInt = SCT_END_FLAG;

ret = HDRawWrite(mp.sctOff + FIXED_SCT_SIZE, (byte*)mp.pSct);
}

Free(mp.pSct);

return ret;
}

创建文件流程-构建FileEntry

根目录存储的时FileEntry类型的值,每个FileEntry值表示一个硬盘的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef struct
{
char magic[32]; //"ROOT"
uint sctBegin; //根目录的开始扇区,本OS初始化时设置为1
uint sctNum; //占用多少个扇区,本OS初始化设置为0非法值,表示还没有FileEntry
uint lastBytes; //最后一个扇区用了多少字节
} FSRoot;
//与FSRoot构成继承关系
typedef struct
{
char name[32]; //文件名
uint sctBegin; //文件起始扇区
uint sctNum; //多少个扇区
uint lastBytes; //
uint type; //是文件还是目录 存储的是用户数据还是文件相关的数据
uint inSctIdx; //此FileEntry位于硬盘的哪一个扇区
uint inSctOff; //此FileEntry位于的扇区内偏移位置
uint reserved[2]; //预留
} FileEntry;

image-20220813154620749

如图所示每个fileEntry对应一个文件。一个扇区可以存储8个FileEntry(示意图上3个有点问题)。根目录的数据链表存储了所有的fileEntry值(随着文件数目越来越多,根目录区的FSRoot数据结构成员sctNum会增加)

  1. 是否有足够空间写入数据,如果空间不足,(比如初始化时把root的lastbytes赋值为512,属于空间不足的情况)因此会找另外一个新的空扇区存放下一个,并且设置sctNum++和lastbyte为0
  2. 在ROOT链表尾部的扇区存放FileEntry并且赋值,最后写入硬盘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
static void AddToLast(uint sctBegin, uint si)
{
//找到此文件的last扇区
uint last = FindLast(sctBegin);

if( last != SCT_END_FLAG )
{
//管理单元
MapPos lmp = FindInMap(last);
MapPos smp = FindInMap(si);

if( lmp.pSct && smp.pSct )
{
//两个管理单元位于同一个扇区,只需要写一次硬盘即可
if( lmp.sctOff == smp.sctOff )
{
//拿到last管理单元
uint* pInt = AddrOff(lmp.pSct, lmp.idxOff);
//si的相对地址赋给 last管理单元
*pInt = lmp.sctOff * MAP_ITEM_CNT + smp.idxOff;
//拿到尾部si对应的管理单元
pInt = AddrOff(lmp.pSct, smp.idxOff);
//赋值
*pInt = SCT_END_FLAG;
//仅仅写一次即可
HDRawWrite(lmp.sctOff + FIXED_SCT_SIZE, (byte*)lmp.pSct);
}
else
{
//拿到last管理单元
uint* pInt = AddrOff(lmp.pSct, lmp.idxOff);
//si的相对地址赋值给 last管理单元
*pInt = smp.sctOff * MAP_ITEM_CNT + smp.idxOff;

pInt = AddrOff(smp.pSct, smp.idxOff);

*pInt = SCT_END_FLAG;
//操作了两个扇区需要写两次硬盘
HDRawWrite(lmp.sctOff + FIXED_SCT_SIZE, (byte*)lmp.pSct);
HDRawWrite(smp.sctOff + FIXED_SCT_SIZE, (byte*)smp.pSct);
}
}

Free(lmp.pSct);
Free(smp.pSct);
}
}

static uint CheckStorage(FSRoot* fe)
{
uint ret = 0;
//最后一个扇区是512字节需要扩展容量
if( fe->lastBytes == SECT_SIZE )
{
uint si = AllocSector();

if( si != SCT_END_FLAG )
{
//当前数据链表是空链表,则新申请的扇区是链表第一个节点
if( fe->sctBegin == SCT_END_FLAG )
{
fe->sctBegin = si;
}
else//否则加入到尾部
{
AddToLast(fe->sctBegin, si);
}

fe->sctNum++;
fe->lastBytes = 0;

ret = 1;
}
}

return ret;
}

static uint CreateFileEntry(const char* name, uint sctBegin, uint lastBytes)
{
uint ret = 0;
uint last = FindLast(sctBegin); //链表最后一个扇区
FileEntry* feBase = NULL; //并且将扇区从硬盘读入内存

if( (last != SCT_END_FLAG) && (feBase = (FileEntry*)ReadSector(last)) )
{
//要在目标扇区写入新的FileEntry值
//偏移位置做除法即可得到,跳过前面已经写好的fileEntry
uint offset = lastBytes / FE_BYTES;
FileEntry* fe = AddrOff(feBase, offset);
//写入数据
StrCpy(fe->name, name, sizeof(fe->name) - 1);

fe->type = 0;
fe->sctBegin = SCT_END_FLAG;
fe->sctNum = 0;
fe->inSctIdx = last;
fe->inSctOff = offset;
fe->lastBytes = SECT_SIZE;
//新的FileEntry已经写入硬盘
ret = HDRawWrite(last, (byte*)feBase);
}

Free(feBase);

return ret;
}

static uint CreateInRoot(const char* name)
{
FSRoot* root = (FSRoot*)ReadSector(ROOT_SCT_IDX);
uint ret = 0;

if( root )
{
//确保root空间足够
CheckStorage(root);
//创建一个新文件
if( CreateFileEntry(name, root->sctBegin, root->lastBytes) )
{
root->lastBytes += FE_BYTES;

ret = HDRawWrite(ROOT_SCT_IDX, (byte*)root);
}
}

Free(root);

return ret;
}

删除文件

判断文件是否存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
static FileEntry* FindFileEntry(const char* name, uint sctBegin, uint sctNum, uint lastBytes)
{
FileEntry* ret = NULL;
uint next = sctBegin;
uint i = 0;
//遍历数据链表,前n-1个扇区是完全利用了
for(i=0; i<(sctNum-1); i++)
{
FileEntry* feBase = (FileEntry*)ReadSector(next);

if( feBase )
{
//查找的名字,位置,次数
ret = FindInSector(name, feBase, FE_ITEM_CNT);
}

Free(feBase);

if( !ret )
{
next = NextSector(next);
}
else
{
break;
}
}
//最后一个扇区找,最后一个扇区不一定充分利用,计算一下找了多少次
if( !ret )
{
uint cnt = lastBytes / FE_BYTES;
FileEntry* feBase = (FileEntry*)ReadSector(next);

if( feBase )
{
ret = FindInSector(name, feBase, cnt);
}

Free(feBase);
}

return ret;
}

static FileEntry* FindInRoot(const char* name)
{
FileEntry* ret = NULL;
//root读取到内存中
FSRoot* root = (FSRoot*)ReadSector(ROOT_SCT_IDX);

if( root && root->sctNum )
{
ret = FindFileEntry(name, root->sctBegin, root->sctNum, root->lastBytes);
}

Free(root);

return ret;
}

uint FExisted(const char* fn)
{
uint ret = FS_FAILED;

if( fn )
{
FileEntry* fe = FindInRoot(fn);

ret = fe ? FS_EXISTED : FS_NONEXISTED;

Free(fe);
}

return ret;
}

删除文件流程

  • 根据文件名在根目录的数据链表中查找FileEntry值。从数据链表中删除FileEntry值
  • 注意需要判断目标文件是否已经打开,只有在关闭状态才能被删除
  • 将数据链表的最后一个FileEntry移动到被删除的FileEntry位置(注意如图所示FileEntry5inSctIdx,inSctOff)处并且更新lastbyte值的大小(lastbytes大小减小64,注意如果lastbytes大小为64,减去后变为0,那么还需要把整个扇区给归还AdjustStorage到空闲链表)

image-20220813170040854

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
static uint FreeFile(uint sctBegin)
{
uint slider = sctBegin;
uint ret = 0;
//将此FileEntry开始到结束的扇区全都释放掉
while( slider != SCT_END_FLAG )
{
uint next = NextSector(slider);

ret += FreeSector(slider);

slider = next;
}
//返回成功释放的总数
return ret;
}

static void MoveFileEntry(FileEntry* dst, FileEntry* src)
{
uint inSctIdx = dst->inSctIdx;
uint inSctOff = dst->inSctOff;

*dst = *src;

dst->inSctIdx = inSctIdx; //此FileEntry位于硬盘的哪一个扇区
dst->inSctOff = inSctOff; //此FileEntry位于扇区的偏移位置
}

static uint AdjustStorage(FSRoot* fe)
{
uint ret = 0;

if( !fe->lastBytes ) //最后一个扇区是否完全空闲
{ //查找最后一个扇区和倒数第二个扇区
uint last = FindLast(fe->sctBegin);
uint prev = FindPrev(fe->sctBegin, last);
//释放最后一个扇区并且标记倒数第二个扇区为最后一个扇区
if( FreeSector(last) && MarkSector(prev) )
{
fe->sctNum--;
fe->lastBytes = SECT_SIZE;

if( !fe->sctNum )
{
fe->sctBegin = SCT_END_FLAG;
}

ret = 1;
}
}

return ret;
}

//数据链表中要抹除的最后n个字节,对FileEntry的lastbyte操作
static uint EraseLast(FSRoot* fe, uint bytes)
{
uint ret = 0;

while( fe->sctNum && bytes )
{
//小于最后扇区数据,直接抹除
if( bytes < fe->lastBytes )
{
fe->lastBytes -= bytes;

ret += bytes;

bytes = 0;
}
else
{
//最后一个扇区的数据要全部抹除
bytes -= fe->lastBytes;

ret += fe->lastBytes;

fe->lastBytes = 0;
//将最后一个扇区归还到空闲扇区区
AdjustStorage(fe);
}
}

return ret;
}

static uint DeleteInRoot(const char* name)
{
FSRoot* root = (FSRoot*)ReadSector(ROOT_SCT_IDX);
//查找到对应的FileEntry
FileEntry* fe = FindInRoot(name);
uint ret = 0;

if( root && fe )
{
//查找最后一个扇区并且将最后一个扇区读取到内存中
uint last = FindLast(root->sctBegin);
//目标FileEntry所在的扇区也要读取到内存中
FileEntry* feTarget = ReadSector(fe->inSctIdx);
//将最后一个扇区读到内存中
FileEntry* feLast = (last != SCT_END_FLAG) ? ReadSector(last) : NULL;

if( feTarget && feLast )
{
//定位最后一个FileEntry
uint lastOff = root->lastBytes / FE_BYTES - 1;
//读取最后一个扇区的最后一个FileEntry和目标FileEntry
FileEntry* lastItem = AddrOff(feLast, lastOff);
FileEntry* targetItem = AddrOff(feTarget, fe->inSctOff);
//将数据链表的每一个扇区释放扇区,本质是链表遍历
FreeFile(targetItem->sctBegin);
//移动FileEntry的值
MoveFileEntry(targetItem, lastItem);
//FileEntry对应的数据链表中要抹除的最后n个字节,是对FileEntry的lastbyte操作
EraseLast(root, FE_BYTES);
//一定要写回硬盘
ret = HDRawWrite(ROOT_SCT_IDX, (byte*)root) &&
HDRawWrite(fe->inSctIdx, (byte*)feTarget);
}

Free(feTarget);
Free(feLast);
}

Free(root);
Free(fe);

return ret;
}

重命名根目录中的文件

  • 判断目标文件是否打开,只有关闭状态才能重命名
  • 根据名字查找目标FileEntry的位置
  • 查找新名字是否已经被占用,未被占用才可修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static uint FlushFileEntry(FileEntry* fe)
{
uint ret = 0;
//将FileEntry读取到内存中
FileEntry* feBase = ReadSector(fe->inSctIdx);
FileEntry* feInSct = AddrOff(feBase, fe->inSctOff);

*feInSct = *fe;
//修改并且写回硬盘
ret = HDRawWrite(fe->inSctIdx, (byte*)feBase);

Free(feBase);

return ret;
}

uint FRename(const char* ofn, const char* nfn)
{
uint ret = FS_FAILED;
//未被打开且新名字不为空
if( ofn && !IsOpened(ofn) && nfn )
{
FileEntry* ofe = FindInRoot(ofn);
FileEntry* nfe = FindInRoot(nfn);
//目标文件存在且新名字文件名也没被占用
if( ofe && !nfe )
{ //拷贝名字
StrCpy(ofe->name, nfn, sizeof(ofe->name) - 1);
//写回硬盘
if( FlushFileEntry(ofe) )
{
ret = FS_SUCCEED;
}
}

Free(ofe);
Free(nfe);
}

return ret;
}

读写文件

文件描述符

用于描述已经打开的文件,文件的信息FileEntry,文件读写位置等信息

1
2
3
4
5
6
7
8
9
10
11
static List gFDList = {0};  //全局已经打开的文件描述符链表

typedef struct
{
ListNode head; //链表--文件描述符最后也要构成一个链表
FileEntry fe; //FileEntry必备
uint objIdx; //文件读写位置-哪一个扇区
uint offset; //文件读写位置-扇区内偏移位置
uint changed; //标志文件已经改变,关闭文件时changed若为1则将cache写入到硬盘
byte cache[SECT_SIZE]; //文件缓冲区-512字节大小
} FileDesc;

文件打开关闭函数

会将文件描述符加入/删除全局文件描述符链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
static uint IsFDValid(FileDesc* fd)
{
uint ret = 0;
ListNode* pos = NULL;

List_ForEach(&gFDList, pos)
{
if( IsEqual(pos, fd) )
{
ret = 1;
break;
}
}

return ret;
}

static uint FlushCache(FileDesc* fd)
{
uint ret = 1;

if( fd->changed )
{
uint sctIdx = FindIndex(fd->fe.sctBegin, fd->objIdx);

ret = 0;

if( (sctIdx != SCT_END_FLAG) && (ret = HDRawWrite(sctIdx, fd->cache)) )
{
fd->changed = 0;
}
}

return ret;
}

static uint ToFlush(FileDesc* fd)
{
return FlushCache(fd) && FlushFileEntry(&fd->fe);
}

uint FOpen(const char *fn)
{
FileDesc* ret = NULL;
//文件名不为空且文件未被打开
if( fn && !IsOpened(fn) )
{
FileEntry* fe = NULL;
//分配文件描述符并且获取FileEntry
ret = (FileDesc*)Malloc(FD_BYTES);
fe = ret ? FindInRoot(fn) : NULL;

if( ret && fe )
{
ret->fe = *fe;
ret->objIdx = SCT_END_FLAG;
ret->offset = SECT_SIZE;
ret->changed = 0;

List_Add(&gFDList, (ListNode*)ret);
}

Free(fe);
}

return (uint)ret;
}

void FClose(uint fd)
{
FileDesc* pf = (FileDesc*)fd;
//文件描述符是否合法
if( IsFDValid(pf) )
{ //写到硬盘上
ToFlush(pf);
//链表删除
List_DelNode((ListNode*)pf);

Free(pf);
}
}

文件数据写入函数

写入数据的时候,文件数据链表可能变长。写入数据先写入缓冲区中,当缓冲区写满了之后,要将缓冲区写入扇区并且将下一个扇区的内容读取到缓冲区中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//链表中的第idx个扇区数据读入缓冲区中
static uint ReadToCache(FileDesc* fd, uint idx)
{
uint ret = 0;

if( idx < fd->fe.sctNum )
{
uint sctIdx = FindIndex(fd->fe.sctBegin, idx);

ToFlush(fd);

if( (sctIdx != SCT_END_FLAG) && (ret = HDRawRead(sctIdx, fd->cache)) )
{
fd->objIdx = idx;
fd->offset = 0;
fd->changed = 0;
}
}

return ret;
}

static uint PrepareCache(FileDesc* fd, uint objIdx)
{
//文件是否需要扩容
CheckStorage(&fd->fe);
//指定扇区数据读入缓冲区中
return ReadToCache(fd, objIdx);
}

static uint CopyToCache(FileDesc* fd, byte* buf, uint len)
{
uint ret = -1;

if( fd->objIdx != SCT_END_FLAG )
{ //计算能向缓冲区写入的最大数据量
uint n = SECT_SIZE - fd->offset;
byte* p = AddrOff(fd->cache, fd->offset);

n = (n < len) ? n : len;

MemCpy(p, buf, n);

fd->offset += n;
fd->changed = 1;
//更新数据链表的最后一个扇区的数据总量,如果offset超过lastbyte更新lastbyte
if( ((fd->fe.sctNum - 1) == fd->objIdx) && (fd->fe.lastBytes < fd->offset) )
{
fd->fe.lastBytes = fd->offset;
}

ret = n;
}

return ret;
}

static uint ToWrite(FileDesc* fd, byte* buf, uint len)
{
uint ret = 1;
uint i = 0;
uint n = 0;

while( (i < len) && ret )
{
//p初始时指向buf开始位置
byte* p = AddrOff(buf, i);

if( fd->offset == SECT_SIZE )
{
//文件要写入的扇区内offset=512时,需要扩容一个新扇区,读取文件数据链表的下一个扇区
ret = PrepareCache(fd, fd->objIdx + 1);
}

if( ret )
{ //数据写入缓冲区
n = CopyToCache(fd, p, len - i);

i += n;
}
}

ret = i;

return ret;
}

uint FWrite(uint fd, byte* buf, uint len)
{
uint ret = -1;

if( IsFDValid((FileDesc*)fd) && buf )
{
ret = ToWrite((FileDesc*)fd, buf, len);
}

return ret;
}

文件中读数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//文件读写指针的位置 
static uint GetFilePos(FileDesc* fd)
{
uint ret = 0;

if( fd->objIdx != SCT_END_FLAG )
{
ret = fd->objIdx * SECT_SIZE + fd->offset;
}

return ret;
}

static uint CopyFromCache(FileDesc* fd, byte* buf, uint len)
{
uint ret = (fd->objIdx != SCT_END_FLAG);

if( ret )
{ //计算当前缓冲区可提供的最大数据量以及数据起始位置
uint n = SECT_SIZE - fd->offset;
byte* p = AddrOff(fd->cache, fd->offset);

n = (n < len) ? n : len;

MemCpy(buf, p, n);

fd->offset += n;

ret = n;
}

return ret;
}

static uint ToRead(FileDesc* fd, byte* buf, uint len)
{
//计算最大可读取数据量
uint ret = -1;
uint n = GetFileLen(fd) - GetFilePos(fd);
uint i = 0;

len = (len < n) ? len : n;
//循环读取
while( (i < len) && ret )
{
byte* p = AddrOff(buf, i);
//缓冲区数据读完了,就需要从硬盘读入文件数据链表的下一个扇区到缓冲区中
if( fd->offset == SECT_SIZE )
{
ret = PrepareCache(fd, fd->objIdx + 1);
}

if( ret )
{ //从缓冲区读取数据
n = CopyFromCache(fd, p, len - i); //len-i 还需要读取多少数据
}

i += n;
}

ret = i;

return ret;
}

uint FRead(uint fd, byte* buf, uint len)
{
uint ret = -1;

if( IsFDValid((FileDesc*)fd) && buf )
{
ret = ToRead((FileDesc*)fd, buf, len);
}

return ret;
}

辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
static uint ToLocate(FileDesc* fd, uint pos)
{
uint ret = -1;
uint len = GetFileLen(fd);
//计算移动后读写指针的位置,不可超出文件长度
pos = (pos < len) ? pos : len;

{ //计算新位置在哪里
uint objIdx = pos / SECT_SIZE;
uint offset = pos % SECT_SIZE;
uint sctIdx = FindIndex(fd->fe.sctBegin, objIdx);//文件系统中的位置

ToFlush(fd);
//flush后在读取数据
if( (sctIdx != SCT_END_FLAG) && HDRawRead(sctIdx, fd->cache) )
{
fd->objIdx = objIdx;
fd->offset = offset;

ret = pos;
}
}

return ret;
}

//擦除文件尾部n个字节
uint FErase(uint fd, uint bytes)
{
uint ret = 0;
FileDesc* pf = (FileDesc*)fd;

if( IsFDValid(pf) )
{
uint pos = GetFilePos(pf);
uint len = GetFileLen(pf);

ret = EraseLast(&pf->fe, bytes);

len -= ret;

if( ret && (pos > len) )
{
ToLocate(pf, len);
}
}

return ret;
}
//移动文件指针
uint FSeek(uint fd, uint pos)
{
uint ret = -1;
FileDesc* pf = (FileDesc*)fd;

if( IsFDValid(pf) )
{
ret = ToLocate(pf, pos);
}

return ret;
}
//文件长度
uint FLength(uint fd)
{
uint ret = -1;
FileDesc* pf = (FileDesc*)fd;

if( IsFDValid(pf) )
{
ret = GetFileLen(pf);
}

return ret;
}
//读写指针位置
uint FTell(uint fd)
{
uint ret = -1;
FileDesc* pf = (FileDesc*)fd;

if( IsFDValid(pf) )
{
ret = GetFilePos(pf);
}

return ret;
}
//缓冲区数据写入硬盘
uint FFlush(uint fd)
{
uint ret = -1;
FileDesc* pf = (FileDesc*)fd;

if( IsFDValid(pf) )
{
ret = ToFlush(pf);
}

return ret;
}