我们需要用文件系统来管理文件。
block
每⼀个⽂件由若⼲个块(block)组成, 这些块不需要顺序存放, 只要按照某种⽅法组织起来. 这样, 即使⼩⽂件被删除, 他们所占⽤的块也可以得到有效的利⽤。块⼤⼩不同对⽂件系统⼀些参数的有影响。这里取一个块占2个扇区即1kb.
INode
创建结构INode来对这些块进行索引。
union Inode { // Inode Table的表项
uint8_t byte[INODE_SIZE];//1024
struct {
int16_t type; // 该⽂件的类型、访存控制等
int16_t linkCount; // 该⽂件的链接数
int32_t blockCount; // 该⽂件的data block总数
int32_t size; // 该⽂件所含字节数
int32_t pointer[POINTER_NUM]; // data block偏移量,扇区为单位
int32_t singlyPointer; // ⼀级data block偏移量索引,扇区为单位
};
};
目录
分类是管理的⼀种有效⼿段, 随着⽂件数量的增加, 我们⾃然希望有⼀种能够对⽂件进⾏分类的⽅法, ⽬录的出现正是为了⽅便⽂件的管理.实际上, ⽬录只是⼀种特殊的⽂件. 它是⼀张记录了当前⽬录⽂件信息的表, 每⼀个表项记录了该⽬录中⼀个⽂件的⽂件名或⽬录的名称和iNode索引。
union DirEntry { // ⽬录⽂件的表项
uint8_t byte[DIRENTRY_SIZE];//128
struct {
int32_t inode; // 该⽬录项对应的inode,编号从1开始,0表示空
char name[NAME_LENGTH]; // 该⽬录项对应的⽂件名
};
};
Inode Bitmap
对Inode使用情况进行记录,一个位(bit)表示一个Inode的使用情况。
struct InodeBitmap {
uint8_t byte[INODE_BITMAP_SIZE];
};
typedef struct InodeBitmap InodeBitmap;
Block Bitmap
对Block使用情况进行记录。一个位(bit)表示一个block的使用情况。
struct BlockBitmap {
uint8_t byte[BLOCK_BITMAP_SIZE];
};
typedef struct BlockBitmap BlockBitmap;
super block
⽂件系统的开头⼀般会有超级块, 超级块会记录⽂件系统的参数, 如iNode总数, 磁盘块总数, 每个区域的
偏移量, 块⼤⼩等, ⽂件系统初始化的时候将会读取超级块中的内容.
//union用于填充满整个扇区,提高磁盘存区效率
union SuperBlock {
uint8_t byte[SUPER_BLOCK_SIZE];
struct {
int32_t sectorNum; // ⽂件系统中扇区总数
int32_t inodeNum; // ⽂件系统中inode总数
int32_t blockNum; // ⽂件系统中data block总数
int32_t availInodeNum; // ⽂件系统中可⽤inode总数
int32_t availBlockNum; // ⽂件系统中可⽤data block总数
int32_t blockSize; // 每个block所含字节数
int32_t inodeBitmap; // inode bitmap在⽂件系统的偏移,扇区为单位
int32_t blockBitmap; // block bitmap在⽂件系统的偏移,扇区为单位
int32_t inodeTable; // inode table在⽂件系统的偏移,扇区为单位
int32_t blocks; // data block在⽂件系统的偏移,扇区为单位
};
};
磁盘区域划分
磁盘区域图如下。
下面是实现文件系统的一些重要的函数
初始化superblock
/*
* When sectorNum < 64, FS is too small, return -1.
* Initialize superblock and write back to first block in FS.
* All offset is sector as unit, index of sector.
*/
//输入:磁盘文件,扇区总数,每个block所占扇区数(2),以及未初始化的superblock
int initSuperBlock (FILE *file, int sectorNum, int sectorsPerBlock, SuperBlock *superBlock) {
if (sectorNum < 64)
return -1;
int inodeBitmapOffset = sizeof(SuperBlock) / SECTOR_SIZE;
//扇区为单位,紧跟superblock后
int blockBitmapOffset = inodeBitmapOffset + INODE_BITMAP_SIZE / SECTOR_SIZE;
//紧跟inodeBitmap后
int inodeTableOffset = blockBitmapOffset + BLOCK_BITMAP_SIZE / SECTOR_SIZE;
//紧跟blockBitmap后
int blocksOffset = inodeTableOffset + INODE_BLOCKS * sectorsPerBlock;
//紧跟inodeTable后
int inodeNum = INODE_BLOCKS * SECTOR_SIZE * sectorsPerBlock / sizeof(Inode);
int blockNum = sectorNum / sectorsPerBlock - 3 - INODE_BLOCKS;
//3表示superblock,inodeBitmap,blockBitmap三个
superBlock->sectorNum = sectorNum;
superBlock->inodeNum = (inodeNum / 8) * 8;
superBlock->blockNum = (blockNum / 8) * 8;
superBlock->availInodeNum = (inodeNum / 8) * 8;
//一开始未创建任何inode和block,所以都是可用的
superBlock->availBlockNum = (blockNum / 8) * 8;
superBlock->blockSize = SECTOR_SIZE * sectorsPerBlock;
superBlock->inodeBitmap = inodeBitmapOffset;
superBlock->blockBitmap = blockBitmapOffset;
superBlock->inodeTable = inodeTableOffset;
superBlock->blocks = blocksOffset;
fseek(file, 0, SEEK_SET);
//写superBlock,file的第0处开始。这个file之后还要和bootloader和操作系统内核态代码拼接起来。
fwrite((void *)superBlock, sizeof(SuperBlock), 1, file);
return 0;
}
初始化根目录
文件系统要有一个根目录(root dir),之后所有的文件和目录都在此根目录之下。
/*
* Root dir contain 2 dirEntry (. and ..).
* 1 Inode and 1 block used.
*/
int initRootDir (FILE *file, SuperBlock *superBlock) {
int inodeBitmapOffset = 0;
int inodeTableOffset = 0;
int inodeOffset = 0; // XXX byte as unit
//用
int blockOffset = 0; // XXX sector as unit
DirEntry *dirEntry = NULL;
uint8_t buffer[superBlock->blockSize];
//buffer相当于中间变量,用于暂时存储一些数据
InodeBitmap inodeBitmap;
Inode inode;
if (superBlock->availInodeNum == 0)
return -1;
//无可用block,则返回
superBlock->availInodeNum --;
//一个目录要占一个block
inodeBitmapOffset = superBlock->inodeBitmap; // XXX sector as unit
inodeTableOffset = superBlock->inodeTable; // XXX sector as unit
inodeBitmap.byte[0] = 0x80;
//使用第一个inode指向根目录
inodeOffset = inodeTableOffset * SECTOR_SIZE;
inode.type = DIRECTORY_TYPE;
getAvailBlock(file, superBlock, &blockOffset);
//返回可用的(或者说空闲的)block下标,用blockOffset记录
inode.pointer[0] = blockOffset;
inode.blockCount = 1;
setBuffer(buffer, superBlock->blockSize, 0);
//清空buffer
dirEntry = (DirEntry *)buffer;
//一个block可以容纳8个DirEntry
dirEntry[0].inode = 1;
dirEntry[0].name[0] = '.';
dirEntry[0].name[1] = '\0';
//指向本身
dirEntry[1].inode = 1;
dirEntry[1].name[0] = '.';
dirEntry[1].name[1] = '.';
dirEntry[1].name[2] = '\0';
//指向父目录,但根目录的父目录还是本身
inode.linkCount = 2;
inode.size = superBlock->blockSize;
fseek(file, blockOffset * SECTOR_SIZE, SEEK_SET);
fwrite((void *)buffer, sizeof(uint8_t), superBlock->blockSize, file);
//写入block
fseek(file, inodeBitmapOffset * SECTOR_SIZE, SEEK_SET);
fwrite((void *)&inodeBitmap, sizeof(uint8_t), 1, file);
//写入inodeBitmap
fseek(file, inodeOffset, SEEK_SET);
fwrite((void *)&inode, sizeof(Inode), 1, file);
//写入inode
fseek(file, 0, SEEK_SET);
fwrite((void *)superBlock, sizeof(SuperBlock), 1, file);
//写入superBlock
return 0;
}
可以看到,用第一个Inode来指向根目录文件。
同时也可以发现,创建一个新的目录需要本目录和父目录的Inode,因为即使是一个“空”目录也会有 ./ 和 …/ 这两项。一个目录文件占1/8个block.创建后superblock等信息会改动,不要忘了更新(即读入内存->更改->写回)。
readInode
给定一个路径,readInode()函数返回这个路径对应的Inode
思路:像链表一样从根目录开始一步步读取下去。
/*
* Find the Inode of destFilePath, '/' to first inode, '/bin' to inode in 'bin'
* Input: file (disk), superBlock
* OutPut: destInode, inodeOffset (byte as unit)
* Return -1 when failed.
*/
int readInode (FILE *file, SuperBlock *superBlock, Inode *destInode,
int *inodeOffset, const char *destFilePath) {
int i = 0;
int j = 0;
int ret = 0;
int cond = 0; //表示当前处理的路径是否还有'/'
*inodeOffset = 0;
uint8_t buffer[superBlock->blockSize];
DirEntry *dirEntry = NULL;
int count = 0;
int size = 0;
int blockCount = 0;
// no content in destFilePath
if (destFilePath == NULL || destFilePath[count] == 0)
return -1;
//返回desFilePath中第一个'/'出现的位置
ret = stringChr(destFilePath, '/', &size);
// destFilePath is not started with '/'
if (ret == -1 || size != 0)
return -1;
// load the first Inode
//先读取根目录对应的Inode
*inodeOffset = superBlock->inodeTable * SECTOR_SIZE;
fseek(file, *inodeOffset, SEEK_SET);
fread((void *)destInode, sizeof(Inode), 1, file);
count += 1;
while(destFilePath[count] != 0) {
ret = stringChr(destFilePath + count, '/', &size);
// pattern '//' occured
if (ret == 0 && size == 0)
return -1;
// no more '/'
if (ret == -1)
cond = 1;
// with more '/' but regular file
//非目录的路径,返回-1
else if (destInode->type == REGULAR_TYPE)
return -1;
// go deeper dir
blockCount = destInode->blockCount;
for (i = 0; i < blockCount; i++) {
//读取destInode指向的block,这些block装的是DirEntry
ret = readBlock(file, superBlock, destInode, i, buffer);
if (ret == -1)
return -1;
// try find matched DirEntry and Inode
dirEntry = (DirEntry *)buffer;
for (j = 0; j < superBlock->blockSize / sizeof(DirEntry); j++) {
//dirEntry[j].inode == 0 表示未使用?
if (dirEntry[j].inode == 0)
continue;
else if (stringCmp(dirEntry[j].name, destFilePath + count, size) == 0) {
*inodeOffset = superBlock->inodeTable * SECTOR_SIZE + (dirEntry[j].inode - 1) * sizeof(Inode);
fseek(file, *inodeOffset, SEEK_SET);
fread((void *)destInode, sizeof(Inode), 1, file);
break;
}
}
if (j < superBlock->blockSize / sizeof(DirEntry))
break;
}
if (i < blockCount) {
if (cond == 0)
//还有'/',应该继续读取下去
count += (size + 1);
else
return 0;
}
else
return -1;
}
return 0;
}
format 指令
format指令用于格式化一个磁盘。
主要操作为:清空磁盘、初始化superblock、初始化根目录
/*
* Create a file as FS, initialize SuperBlock and Root dir.
*/
int format (const char *driver, int sectorNum, int sectorsPerBlock) {
int i = 0;
int ret = 0;
FILE *file = NULL;
uint8_t byte[SECTOR_SIZE];
SuperBlock superBlock;
if (driver == NULL) {
printf("driver == NULL\n");
return -1;
}
file = fopen(driver, "w+");
if (file == NULL) {
printf("Failed to open driver.\n");
return -1;
}
for (i = 0; i < SECTOR_SIZE; i++) {
byte[i] = 0;
}
for (i = 0; i < sectorNum; i++) {
fwrite((void*)byte, sizeof(uint8_t), SECTOR_SIZE, file);
}//用0填充磁盘
ret = initSuperBlock(file, sectorNum, sectorsPerBlock, &superBlock);//初始化superblock
if (ret == -1) {
printf("Failed to format: No enough sectors.\n");
fclose(file);
return -1;
}
ret = initRootDir(file, &superBlock);
//初始化根目录
if (ret == -1) {
printf("Failed to fromat: No enough inodes or data blocks.\n");
fclose(file);
return -1;
}
printf("format %s -s %d -b %d\n", driver, sectorNum, sectorsPerBlock);
printf("FORMAT success.\n%d inodes and %d data blocks available.\n", superBlock.availInodeNum, superBlock.availBlockNum);
fclose(file);
return 0;
}
ls指令
ls指令:给定一个文件路径,展示该路径的文件信息
思路:根据路径读取Inode,若Inode对应的是普通文件,则输出其信息;若Inode对应的是目录文件,则输出该目录下的文件信息。
int ls (const char *driver, const char *destFilePath) {
// definition
FILE *file = NULL;
int ret = 0;
SuperBlock superBlock;
Inode inode;
int inodeOffset = 0;
DirEntry dirEntry;
int dirIndex = 0;
Inode childInode;
int inodeIndex = 0;
if (driver == NULL) {
printf("driver == NULL.\n");
return -1;
}
if (destFilePath == NULL) {
printf("destFilePath == NULL.\n");
fclose(file);
return -1;
}
file = fopen(driver , "r");
if (file == NULL) {
printf("Failed to open driver.\n");
return -1;
}
ret = readSuperBlock(file, &superBlock);
if (ret == -1) {
printf("Failed to load superBlock.\n");
fclose(file);
return -1;
}
//读取Inode
ret = readInode(file, &superBlock, &inode, &inodeOffset, destFilePath);
if (ret == -1) {
printf("Failed to read inode.\n");
fclose(file);
return -1;
}
//如果Inode指向常规文件
if (inode.type == REGULAR_TYPE) {
printf("ls %s\n", destFilePath);
inodeIndex = (inodeOffset - superBlock.inodeTable) / sizeof(Inode) + 1;
printf("Inode: %d, Type: %d, LinkCount: %d, BlockCount: %d, Size: %d.\n",
inodeIndex, inode.type, inode.linkCount, inode.blockCount, inode.size);
fclose(file);
return 0;
}
printf("ls %s\n", destFilePath);
//如果Inode指向的是目录文件
while (getDirEntry(file, &superBlock, &inode, dirIndex, &dirEntry) == 0) {
//将目录下的所有文件对应的Inode信息输出
dirIndex ++;
fseek(file, superBlock.inodeTable * SECTOR_SIZE + (dirEntry.inode - 1) * sizeof(Inode), SEEK_SET);
fread((void*)&childInode, sizeof(Inode), 1, file);
printf("Name: %s, Inode: %d, Type: %d, LinkCount: %d, BlockCount: %d, Size: %d.\n",
dirEntry.name, dirEntry.inode, childInode.type, childInode.linkCount, childInode.blockCount, childInode.size);
}
printf("LS success.\n%d inodes and %d data blocks available.\n", superBlock.availInodeNum, superBlock.availBlockNum);
fclose(file);
return 0;
}
mkdir指令
mkdir指令作用是新建一个目录文件。
思路:找到父目录对应的Inode;创建连接父目录Inode和子目录Inode的Dir; 创建子目录的Dir.
int mkdir (const char *driver, const char *destDirPath){
FILE *file = NULL;
char tmp = 0;
int length = 0;
int cond = 0;
int ret = 0;
int size = 0;
SuperBlock superBlock;
int fatherInodeOffset = 0;
int destInodeOffset = 0;
Inode fatherInode;
Inode destInode;
if (driver == NULL) {
printf("driver == NULL.\n");
return -1;
}
file = fopen(driver, "r+");
if (file == NULL) {
printf("Failed to open driver.\n");
return -1;
}
ret = readSuperBlock(file, &superBlock);
if (ret == -1) {
printf("Failed to load SuperBlock.\n");
fclose(file);
return -1;
}
if (destDirPath == NULL) {
printf("destDirPath == NULL");
fclose(file);
return -1;
}
//获取路径字符串长度
length = stringLen(destDirPath);
if (destDirPath[length - 1] == '/') {
cond = 1;
*((char*)destDirPath + length - 1) = 0;
}
//获取第一个'/'出现的位置
ret = stringChrR(destDirPath, '/', &size);
if (ret == -1) {
printf("Incorrect destination file path.\n");
fclose(file);
return -1;
}
//在第一个'/'后面的字符设0,截断路径
tmp = *((char*)destDirPath + size + 1);
*((char*)destDirPath + size + 1) = 0;
ret = readInode(file, &superBlock, &fatherInode, &fatherInodeOffset, destDirPath);
*((char*)destDirPath + size + 1) = tmp;
if (ret == -1) {
printf("Failed to read father inode.\n");
if (cond == 1)
*((char*)destDirPath + length - 1) = '/';
fclose(file);
return -1;
}
/*allocInode()
建立一个fatherInode和destInode之间的一个Dir。Dir指向destlnode
*/
ret = allocInode(file, &superBlock, &fatherInode, fatherInodeOffset,
&destInode, &destInodeOffset, destDirPath + size + 1, DIRECTORY_TYPE);
if (ret == -1) {
printf("Failed to allocate inode.\n");
if (cond == 1)
*((char*)destDirPath + length - 1) = '/';
fclose(file);
return -1;
}
/*
initDir()
建立destInode指向的两个dirEntry,一个dirEntry指向destInode本身,一个指向destInode的父目录。
*/
ret = initDir(file, &superBlock, &fatherInode, fatherInodeOffset,
&destInode, destInodeOffset);
if (ret == -1) {
printf("Failed to initialize dir.\n");
if (cond == 1)
*((char*)destDirPath + length - 1) = '/';
fclose(file);
return -1;
}
if (cond == 1)
*((char*)destDirPath + length - 1) = '/';
printf("mkdir %s\n", destDirPath);
printf("MKDIR success.\n%d inodes and %d data blocks available.\n", superBlock.availInodeNum, superBlock.availBlockNum);
fclose(file);
return 0;
}