文件系统设计与实现

文件系统的设计和实现

背景知识

文件是记录在外存上的相关信息具有名称的集合。文件系统是操作系统中复制存取和管理信息的模块,它用统一方法管理用户和系统信息的存储、检索、更新、共享和保护,并为用户提供一整套方便有效的文件使用和操作方法。需要提供的功能有:

  • 提供文件的逻辑组织方法;

  • 提供文件的物理组织方法;

  • 提供文件的存取和使用方法;

  • 实现文件的目录管理;

  • 实现文件的共享、保护和保密;

  • 实现文件的存储空间管理;

  • 提供与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操作系统实验教程(费翔林)

posted @ 2022-12-26 17:24  Leo哥coding~  阅读(107)  评论(0编辑  收藏  举报  来源