文章目录
文件系统的设计和实现
背景知识
文件是记录在外存上的相关信息具有名称的集合。文件系统是操作系统中复制存取和管理信息的模块,它用统一方法管理用户和系统信息的存储、检索、更新、共享和保护,并为用户提供一整套方便有效的文件使用和操作方法。需要提供的功能有:
-
提供文件的逻辑组织方法;
-
提供文件的物理组织方法;
-
提供文件的存取和使用方法;
-
实现文件的目录管理;
-
实现文件的共享、保护和保密;
-
实现文件的存储空间管理;
-
提供与I/O子系统的统一接口等。
实验设计相关的定义
文件:文件是由文件名标识的有序字节串,典型的配套文件操作有读、写、创建和删除等。
目录项:目录项是文件路径名中的一部分,其中/、home、fei和fei.c都是目录项。
索引节点(inode):索引节点是存放文件控制信息的数据结构,又分磁盘块中的索引节点和主存中活动的索引节点。
安装点:文件系统被安装在一个特定的安装点,所有的已安装文件系统都作为根文件系统树中的叶子出现在系统中。
文件控制块和文件目录
文件控制块(file control block, FCB)是文件系统给每个文件建立的唯一管理数据结构,一个文件由两个部分组成:FCB和文件信息。FCB需要包括文件标识和控制信息、文件逻辑结构信息、文件物理结构信息、文件使用信息、文件管理信息等。
文件目录有两种,分别用于描述子目录和FCB。
文件管理数据结构设计
基本思路
在现有文件系统中创建一个文件,并将其模拟成一个物理磁盘,并在该文件上实现模拟磁盘块及文件系统的管理。在此基础上,设计并实现一组文件操作函数调用接口,支持对模拟文件系统的访问。
实验目标
1.设计和实现一个模拟文件系统,包括目录、普通文件和文件的存储。
2.文件系统的目录结构采用类似Linux的树状结构。
3.操作包括:
(1)目录的添加、删除、重命名;
(2)目录的显示(列表);
(3)文件的添加、删除、重命名;
(4)文件和目录的拷贝;
(5)文件的读写操作
物理磁盘设计
卷盘块数等于100块,每个磁盘块512字节,磁盘块之间用“/n”隔开,总共是514B.0#表示超级块,1#~10#放索引节点,每个节点占64B,共80个索引节点。初始化时存在根目录root,占用0#节点和11#盘块。
物理磁盘采用下图的设计方案。其中超级块用于存放文件系统结构和管理信息;索引节点区用于存放索引节点表,索引节点记录文件属性,每个索引节点都有相同的大小和唯一的编号,对于文件系统的每个文件在该表中都有一个索引节点;数据区为数据块,文件的内容保存在这个区域的块中。
空闲磁盘块管理
采用空闲块成组链接法对磁盘空间的空闲块加以组织,下图给出基于空闲块成组链接法的示例,该图中将每100个空闲块划归1组,将各组中的盘块号存放在其前组中的第1个空闲块,但第1组的空闲盘块号放入系统专用控制块中,最后一组为99块,第1组不足100块(文件存储空间不会恰好为100的整数倍,所以第1组小于100),其他各组均为100块。在本实验中,我们采用每组10块,12#~99#分为九组,每组的最后一个磁盘块里存放下一组的磁盘块信息。最后一组只有8块,加上0作为结束标志。在超级块中用一个一位数组(10个元素)作为空闲的磁盘块栈,放入第一组盘块。
超级块、索引节点及目录结构设计
struct SUPERBLOCK /*超级块*/
{
int fistack[80]; //空闲节点号栈
int fiptr; //空闲节点栈指针
int fbstack[10]; //空闲盘块号栈
int fbptr; //空闲节点栈指针
int inum; //空闲节点总数
int bnum; //空闲盘块总数
};
struct INODE /*节点(64B)已保证每两个数据之间由空格隔开*/
{
int fsize; //文件大小
int fbnum; //文件盘块数
int addr[4]; //4个直接盘块号
int addr1; //一个一次间址()
int addr2; //一个两次间址()
char owner[6]; //文件所有者
char group[6]; //文件所属组
char mode[11]; //文件类别及存储权限
char ctime[9]; //最近修改时间
};
struct DIR /*目录项(36B)*/
{
char fname[14]; //文件名(当前目录)
int index; //节点号
char parfname[14]; //父目录名
int parindex; //父目录节点号
};
代码展示
节点操作函数
节点申请
ialloc函数主要是向超级快申请一个索引节点,ifree函数是释放一个索引节点,需要回收对应的盘块和索引号,readinode函数是读取一个inode节点的内容,根据index即索引节点号可以获取到对应的文件或者目录项的文件信息,writeinode函数是将一个索引节点inode写入磁盘中。
int ialloc() {
if (superblock.fiptr > 0) {
// 当前可用 80 - superblock.fiptr 80 是索引节点总个数,fiptr是空闲节点的个数
int temp = superblock.fistack[80 - superblock.fiptr];
superblock.fistack[80 - superblock.fiptr] = -1;
superblock.fiptr--;
return temp;
}
return -1;
}
归还节点
/**
* 归还节点
* 指定一个节点号,回收一个节点。先清空空节点,
* 然后插入栈中合适节点,保持节点的有序性
* @param index
*/
void ifree(int index) {
openDisk();
// 清空节点, 节点的索引定位 514 是0#, 64是索引大小,
disk.seekp(514 + 64 * index + 2 * ( index / 8));
disk << setw(6) << "";
closeDisk();
// 把节点号找到合适的位置插入到空闲节点栈
for (int i = 80 - superblock.fiptr; i < 80; ++i) {
// 若小于它, 前移一位
if (superblock.fistack[i] < index) {
superblock.fistack[i - 1] - superblock.fistack[i];
} else {
// 放在第1个大于它的节点号前面
superblock.fistack[i - 1] = index;
break;
}
}
superblock.fiptr++;
}
读取指定的inode
/**
* 读指定节点的索引节点信息(节点为index,读指针应该定位到514+64*index+2*(index/8),
* 把索引节点信息保存到变量inode中,便于对同一节点进行大量操作
* @param index
* @param inode
*/
void readinode(int index, INODE & inode) {
openDisk();
disk.seekp(514 + 64*index + 2 * (index / 8));
// 文件大小
disk >> inode.fsize;
// 文件盘块数
disk >> inode.fbnum;
// 4个直接盘块号
for (int i = 0; i < 4; ++i) {
disk >> inode.addr[i];
}
// 一个一次间址
disk >> inode.addr1;
// 一个两次间址
disk >> inode.addr2;
// 文件所有者
disk >> inode.owner;
// 文件所属组
disk >> inode.group;
// 文件类别及存储权限
disk >> inode.mode;
// 最近修改时间
disk >> inode.ctime;
closeDisk();
}
写节点
/**
* 写节点 把inode写回指定的节点
* @param inode
* @param index
*/
void writenode(INODE inode, int index) {
openDisk();
disk.seekp(514 + 64*index + 2 * (index / 8));
// 文件大小
disk << setw(6) << inode.fsize;
// 文件盘块数
disk << setw(6) << inode.fbnum;
for (int i = 0; i < 4; ++i) {
disk << setw(3) << inode.addr[i];
}
// 一个一次间址
disk << setw(3) << inode.addr1;
// 一个两次间址
disk << setw(3) <<inode.addr2;
// 文件所有者
disk << setw(6) << inode.owner;
// 文件所属组
disk << setw(6) << inode.group;
// 文件类别及存储权限
disk << setw(12) << inode.mode;
// 最近修改时间
disk << setw(10) << inode.ctime;
closeDisk();
}
盘块的函数
本实验中,我们将盘块的默认大小设计为514B,总共100个盘块,使用一个文件去模拟文件系统,而盘块是文件具体存放的数据信息,一个文件可能存放在多个盘块中,所以需要编写盘块的申请和释放函数。
盘块申请
/**
* 申请一个盘块,返回盘块号,否则返回-1
* @return
*/
int balloc() {
int temp = superblock.fbstack[10 - superblock.fbptr];
// 到栈底了
if (superblock.fbptr == 1) {
// 最后记录盘块号0 (保留为栈底,分配不成功)
if (temp == 0) {
return -1;
}
superblock.fbstack[10 - superblock.fbptr] = -1;
superblock.fbptr = 0;
// 读取盘块内容读入栈
int id, num = 0;
openDisk();
// 计算盘块内容个数num(最多10)最后盘块可能不到10个
disk.seekp(514 * temp);
for (int j = 0; j < 10; ++j) {
disk >> id;
num ++;
if (id == 0) {
break;
}
}
disk.seekp(514 * temp);
for (int j = 10 - num; j < 10; ++j) {
disk >> id;
superblock.fbstack[j] = id;
}
superblock.fbptr = num;
closeDisk();
// 清空回收盘块
openDisk();
disk.seekp(514 * temp);
disk << setw(512) << "";
closeDisk();
return temp;
} else {
// 不是记录盘块
superblock.fbstack[10 - superblock.fbptr] = -1;
superblock.fbptr--;
return temp;
}
}
释放盘块
/**
* 归还盘块
* @param index
*/
void bfree(int index) {
openDisk();
// 清空该回收盘
disk.seekp(514 * index);
disk << setw(512) << "";
closeDisk();
// 如果栈已经满了的话,栈中内容计入回收盘块,栈清空
if (superblock.fbptr == 10) {
openDisk();
disk.seekp(514* index);
for (int i = 0; i < 10; ++i) {
disk << setw(3) << superblock.fbstack[i];
superblock.fbstack[i] = -1;
}
closeDisk();
superblock.fbptr = 0;
}
// 回收盘块压栈
superblock.fbstack[10 - superblock.fbptr - 1] = index;
superblock.fbptr++;
}
超级块操作函数
超级块是管理整个磁盘所有资源的,包括索引节点和对应盘块的空闲信息,是整个文件系统非常重要的模块,在整个系统启动的时候,需要把超级块加载进入到主存中。
读超级块
/**
* 读超级块到主存区
*/
void readsuper() {
openDisk();
int i;
for (i = 0; i < 80; i++) {
// 读取空闲节点栈
disk >> superblock.fistack[i];
}
// 空闲栈指针
disk >> superblock.fiptr;
for (i = 0; i < 10; i++) {
// 空闲盘号栈
disk >> superblock.fbstack[i];
}
// 空闲盘号栈指针
disk >> superblock.fbptr;
// 空闲索引节点数量
disk >> superblock.inum;
// 空闲盘块号总数
disk >> superblock.bnum;
closeDisk();
}
写超级块
/**
* 写超级块
*/
void writesuper() {
// 主存写回超级块
openDisk();
int i;
for (i = 0; i < 80; i++) {
// 写空闲节点栈
disk << setw(3) << superblock.fistack[i];
}
// 空闲节点栈指针
disk << setw(3) << superblock.fiptr;
for (i = 0; i < 10; i++) {
// 空闲盘块栈
disk << setw(3) << superblock.fbstack[i];
}
disk << setw(3) << superblock.fbptr;
// 空闲节点号
disk << setw(3) << superblock.inum;
// 空闲盘块号
disk << setw(3) << superblock.bnum;
closeDisk();
}
代码说明
上面的是一部分文件系统的代码,其他代码就不放在博客中,但是对于文件操作的命令都是建立在这些基础函数上的,包括目录项和文件切换等等,上面的代码如果大家需要的完整代码的话,可以在评论区交流。
参考资料:
Linux操作系统实验教程(费翔林)