zoukankan      html  css  js  c++  java
  • 对文件系统的理解

    目录

    如果没有文件系统

             如何读写文件

             提炼上述过程中我们需要知道的信息

    文件系统的实现

             需要在硬盘上保存的信息

             代码上实现的逻辑

                      设备号

                      分区信息

                      file结构体

                      inode保存的信息

    如果有文件系统

             读写接口

             读写流程

             TASK_FS


    如果没有文件系统

      如果我们不在硬盘本身建立文件系统,我们直接面对硬盘的扇区。

    如何读写文件  

    先看看对于操作普通文件来说,意味着什么。

      我们要拿着一个小本本,上面记着,文件名,文件所在扇区以及文件大小。每次要读写文件,我们要人工查询这个账本,知道我们要的文件在哪里。如果文件A所在的扇区M已经写满了,随后的一个扇区M+1被文件B占用了,我们还想接着写文件A,怎么办呢?只能从其他地方找一个空闲扇区N,然后在账本上把N记录到文件A占用的扇区项中。

      我们如何知道硬盘上还有哪些空间可以用呢?难道每次都从前往后把扇区使用情况计算一遍吗吗?可能还需要另起一个账本记录扇区使用情况,删除文件,我们把对应的扇区标记为空闲,如果创建文件,把对应的扇区标记为不能使用。

    对于操作系统而言呢?我觉得,没有文件系统就不会有操作系统,这样的操作系统充其量就是一个硬盘驱动。为什么?可以设想一下创建文件的过程:

    1. 用户告诉这样的操作系统,说要创建一个文件A
    2. 计算机输出,请你自己记录好文件名,并告诉我要在哪个扇区创建。并且记录好这个文件你占用了哪些扇区

    我不能忍受。。。

    提炼上述过程中我们需要的信息

    将变化的放在一起,将不变的放在一起。统一才有美感。

    dir_entry

      对于文件使用情况的账本而言,看起来要表述一个文件在硬盘上的信息,我们需要知道它占用了哪些扇区,它的名字,文件大小这样的信息。那么这些信息应该放在哪里呢?当然可以随机存放,但是存放完了,计算机如何在下次使用的时候找到这个文件呢?还是需要一份记录来索引这些信息,还不如把这些文件信息按照统一格式存放在一起,这就是目录结构(dir_entry)的由来。按照树状目录能得到任何文件信息。

    sector_map

    对于硬盘使用情况的账本而言,要记录好哪些扇区空闲,哪些已经被使用了。这就是sector_map的由来。

    super_block

    那么这些账本本身是存在于硬盘的某些地方,还需要一个总账本来记录这些管理块的信息,这个总账本就是super_block。

    inode

    那么inode的由来呢?为什么文件名和inode分开存放呢?

      试想一下,如果文件名和文件的属性信息一起存放的话,一个文件目录项会占用很大的空间,一个扇区也许只能存几个文件的信息,而系统在查找文件的时候,可能要读很多次扇区才能找到需要的文件,这样大大影响系统的效率。毕竟我们在找文件的时候,不需要文件的信息,不需要知道文件大小、所在扇区等等信息全部与查找无关,为什么要这些信息来影响我们的速度呢?我们只要文件名来判断这是不是我们要找的文件。所以将文件的其余信息剥离出来概括为inode结构体。

    inode_array

      inode单独列出来了,存放在哪里呢?如何通过dir_entry找到inode呢?当然可以存放于任何扇区上,只不过dir_entry可能要加上inode所在扇区和在扇区中的偏移两个字段了,随之而来问题就是存放inode的扇区只能用来存放其他inode而不能用来存放文件数据了,因为我们给文件分配空间是按照扇区为单位的,难道一个扇区分给文件时候,还要记上一笔在偏移offset处是inode占用的,读写的时候请跳过,这样的逻辑恐怕没人会去用代码实现它吧。另外,由于“存放inode的扇区只能用来存放其他inode而不能用来存放文件数据”这样的原因,设计者就折中了一下,把indoe占用的扇区都提到一个单独空间,以后所有的inode都放到这个空间里,这个空间就是inode_array。

      当然也会出现问题,可能inode_array满了,而硬盘空间还要很大剩余;或者硬盘空间嘛呢,inode_array还有很多剩余。这是很极端的情况,总要有不尽人意的地方,那就把这个不足最小化吧。

      在存放inode的时候,怎么知道inode_array中哪个下标可以用呢?这就是又需要一份记录,来记录inode_array中哪些是空闲的,哪些是已经使用,这个记录就是inode_map。而inode在inode_array中的下标就是inode_num。dir_entry中记录了这个inode_num就可以在inode_array中找到对应的文件信息了。这个过程衔接的太美妙了。


    文件系统的实现

    文件系统需要的结构体大概都知道了,剩下的仅仅是需要规划处具体的结构体了。我们来看看。

    需要在硬盘上保存的信息

        超级块

        Inode-map

        Sector-map

        Inode-array

        上面几个结构体作者在书中都列举出来了,都是很好理解的。我不啰嗦再搬运过来了。

    代码上实现的逻辑

    设备号

      正是在作者的讲解下,我算是真正的了解到设备号的意义。以前总是看书上说主设备号代表设备归属于哪个驱动,子设备号真正表明是哪个具体的设备。我虽然能顺着设备号找到驱动,能从驱动中看到子设备号对流程的分用作用,但是感觉总是欠缺点什么。我就好奇为什么linux 0.12中将0x300就能代表第一块硬盘,难道不能是0x400吗?为什么0代表整个硬盘,1代表第一个分区?分区编号要按照物理分区顺序吗?如果是0x400会产生什么影响呢?

      跟着作者一起学着规划硬盘空间,才渐渐明白,这些编号可以随意编,跟硬盘上的分区顺序不存在某种必然的联系,只是最后落实到保存硬盘信息的结构体上的时候,不会出现偏差就可以了。

      对于操作系统而言,每个分区都被当做一个独立的设备对待。看看书中所描述的硬盘信息结构体。

    struct part_info {
        u32    base;    /* # of start sector (NOT byte offset, but SECTOR) */
        u32    size;    /* how many sectors in this partition */
    };
    
    /* main drive struct, one entry per drive */
    struct hd_info
    {
        int            open_cnt;
        struct part_info    primary[NR_PRIM_PER_DRIVE];//计算后NR_PRIM_PER_DRIVE = 5
        struct part_info    logical[NR_SUB_PER_DRIVE];// 计算后NR_SUB_PER_DRIVE = 64
    };

      由结构体可以看出来,硬盘上存在的每个分区都会被记录下来。

      书中根设备编号是0x322,可以知道子设备号是0x22,一开始很困惑,这么大的子设备号,难道要分0x22个分区?或者说系统怎么就知道0x22表示的是根分区呢?

      还得再看一段代码:

    logidx = (p->DEVICE - MINOR_hd1a) % NR_SUB_PER_DRIVE;
    sect_nr += p->DEVICE < MAX_PRIM ?
            hd_info[drive].primary[p->DEVICE].base :
            hd_info[drive].logical[logidx].base;

      先将设备号减去第一个逻辑设备的编号得到设备号在logical数组的下标。当然,可能这个设备号不是逻辑设备,而是主分区。没关系,下一步判断p->DEVICE 是不是小于MAX_PRIM,如果小于,说明是主分区,直接用p->DEVICE在primary数组中取值就可以了。

      原来是这样,你想怎么样编号就怎么样编号,只要你自己能找到映射关系就可以了。

    分区信息

      硬盘的管理结构体已经设计好了,那么如何获取硬盘的分区信息呢?见硬盘驱动那篇总结。

    文件描述符

      内存中的文件如何和硬盘中的文件联系起来?当我们打开一个文件后,后续的操作,如何来标示我们操作的是一个文件而不是一段莫名其妙的内存呢?

      首先,我们会想到将inode读到内存就好了,我们就知道文件的所有信息了。那文件名呢?好像文件名除了查找匹配能贡献一份力量,其它地方用不着啊,难道也一些读进来吗?仅仅是做个标识而已,用一个数不是更好、更简单吗?这就是文件描述符的作用。那文件描述符放在哪里呢?由于每个进程打开的文件不同,打开同一个文件的次序不同,那么文件描述符一般情况下也就不能作为进程共享的资源了(当然,域套接字是可以的,内核社区的人员一次又一次地刷新人们的理解力)。既然如此,文件描述符最好是进程私有的了,就只能放在进程表(也就是进程控制块)里面了。此外,机器资源有限,总不能让一个进程无限制的打开文件,最好大家都没内存了,只能歇菜了。所以,一个进程打开的文件数是有限制的,目前我们只给20个就好了。

    file结构体

      好像有了文件描述符就可以直接和inode关联起来了,没必要中间再加一层file结构体啊。我想是因为要以比较节约的方式共享文件吧,节约什么呢,除了内存还能有谁能让那些设计师精益求精呢?  

    1. 我们当然可以在每个进程控制块里面分配20个存放文件信息的结构体,存放读写偏移指针、打开的权限、inode指针等等信息。但是能保证进程会长时间打开20个文件吗?如果不能保证,那不就浪费了。如果以后允许打开100个文件呢?难道进程控制块也要随之而增大吗?
    2. 关于共享文件,父子进程通过一个放在描述符数组里面的指针共享一个file结构体,而不用在单独维护一个file时候还要考虑同步。设想这样一个情形:父子进程都对一个文件进行写操作,父进程写了10个字符,按照需求该子进程接着写10个字符了,如果是父子进程单独维护file结构体,那么实际上只有子进程写了10个字符,父进程写的10个字符被覆盖了。如果共享呢?file中的pos每次操作对于两个进程而言都是同步的(当然这个例子不太严谨,它本身就存在同步问题,但是仅仅用来说明一点问题还是可以的)。

    inode保存的信息

      为什么不用inode本身当做系统或者进程操作文件的接口呢?这个问题比较好考虑。多个进程操作同一个文件,那读写指针的值肯定不一样,读写方式也不一样,其实这些不一样的地方提炼出来就是file结构体啦,file结构体的内容也不是随意产生的。

      将变化的放在一起,将不变的放在一起。


    如果有文件系统

    再接下来考虑一下如果有文件系统能给我们带来什么好处呢?

    读写接口

    不过,首先还是要实现读写接口的,就套用linux惯用的读写接口就好了。

    int read(int fd, void *buf, int count);

    只不过linux是通过中断调用来和内核交互,咱们是通过给TASK_FS发送消息并同步等待来实现的。

    读写流程

    1. 那么如果一个用户进程A请求读写一个文件X,那么A会向TASK_FS进程发送消息,告诉FS文件名和读写模式。
    2. 功能完备的文件系统还要考虑很多因素,诸如做下判断,看看文件路径是相对于当前目录还是根目录。我们比较简单,全部按照根目录实现,而且不支持多级目录,所有文件都放在根目录中。
    3. TASK_FS会给TASK_HD发消息,把目录区读给我。然后逐一比较有没有相同的文件名,假设有同名的,根据dir_entry中记录的inode_num算一下文件indoe所在的扇区是多少,然后再给TASK_HD发消息,把inode所在扇区读进来,文件具体的信息就有了。
    4. 后续操作这个文件,TASK_HD根据进程控制块中的信息来计算和决定该怎么操作文件数据。比如说根据文件描述找到file结构体,里面有读写指针知道下一步要操作的位置是哪里,通过file结构体找到inode,这样就知道文件数据在哪个扇区了。

    通过上面简单的叙述,也可以窥见现代文件系统问什么加入了dentry这个成员,目录项在查找的时候也是经常用到的,还不如缓存在内存中,加快读写速度。

    TASK_FS

      TASK_FS在微内核的设计中,被设计为一个进程了,它不断地循环读取其它进程发给它的读写请求,但是一次只能处理一个请求,如果这个请求没有完成,那么其它进程只能挂接在TASK_FS的等待队列上等待了。不过没关系,过早的优化是万恶之源。

  • 相关阅读:
    Codeforces 451A Game With Sticks
    POJ 3624 Charm Bracelet
    POJ 2127 Greatest Common Increasing Subsequence
    POJ 1458 Common Subsequence
    HDU 1087 Super Jumping! Jumping! Jumping!
    HDU 1698
    HDU 1754
    POJ 1724
    POJ 1201
    CSUOJ 1256
  • 原文地址:https://www.cnblogs.com/shangye/p/6177993.html
Copyright © 2011-2022 走看看