Linux高速缓存详解(二)

[高速缓存的写操作]
对高速缓存的写操作,主要来自于其它函数对高速缓存的使用。写操作的使用方式很多,例如在inode.c的write_node函数。write_node函数在调用是需要传递一个指向inode节点的指针,并且设置好这个i节点的对应的设备号和节点号。write_node函数的作用是将一个i节点的信息写入设备中(其实是写入高速缓存中)。这里摘抄部分与高速缓存相关的部分
                        if  (!(sb=get_super(inode->i_dev)))
                                panic(  "trying to write inode without device"  );
              
                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节点所在逻辑块的高速缓存,bh指向这个高速缓存头*/
                ((  struct  d_inode *)bh->b_data)
                                [(inode->i_num-1)%INODES_PER_BLOCK] =
                                                *(  struct  d_inode *)inode;
              
                bh->b_dirt=1;
                inode->i_dirt=0;
                brelse(bh);
对高速缓存进行写操作的部分为 ((   struct  d_inode *)bh->b_data) [(inode->i_num-1)%INODES_PER_BLOCK] =  *(   struct  d_inode *)inode;
由于之前读入高速缓存的数据是i节点所在的逻辑块的整块数据,而这个逻辑块是由许多的设备i节点组成的。因此可以将这个逻辑块的数据看成设备上i节点的数组。所以(struct d_inode*)bd->b_data,将缓存数据块当作设备i节点数组来了解。inode->i_num%INODES_PER_BLOCK,根据i节点号计算这个i节点在设备i节点数组上的索引。最后 ((  struct  d_inode *)bh->b_data) [(inode->i_num-1)%INODES_PER_BLOCK] =  *(  struct  d_inode *)inode;将i节点数据写入了缓存中。在对缓存写操作结束之后,bh->b_dirt=1设置缓存信息,表明缓存中数据被修改,其它进程如果需要使用这个缓存,就需要先把数据同步到设备中。brelse(bh)在缓存使用结束后释放缓存,在后面将会介绍这个函数的代码实现。

[高速缓存的管理函数]
      高速缓存的管理包括高速缓存队列的管理,即在前面getblk中看到的 insert_into_queues和remove_from_queues;释放一个高速缓存块函数brelse函数;指定某设备对应的高速缓存失效的invalidate_buffers函数;将高速缓存数据与设备同步的sync_dev函数。其它与高速缓存相关的操作函数这里就不多做说明了。
     insert_into_queues是将缓存块插入到队列中,将缓存块插入到空闲链表的尾部。根据缓存块中是否设置了设备号来决定是否要将其插入到散列表中。代码如下
      static   inline  void  insert_into_queues(  struct  buffer_head * bh)
{
                  /* 插入的时候,是将缓存块插入到空闲链表的尾部 */
                bh->b_next_free = free_list;
                bh->b_prev_free = free_list->b_prev_free;
                free_list->b_prev_free->b_next_free = bh;
                free_list->b_prev_free = bh;
                  /* put the buffer in new hash-queue if it has a device */
                bh->b_prev = NULL;
                bh->b_next = NULL;
                  if  (!bh->b_dev)
                                  return  ;
            /*只有设置了设备号(设备号不为0)的缓存块才会加入到散列表中去。*/
                bh->b_next = hash(bh->b_dev,bh->b_blocknr);
                hash(bh->b_dev,bh->b_blocknr) = bh;
                bh->b_next->b_prev = bh;
}
     remove_from_queues是将缓存块从链表中移除去。同时从空闲链表和散列数组的链表中移走。代码如下
      static   inline   void  remove_from_queues(   struct  buffer_head * bh)
{
                  /* 如果在散列表上,就把这个节点从散列表中删除 */
                  if  (bh->b_next)  
                                bh->b_next->b_prev = bh->b_prev;
                  if  (bh->b_prev)
                                bh->b_prev->b_next = bh->b_next;
                  if  (hash(bh->b_dev,bh->b_blocknr) == bh) //看这个节点是否是散列表中所在链表的第一个节点
                                hash(bh->b_dev,bh->b_blocknr) = bh->b_next;
                  /*
                   从空闲链表中将这个节点删除。所有的节点应该都是在空闲链表中的,所以这里做个检查
                       remove_from_queues要和insert_into_queues配套使用,才能保证所有的缓存块都在空闲链表中
                   */
                  if  (!(bh->b_prev_free) || !(bh->b_next_free))
                                panic(  "Free block list corrupted"  );
                bh->b_prev_free->b_next_free = bh->b_next_free;
                bh->b_next_free->b_prev_free = bh->b_prev_free;
                  if  (free_list == bh)  //看这个节点是否是空闲链表的第一个节点
                                free_list = bh->b_next_free;
}
     
