【linux0.12】i节点操作

13 篇文章 0 订阅
10 篇文章 0 订阅

bmap和create_block

  bmap完成文件的逻辑盘块号到物理盘块号的映射,create_block完成新建一个物理盘块并返回盘块号的操作。
  为什么把这两个函数放在一块讲,因为这两个函数其实是同一个函数。请看定义

代码

  142 int bmap(struct m_inode * inode,int block)
  143 {
  144     return _bmap(inode,block,0);
  145 }
  146
  147 int create_block(struct m_inode * inode, int block)
  148 {
  149     return _bmap(inode,block,1);
  150 }

都是调用的同一个函数_bmap,下面我们来讲解_bmap函数

_bmap

代码

  参见_bmap

讲解

  这个函数看着一长串,但是功能也很清晰,就是读取或者创建一个数据块并返回块号。之所以写了这么多,是为了兼容处理直接块、一级间接块、二级间接块。
  83-90行处理直接块的情况,如果指定要创建数据块的话,则调用new_block,这个函数会在【linux0.12】盘块位图和i节点位图的管理与操作中来讲。其实直接块就是直接返回数据即可。
  92-110行处理一级间接块的情况。第一次new_block是新建间接块,间接块中存放直接块的块号。第二次new_block是新建直接块,直接块中存放真实数据如目录项。可以看到有一次读取间接块的操作(100行),间接块中存放的就是一个个的块号,所以接下来以数组形式访问。
  112-140处理二级间接块的情况。处理一级间接块是一样的思路,创建或者获取二级间接块,然后读入二级间接块,再根据二级间接块内容决定是创建一级间接块还是直接获取一级间接块的快号,获取到一级间接块块号之后读入,然后返回参数指定的块号。可以看到有两次读取块内容的操作,第一次是读取二级间接块,第二次是读取一级间接块。注意121行的block>>9是为了除以512,因为每个一级间接块都有512个块号,这样可以算出指定块号是在二级间接块上的哪一个一级间接块。有点绕,但原理是清晰的。

iput

  主要就是把inode的引用计数减一,并处理减一后的后果。之所以写了这么多,是为了兼容普通文件、块设备文件、管道文件等的处理。

代码

  152 void iput(struct m_inode * inode)
  153 {
  154     if (!inode)
  155         return;
  156     wait_on_inode(inode);
  157     if (!inode->i_count)
  158         panic("iput: trying to free free inode");
  159     if (inode->i_pipe) {
  160         wake_up(&inode->i_wait);
  161         wake_up(&inode->i_wait2);
  162         if (--inode->i_count)
  163             return;
  164         free_page(inode->i_size);
  165         inode->i_count=0;
  166         inode->i_dirt=0;
  167         inode->i_pipe=0;
  168         return;
  169     }
  170     if (!inode->i_dev) {
  171         inode->i_count--;
  172         return;
  173     }
  174     if (S_ISBLK(inode->i_mode)) {
  175         sync_dev(inode->i_zone[0]);
  176         wait_on_inode(inode);
  177     }
  178 repeat:
  179     if (inode->i_count>1) {
  180         inode->i_count--;
  181         return;
  182     }
  183     if (!inode->i_nlinks) {
  184         truncate(inode);
  185         free_inode(inode);
  186         return;
  187     }
  188     if (inode->i_dirt) {
  189         write_inode(inode); /* we can sleep - so do again */
  190         wait_on_inode(inode);
  191         goto repeat;
  192     }
  193     inode->i_count--;
  194     return;
  195 }

讲解

  156行,当我们处理该inode时,可能有别的进程已经锁了该inode,所以我们先等待唤醒。
  157行,如果引用计数已经为0,说明内核出错
  159-169行,处理管道文件的情况。可以看到就是唤醒等待在该管道上的任务,并如果引用计数减一后为0,则释放管道文件占用的内存。可以看到,管道文件实际是占用了内存的,i_size即是占用的内存地址。
  174-177行,处理块设备文件的情况。主要就是刷新设备。
  如果引用计数不为1,也就是还有别的进程在使用该inode,那么减一后直接返回。
  如果链接数为0,则说明文件已经被删除,截断文件并释放inode即可。释放inode的操作在【linux0.12】盘块位图和i节点位图的管理与操作中讲解,实际上就是设置该i节点对应的位图位为0。
  inode中数据与高速缓冲区数据不一致,则将i节点信息写入高速缓冲区。

