i节点

以Linux 0.11为实例,兴趣所至,个人总结,不保证正确性。。。


[i节点]
     i节点是表征文件的方式,因此在linux 0.11内核中,有一系列专门操作i节点的函数,在fs/inode.c中。与i节点进程同步相关的函数,主要是wait_on_inode、lock_inode和unlock_inode,这几个函数都比较简单。以lock_inode函数为例,
static  inline  void  lock_inode(  struct  m_inode * inode)
{
                cli();
                  while  (inode->i_lock)
                                sleep_on(&inode->i_wait);
                inode->i_lock=1;
                sti();
}
在进入函数时,先用cli关闭中断。如果这个i节点已经被加锁,那让当前进程睡眠在这个i节点上。等待这个i节点被解锁,然后当前进程将这个i节点加锁。最后退出函数之前,打开中断。unlock_inode与lock_inode执行的动作相反,将这个i节点解锁,然后唤醒等待在这个i节点上的进程。

系统在启动的时候,会初始化一个i节点的数组inode_table,这个数组中的i节点就是系统全部可用的i节点。iget是根据设备号和i节点号获取i节点的入口函数,get_empty_inode是从inode_table数组中获取一个空的i节点的入口。get_pipe_inode是获取一个管道设备的i节点,可以把管道看成一个虚拟的设备,所以也需要一个i节点来表示。读写i节点的入口函数是read_inode和write_inode,在进行i节点的读写之前,要先设置好待读写的i节点的设备号和i节点号。对于文件的i节点,还需要保存相应的文件数据,通过_bmap来实现。

[get_empty_inode]
get_empty_inode的作用是从inode_table中获取一个空的inode。如果有必要,就将数据同步到磁盘上去。并且设置i节点各个字段的初值。实现原理就是对inode_table做一次遍历,找到空闲的i节点。代码如下
struct  m_inode * get_empty_inode( void  )
{
                  struct  m_inode * inode;
                  static  struct  m_inode * last_inode = inode_table; //inode_table在inode.c中定义, struct  m_inode inode_table[NR_INODE]={{0,},};
                  int  i;

                  do  {
                                inode = NULL;
                                  for  (i = NR_INODE; i ; i--) {
                                              /*
                                       如果last_inode已经指向了inode_table数组的最后一项,就让它重新指向inode_table的第一项
                                       保证外围的for语句在最坏的情况下能够将数组整个遍历一遍
                                    */
                                                  if  (++last_inode >= inode_table + NR_INODE)
                                                                last_inode = inode_table;
                                      /*
                                        虽然说,找到一个空闲的i节点只需要i_count为0即可。i_dirt和i_lock都为0的话,就更好了。i_dirt为0,表明在
                                        使用这个i节点之前不需要就i节点数据写入磁盘。i_lock为0,说明不需要等待其它进程释放这个i节点
                                        */
                                                  if  (!last_inode->i_count) {
                                                                inode = last_inode; 
                                                                  if  (!inode->i_dirt && !inode->i_lock)
                                                                                  break ;
                                                }
                                }
                                  if  (!inode) {   //遍历完之后没有找到空闲的i节点,表明系统中的i节点已经全部被占用
                                                  for  (i=0 ; i<NR_INODE ; i++)
                                                                printk(  "%04x: %6d\t" ,inode_table[i].i_dev,
                                                                                inode_table[i].i_num);
                                                panic(  "No free inodes in mem" );
                                }
                                /*
                       能运行到这里说明能找到空闲的i节点,剩下的工作就是看是否需要先把i节点数据写入磁盘,等待解锁了  
                        */
                                wait_on_inode(inode);
                                  while  (inode->i_dirt) {
                                                write_inode(inode);
                                                wait_on_inode(inode);
                                }
                }  while  (inode->i_count); //在进程睡眠期间,如果这个i节点又被其它进程所用。那就需要重新寻找了
              /*
                   到此,找到了一个空的i节点,没有被加锁,也不再需要写入磁盘了。剩下就做一些初始化字段值的工作即可
               */
                memset(inode,0,  sizeof (*inode));
                inode->i_count = 1;
                  return  inode;
}

[iget]
      iget函数需要两个参数,设备号和i节点号。它的作用是根据设备号和i节点号,获取相应的i节点信息。在寻找时,如果存在设备和i节点号的i节点,就使用这个i节点。否则,就使用一个空的i节点。