brelse释放进程使用的一个缓存块,主要工作是将这个缓存块的应用数减一,注意这里只是将应用次数减一,并没有保证释放后将这个缓存块变为空闲的。然后唤醒等待在缓存上的进程。代码如下
void  brelse( struct  buffer_head * buf)
{
                  if  (!buf)
                                  return  ;
                wait_on_buffer(buf);
                  if  (!(buf->b_count--)) //将缓存块的应用次数减1.如果这个缓存块本身已经是空闲的了,释放的时候就会导致系统down掉
                                panic(  "Trying to free free buffer"  );
                wake_up(&buffer_wait);  /唤醒等待在这个缓存块上的进程
}
唤醒等待进程函数wake_up在sched.c中定义,调用这个函数需要传递一个执行进程的指针的地址,这个函数很短。只是将进程的状态变为可执行的,然后将指针置为空。代码如下
void  wake_up( struct  task_struct **p) //p是指向进程的指针的地址
{
                  if  (p && *p) {  //将进程的状态设为可运行,然后将指针设为NULL
                                (**p).state=0; 
                                *p=NULL;
                }
}
  在brelse中调用wake_up之后的效果是buffer_wait指向的进程的状态变为可运行,然后buffer_wait=NULL

invalidate_buffers有个设备号的参数,invalidate_buffers的作用是将指定设备号上的缓存变成无效状态。代码如下
void   inline  invalidate_buffers(  int  dev)
{
                  int  i;
                  struct  buffer_head * bh;

                bh = start_buffer;
                  for  (i=0 ; i
                                  if  (bh->b_dev != dev) //只对设备号相同的缓存块做操作
                                                  continue  ;
                                wait_on_buffer(bh); //首先要等待这个缓存块解锁。如果这个缓存块没有被加锁,那就直接往后执行
                                  if  (bh->b_dev == dev)  //在进程睡眠期间缓存块的内容可能被修改
                                                bh->b_uptodate = bh->b_dirt = 0; 
     /*
               b_uptodate 为0,这样其它进程使用这个设备的数据时,就会去将设备的数据同步到缓存中。
              b_dirt为0,其它进程在使用这个缓存之前就不需要将缓存数据写入设备了
          */
                }
}

sync_dev函数需要有个参数指定要同步的设备号,它的作用是将高速缓存中的数据同步到设备中去。所有与此设备相关的高速缓存的数据都要同步。代码如下
int  sync_dev( int  dev)
{
                  int  i;
                  struct  buffer_head * bh;

                bh = start_buffer;
                  for  (i=0 ; i
                                  if  (bh->b_dev != dev) //比较设备号,设备号相同时才做写入设备操作
                                                  continue  ;
                                wait_on_buffer(bh); 
                                  if  (bh->b_dev == dev && bh->b_dirt) //如果缓存中的数据没有被修改,那就不需要写入设备中,因为它们的数据已经是一致的了
                                                ll_rw_block(WRITE,bh); //ll_rw_block实现对设备的写操作,由WRITE指定动作为写动作
                }
                sync_inodes(); //同步缓存中的i节点信息,将i节点数据写入设备
                bh = start_buffer;
              /*
               再次同步一次,同步i节点时可能影响缓存中数据。
          */
                  for  (i=0 ; i
                                  if  (bh->b_dev != dev)
                                                  continue  ;
                                wait_on_buffer(bh);
                                  if  (bh->b_dev == dev && bh->b_dirt)
                                                ll_rw_block(WRITE,bh);
                }
                  return  0;
}
sys_sync函数与sync_dev功能相近,只不过sys_sync是将所有的缓存数据都进行同步。而不像sync_dev那样,只针对某一个设备的缓存块进行操作

你可能感兴趣的:(Linux)