iget

  与iput函数功能相反,主要是找到节点号为nr的i节点,并将其引用计数加一。

前置知识

  首先要知道磁盘i节点(struct d_inode)和内存i节点(struct m_inode)的结构。可以看到,磁盘i节点是内存i节点的子集。
  内核中维护一个内存i节点表,里面是常驻内存的i节点,可以看到i节点表的大小(NR_INODE)是和打开文件表的大小(NR_FILE)是一样的。
  内核中维护一个super_block表,里面存放了所有被挂载的文件系统的超级块结构。可以看到Linux对最多挂载的文件系统数量也有所限制(NR_SUPER)。

代码

      #define NR_INODE 64
      #define NR_FILE 64
      #define NR_SUPER 8
 	  struct m_inode inode_table[NR_INODE]={{0,},};
 	  struct super_block super_block[NR_SUPER];
   88 struct d_inode {
   89     unsigned short i_mode;
   90     unsigned short i_uid;
   91     unsigned long i_size;
   92     unsigned long i_time;
   93     unsigned char i_gid;
   94     unsigned char i_nlinks;
   95     unsigned short i_zone[9];
   96 };
   97
   98 struct m_inode {
   99     unsigned short i_mode;
  100     unsigned short i_uid;
  101     unsigned long i_size;
  102     unsigned long i_mtime;
  103     unsigned char i_gid;
  104     unsigned char i_nlinks;
  105     unsigned short i_zone[9];
  106 /* these are in memory also */
  107     struct task_struct * i_wait;
  108     struct task_struct * i_wait2;   /* for pipes */
  109     unsigned long i_atime;
  110     unsigned long i_ctime;
  111     unsigned short i_dev;
  112     unsigned short i_num;
  113     unsigned short i_count;
  114     unsigned char i_lock;
  115     unsigned char i_dirt;
  116     unsigned char i_pipe;
  117     unsigned char i_mount;
  118     unsigned char i_seek;
  119     unsigned char i_update;
  120 };

  247 struct m_inode * iget(int dev,int nr)
  248 {
  249     struct m_inode * inode, * empty;
  250
  251     if (!dev)
  252         panic("iget with dev==0");
  253     empty = get_empty_inode();
  254     inode = inode_table;
  255     while (inode < NR_INODE+inode_table) {
  256         if (inode->i_dev != dev || inode->i_num != nr) {
  257             inode++;
  258             continue;
  259         }
  260         wait_on_inode(inode);
  261         if (inode->i_dev != dev || inode->i_num != nr) {
  262             inode = inode_table;
  263             continue;
  264         }
  265         inode->i_count++;
  266         if (inode->i_mount) {
  267             int i;
  268
  269             for (i = 0 ; i<NR_SUPER ; i++)
  270                 if (super_block[i].s_imount==inode)
  271                     break;
  272             if (i >= NR_SUPER) {
  273                 printk("Mounted inode hasn't got sb\n");
  274                 if (empty)
  275                     iput(empty);
  276                 return inode;
  277             }
  278             iput(inode);
  279             dev = super_block[i].s_dev;
  280             nr = ROOT_INO;
  281             inode = inode_table;
  282             continue;
  283         }
  284         if (empty)
  285             iput(empty);
  286         return inode;
  287     }
  288     if (!empty)
  289         return (NULL);
  290     inode=empty;
  291     inode->i_dev = dev;
  292     inode->i_num = nr;
  293     read_inode(inode);
  294     return inode;
  295 }
  296

讲解

  253行,预先从i节点表中取得一个空闲的节点。取空闲i节点的过程放在下面来讲。
  256-264行看指定节点号的i节点是否已经在内存i节点表中了。当我们拿到这个i节点时,可能有别的进程在对该i节点操作并上锁,于是等待唤醒。261行再判断的原因是,有可能再等待唤醒的过程中,别的进程改变了该i节点的属性,于是这里需要再判断。
  266行-283行,对于挂载点的特殊处理。如果该节点是挂载点但是没有在全局super_block表中找到,则出错。否则重新设置inode的设备号,并将节点号设为ROOT_INO重新查找。对应的场景是:如果在/tmp/etc下挂载了一个文件系统,该文件系统的设备号为3,那么搜索内存i节点时不应该以根文件系统为设备号来查找,而是以新的文件系统的设备号3来查找,并且挂载点是新文件系统的根目录。
  走到288行则说明没有在节点表中找到指定的节点,如果预先取i节点失败则出错返回。
  否则设置新i节点的设备号和节点号,然后从磁盘i节点读取相关的属性。读取磁盘i节点到内存i节点的过程(read_inode)放在下面来讲。