struct  m_inode * iget( int  dev,  int  nr)
{
                  struct  m_inode * inode, * empty;

                  if  (!dev)
                                panic(  "iget with dev==0"  );
                empty = get_empty_inode(); //先获取一个空的i节点
                inode = inode_table;
                  while  (inode < NR_INODE+inode_table) {
                                  if  (inode->i_dev != dev || inode->i_num != nr) {
                                                inode++;
                                                  continue  
                                }
                         /*
                              如果能找到已有的i节点的设备号和i节点号都对应,那么就等待这个i节点解锁
                              在等待解锁的过程中,这个i节点的内容可能发生变化
                              所以解锁后,需要再次确认i节点的设备号和i节点号是否就是我们所需要的
                         */
                                wait_on_inode(inode);
                                  if  (inode->i_dev != dev || inode->i_num != nr) {
                                                inode = inode_table;
                                                  continue  ;
                                }
                                inode->i_count++;  //here!找到了设备号和i节点号都对应的i节点,就是所需要的
                                  /*
                               i_mout为1表示这个i节点是否是其它设备/文件系统的挂载节点。如果是其它设备/文件系统的挂载节点,就需要用这个设备超级块
                                上的设备号来重新获取i节点,并且这是要获取的i节点号为1,获取这个文件系统的根节点。
                                */
                                  if  (inode->i_mount) { 
                                                  int  i;
                                                  for  (i = 0 ; i<NR_SUPER ; i++)  //找到对应的超级块
                                                                  if  (super_block[i].s_imount==inode)
                                                                                  break  ;
                                                  if  (i >= NR_SUPER) { 
                                                                printk(  "Mounted inode hasn't got sb\n"  );
                                                                  if  (empty)
                                                                                iput(empty);
                                                                  return  inode;
                                                }
                                                iput(inode); //前面找到的i节点被释放,重新寻找
                                                dev = super_block[i].s_dev;
                                                nr = ROOT_INO; // #define ROOT_INO 1
                                                inode = inode_table;
                                                  continue   //
                                }   //end of if
                                  if  (empty)  // 如果 找到了设备号和i节点号都对应的i节点,最开始获取的空的i节点就要被释放
                                                iput(empty);
                                  return  inode;
                }  //end of while
                  /*
                没有找到设备号和i节点号都对应的i节点,就是用那个空的i节点,设置好初值
                */
                  if  (!empty) 
                                  return  (NULL);
                inode=empty;
                inode->i_dev = dev;
                inode->i_num = nr;
                read_inode(inode);  //从磁盘读取i节点信息
                  return  inode;
}

[iput]
iput释放一个i节点。根据实际情况,可能需要将数据写回磁盘,或者释放磁盘空间
void  iput( struct  m_inode * inode)
{
                  if  (!inode)
                                  return  ;
                wait_on_inode(inode);
                  if  (!inode->i_count)
                                panic(  "iput: trying to free free inode"  );
                  /*
               管道i节点比较特殊,它涉及到两个进程。读进程和写进程。由于管道是一个虚拟设备,管道i节点的i_size字段表示的是这个管道i节点实际使                  
             用的物理页的其实地址。
                */
                  if  (inode->i_pipe) {
                                wake_up(&inode->i_wait);
                                  /*
                            管道i节点涉及到两个进程,所以引用次数减一之后,如果不为零,就不用释放内存空间
                                */
                                  if  (--inode->i_count)
                                                  return  ;
                                free_page(inode->i_size);
                                inode->i_count=0;
                                inode->i_dirt=0;
                                inode->i_pipe=0;
                                  return  ;
                }
                  if  (!inode->i_dev) {
                                inode->i_count--;
                                  return  ;
                }
                  /*
               S_ISBLC宏表示这个i节点是否是设备的i节点。即这个i节点用来表示一个“设备文件”。如果是,那么i_zone[0]中就存放的是设备号
                */
                  if  (S_ISBLK(inode->i_mode)) {
                                sync_dev(inode->i_zone[0]);
                                wait_on_inode(inode);
                }
repeat:
                  if  (inode->i_count>1) {
                                inode->i_count--;
                                  return  ;
                }
                  /*
               i_nlinks为0,就要释放i节点,如果有需要就要释放磁盘空间(当这个i节点代表的是一般的文件或目录时。存储文件数据和目录数据的逻辑块需要被释放掉。truncate函数实现释放磁盘的功能。它会首先进行是否为一般文件或目录的i节点检查
                */
                  if  (!inode->i_nlinks) {
                                truncate(inode);
                                free_inode(inode);
                                  return  ;
                }
                  /*
             i节点的内容被修改,那么就需要把内容同步到磁盘上
                */
                  if  (inode->i_dirt) {
                                write_inode(inode);             
                                wait_on_inode(inode);
                                  goto  repeat;
                }
                inode->i_count--;
                  return  ;
}

