Linux内核源码分析之文件系统——inode

众所周知,计算机系统在掉电后也能存储数据的就是磁盘了,所以大量数据大部分时间是存放在磁盘的;现在新买的PC,磁盘从数百G到1TB不等;服务器的磁盘从数十TB到上百TB,这么大的存储空间,该怎么高效地管理和使用了?站在硬件角度,cpu的分页机制把虚拟内存切割成大量4KB大小的块,所以4KB也成了硬件层面最小的内存分配单元;对比内存,磁盘的管理方式也类似,只不过磁盘最小的存储或读写单元是512byte,称之为扇区(用户哪怕只想读1格byte,驱动每次也要读512byte的数据);不过现在的文件一般都远超512byte,所以存储单个文件肯定需要超过1个扇区的空间,这就导致了磁盘的磁头要挨个读不同的扇区,花费大量时间在磁盘上寻址,导致IO效率低下,形成了瓶颈!为了提升读取效率,磁盘一般都是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。"块"的大小,最常见的是4KB(和内存页的大小保持一致, 便于从磁盘读写数据???),即连续八个 sector组成一个 block;

  1、上一篇文章介绍了高速缓存区,为了方便管理这么一大块缓存区,linux采用了buffer_head结构体来描述缓存区的各种属性;同理:磁盘上也是被人为划分成了很多“块”,为了方便管理这些块,也需要相应的结构体,linux采用结构体叫m_inode(或则这样理解:文件数据都存放在block中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为“索引节点”;每一个文件都有对应的inode,里面包含了与该文件有关的一些信息),如下:

  注意:

一个文件只需要一个inode节点来存储文件的元信息就够了,所以文件和inode节点是一一对应的(注意这里是文件,不是文件名);

如果说文件很大,占用了很多的磁盘block,怎么才能找全文件的占用的所有磁盘block了?此刻就要用到inode结构体的i_zone[9]字段了,文件中的数据存放在哪个硬盘上的逻辑块上就是由这个数组来映射的:前面7个是直接存储文件数据块,第8个是间接块,第9个是二级间接块!所有直接+间接+二级间接块加起来,一共64M ,这个在0.11版本所在的1991年已经非常大了!

struct m_inode {
    unsigned short i_mode;/*文件类型和属性,ls查看的结果,比如drwx------*/
    unsigned short i_uid;/*文件宿主id*/
    unsigned long i_size;
    unsigned long i_mtime;/*文件内容上一次变动的时间*/
    unsigned char i_gid;/*groupid:宿主所在的组id*/
    unsigned char i_nlinks; /*链接数:有多少个其他的文件夹链接到这里*/
    unsigned short i_zone[9];/*文件映射的逻辑块号*/
/* these are in memory also */
    struct task_struct * i_wait;/*等待该inode节点的进程队列*/
    unsigned long i_atime;/*文件上一次打开的时间*/
    unsigned long i_ctime;/*文件的inode上一次变动的时间*/
    unsigned short i_dev;/*设备号*/
    unsigned short i_num;
    /* 多少个进程在使用这个inode*/
    unsigned short i_count;
    unsigned char i_lock;/*互斥锁*/
    unsigned char i_dirt;
    unsigned char i_pipe;
    unsigned char i_mount;
    unsigned char i_seek;
    /*
    数据是否是最新的,或者说有效的,
    update代表数据的有效性,dirt代表文件是否需要回写,
    比如写入文件的时候,a进程写入的时候,dirt是1,因为需要回写到硬盘,
    但是数据是最新的,update是1,这时候b进程读取这个文件的时候,可以从
    缓存里直接读取。
      */
    unsigned char i_update;
};

为了把内存文件块的数据映射到磁盘的block,linux专门写了_bmap函数:

i_zone映射关系图示:

 文件数据块映射到盘块的处理操作。(block位图处理函数,bmap - block map)