总结

  总结一下这个函数干的事情,就是看一下内存结点表中是否有指定的节点,如果有则处理一下返回。如果没有则使用一个新的节点结构,并从磁盘i节点读入相关的属性。

get_empty_inode

代码

  197 struct m_inode * get_empty_inode(void)
  198 {
  199     struct m_inode * inode;
  200     static struct m_inode * last_inode = inode_table;
  201     int i;
  202
  203     do {
  204         inode = NULL;
  205         for (i = NR_INODE; i ; i--) {
  206             if (++last_inode >= inode_table + NR_INODE)
  207                 last_inode = inode_table;
  208             if (!last_inode->i_count) {
  209                 inode = last_inode;
  210                 if (!inode->i_dirt && !inode->i_lock)
  211                     break;
  212             }
  213         }
  214         if (!inode) {
  215             for (i=0 ; i<NR_INODE ; i++)
  216                 printk("%04x: %6d\t",inode_table[i].i_dev,
  217                     inode_table[i].i_num);
  218             panic("No free inodes in mem");
  219         }
  220         wait_on_inode(inode);
  221         while (inode->i_dirt) {
  222             write_inode(inode);
  223             wait_on_inode(inode);
  224         }
  225     } while (inode->i_count);
  226     memset(inode,0,sizeof(*inode));
  227     inode->i_count = 1;
  228     return inode;
  229 }

讲解

  代码很清晰,尝试从内存i节点表中找到一个引用计数为0的表项。如果没找到则出错停机。找到的话等待该i节点解锁并把i节点信息同步到高速缓冲区(write_inode)。最后把i节点所有结构清零返回。
  需要注意的是没有找到空闲i节点的处理,这里是打印节点表的每个设备号和节点号然后停机。这么处理的原因在于,linux规定了内存表大小和最大同时打开文件数量是相同的,如果打开超出最大数量的文件时会在sys_open函数内就直接返回错误,从而理论上永远不会在内存表节点全部使用的情况下再获取空闲i节点,如果这样做则说明内核出错了,那么做停机处理。

read_inode

前置知识

  我们要知道文件系统整体的结构,引导块+超级块+i节点位图块+盘块位图块+i节点块+数据块。如下图
在这里插入图片描述

代码

      #define BLOCK_SIZE 1024
	  #define INODES_PER_BLOCK ((BLOCK_SIZE)/(sizeof (struct d_inode)))
  297 static void read_inode(struct m_inode * inode)
  298 {
  299     struct super_block * sb;
  300     struct buffer_head * bh;
  301     int block;
  302
  303     lock_inode(inode);
  304     if (!(sb=get_super(inode->i_dev)))
  305         panic("trying to read inode without dev");
  306     block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
  307         (inode->i_num-1)/INODES_PER_BLOCK;
  308     if (!(bh=bread(inode->i_dev,block)))
  309         panic("unable to read i-node block");
  310     *(struct d_inode *)inode =
  311         ((struct d_inode *)bh->b_data)
  312             [(inode->i_num-1)%INODES_PER_BLOCK];
  313     brelse(bh);
  314     if (S_ISBLK(inode->i_mode)) {
  315         int i = inode->i_zone[0];
  316         if (blk_size[MAJOR(i)])
  317             inode->i_size = 1024*blk_size[MAJOR(i)][MINOR(i)];
  318         else
  319             inode->i_size = 0x7fffffff;
  320     }
  321     unlock_inode(inode);
  322 }

讲解

  304行首先拿到该设备的超级块(304行)
  306行根据超级块中的记录算出该节点的信息在哪个盘块上。2 + sb->s_imap_blocks + sb->s_zmap_blocks可以算出第一个存放i节点的盘块号。如i节点号是1024,第一个存放i节点的盘块是12,每个盘块上可以存放64个i节点,那么该i节点就存放在第(1023-1)/64+12=75个盘块上.
  308行然后读入该盘块中的数据
  310-312完成赋值操作,使磁盘i节点上的信息赋值给内存i节点。
  314-320行完成对块设备文件的特殊处理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值