[读写i节点]
      读写i节点的函数分别是read_inode和write_inode。它们的作用分别是根据i节点的设备号和i节点号,将磁盘上的数据读入到i节点中,或者把i节点的数据写入磁盘。当然,都是通过中间的高速缓存来实现的。
     在对一个i节点进行read_inode调用之前,需要首先设定好这个i节点的设备号和i节点号。同理也是write_inode。
static   void  read_inode(  struct  m_inode * inode)
{
                  struct  super_block * sb;
                  struct  buffer_head * bh;
                  int  block;

                lock_inode(inode);
              /*
                get_super是根据设备号,获取这个设备的超级快的数据。
               linux 0.11中定义了一个超级快数据 struct  super_block super_block[NR_SUPER];
               设备在挂载的时候,会把超级快的数据加入到这个数组中。get_super的实现就是遍历这个数组,找到所需的超级块为止
               */
                  if  (!(sb=get_super(inode->i_dev))) 
                                panic(  "trying to read inode without dev"  );
                  /*
                这里是根据i节点号计算这个i节点所在的逻辑块的块号。因为磁盘是块设备,所以,对磁盘数据的读写都是以块为单位的。在逻逻辑块的编号上,从小到大是:引导块、超级块、i节点位图区、逻辑块节点位图区。然后才是i节点区。所以是2(超级块+引导块)+s_imap_blocks(i节点位图区所占的逻辑块的快数)+s_zmap_blocks(逻辑块位图区所占的逻辑块的块数)+(i_num-1)/每个逻辑块的i节点的个数
                */
                block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
                                (inode->i_num-1)/INODES_PER_BLOCK;
                  if  (!(bh=bread(inode->i_dev,block)))  //根据设备号和逻辑块号,读取整个逻辑块的数据。(其实是读入到高速缓存中)
                                panic(  "unable to read i-node block"  );
                  /*
                  磁盘上存放的i节点数据是磁盘上i节点,对应的数据结构是d_inode。而内存中的i节点要比磁盘上i节点的数据结构多一些数据项,但在前面部分是一样的。将整个磁盘块看成磁盘i节点数组,根据i节点号计算索引,读取数据。最后,数据读取完成后,释放缓存,解锁这个i节点
                 */
                *(  struct  d_inode *)inode =
                                ((  struct  d_inode *)bh->b_data)
                                                [(inode->i_num-1)%INODES_PER_BLOCK];
                brelse(bh);
                unlock_inode(inode);
}
/*

*/
static   void  write_inode(  struct  m_inode * inode)
{
                  struct  super_block * sb;
                  struct  buffer_head * bh;
                  int  block;

                lock_inode(inode);  
                  /*
               i_dirt为0表明i节点读入内存后,内容没有被修改,所以不需要重新写回磁盘
                */
                  if  (!inode->i_dirt || !inode->i_dev) {
                                unlock_inode(inode);
                                  return  ;
                }
                  if  (!(sb=get_super(inode->i_dev)))
                                panic(  "trying to write inode without device"  );
                  /*
                      计算i节点所在的逻辑块的编号               
                */
                block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
                                (inode->i_num-1)/INODES_PER_BLOCK;
                  /*
                读取逻辑块数据
                 */
                  if  (!(bh=bread(inode->i_dev,block)))
                                panic(  "unable to read i-node block"  );
                  /*
               写入i节点数据,实际上是写到高速缓存
                */
                ((  struct  d_inode *)bh->b_data)
                                [(inode->i_num-1)%INODES_PER_BLOCK] =
                                                *(  struct  d_inode *)inode;
                  /*
               b_dirt设为1,表明内容被修改。如果有需要,就将数据同步到磁盘
                */
                bh->b_dirt=1;
                inode->i_dirt=0;
                brelse(bh);
                unlock_inode(inode);
}
  

你可能感兴趣的:(linux,linux,linux,磁盘,i节点)