// 参数:inode - 文件的i节点指针;block - 文件中的数据块号;create - 创建块标志。
// 该函数把指定的文件数据块block对应到设备上逻辑块上,并返回逻辑块号。如果创建标志
// 置位,则在设备上对应逻辑块不存在时就申请新磁盘块,返回文件数据块block对应在设备
// 上的逻辑块号(盘块号)。
static int _bmap(struct m_inode * inode,int block,int create)
{
    struct buffer_head * bh;
    int i;

    // 首先判断参数文件数据块号block的有效性。如果块号小于0,则停机。如果块号大于
    // 直接块数7+间接块数(相当于二级指针)512+二次间接块数(相当于三级指针)512*512,超出文件系统表示范围,则停机。
    // 这种间接块、二次间接块类似内存分页的机制
    if (block<0)
        panic("_bmap: block<0");
    if (block >= 7+512+512*512)
        panic("_bmap: block>big");
    // 然后根据文件块号的大小值和是否设置了创建标志分别进行处理。如果该块号小于7,
    // 则使用直接块表示。如果创建标志置位,并且i节点中对应块的逻辑块(区段)字段为0,
    // 则相应设备申请一磁盘块(逻辑块),并且将磁盘上逻辑块号(盘块号)填入逻辑块
    // 字段中。然后设置i节点改变时间,置i节点已修改标志。然后返回逻辑块号。
    if (block<7) {
        if (create && !inode->i_zone[block])
            if ((inode->i_zone[block]=new_block(inode->i_dev))) {
                inode->i_ctime=CURRENT_TIME;
                inode->i_dirt=1;
            }
        return inode->i_zone[block];
    }
    // 如果该块号>=7,且小于7+512,则说明使用的是一次间接块。下面对一次间接块进行处理。
    // 如果是创建,并且该i节点中对应间接块字段i_zone[7]是0,表明文件是首次使用间接块,
    // 则需申请一磁盘块用于存放间接块信息,并将此实际磁盘块号填入间接块字段中。然后
    // 设置i节点修改标志和修改时间。如果创建时申请磁盘块失败,则此时i节点间接块字段
    // i_zone[7] = 0,则返回0.或者不创建,但i_zone[7]原来就为0,表明i节点中没有间接块,
    // 于是映射磁盘是吧,则返回0退出。
    block -= 7;
    if (block<512) {
        if (create && !inode->i_zone[7])
            if ((inode->i_zone[7]=new_block(inode->i_dev))) {
                inode->i_dirt=1;
                inode->i_ctime=CURRENT_TIME;
            }
        if (!inode->i_zone[7])
            return 0;
        // 现在读取设备上该i节点的一次间接块。并取该间接块上第block项中的逻辑块号(盘块
        // 号)i。每一项占2个字节。如果是创建并且间接块的第block项中的逻辑块号为0的话,
        // 则申请一磁盘块,并让间接块中的第block项等于该新逻辑块块号。然后置位间接块的
        // 已修改标志。如果不是创建,则i就是需要映射(寻找)的逻辑块号。
        if (!(bh = bread(inode->i_dev,inode->i_zone[7])))
            return 0;
        i = ((unsigned short *) (bh->b_data))[block];
        if (create && !i)
            if ((i=new_block(inode->i_dev))) {
                ((unsigned short *) (bh->b_data))[block]=i;
                bh->b_dirt=1;
            }
        // 最后释放该间接块占用的缓冲块,并返回磁盘上新申请或原有的对应block的逻辑块号。
        brelse(bh);
        return i;
    }
    // 若程序运行到此,则表明数据块属于二次间接块。其处理过程与一次间接块类似。下面是对
    // 二次间接块的处理。首先将block再减去间接块所容纳的块数(512),然后根据是否设置了
    // 创建标志进行创建或寻找处理。如果是新创建并且i节点的二次间接块字段为0,则序申请一
    // 磁盘块用于存放二次间接块的一级信息,并将此实际磁盘块号填入二次间接块字段中。之后,
    // 置i节点已修改标志和修改时间。同样地,如果创建时申请磁盘块失败,则此时i节点二次
    // 间接块字段i_zone[8]为0,则返回0.或者不是创建,但i_zone[8]原来为0,表明i节点中没有
    // 间接块,于是映射磁盘块失败,返回0退出。
    block -= 512;
    if (create && !inode->i_zone[8])
        if ((inode->i_zone[8]=new_block(inode->i_dev))) {
            inode->i_dirt=1;
            inode->i_ctime=CURRENT_TIME;
        }
    if (!inode->i_zone[8])
        return 0;
    // 现在读取设备上该i节点的二次间接块。并取该二次间接块的一级块上第 block/512 项中
    // 的逻辑块号i。如果是创建并且二次间接块的一级块上第 block/512 项中的逻辑块号为0的
    // 话,则需申请一磁盘块(逻辑块)作为二次间接块的二级快i,并让二次间接块的一级块中
    // 第block/512 项等于二级块的块号i。然后置位二次间接块的一级块已修改标志。并释放
    // 二次间接块的一级块。如果不是创建,则i就是需要映射的逻辑块号。
    if (!(bh=bread(inode->i_
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值