Linux page cache映射方式

一开始接触到Linux page cache时,就有一些疑问。Linux page cache很大,一般的文件读写是怎么通过虚拟内存访问的,一开始便想到的是通过mmap的机制,将文件映射到用户层的虚拟内存区间,然后通过触发page fault来从文件中读取,后来发现并不是这样,我们先来看下linux 的page cache的存储方式。

page cache是存放在struct inode中的,通过struct address_space(i_data)中的iru链表,串联起了该文件所有的page cache,所以这个page cache是一个全局性质的资源。

那么每个进程通过一般的文件的写过程进入内核态的时候,又是如何通过操作page cache的呢?我们之前提到过,每个进程拥有独立的4GB的虚拟内存区间(32位操作系统),其中低3GB是用户空间,高1GB是内核空间,在父进程在创建子进程时会将高1GB内核空间的页表拷贝,这样每个进程在内核空间中的映射都是一样的,可以说内核空间是所有进程的全局段。

既然page cache是全局属性。那么把page cache在内核空间申请行不行呢,答案是不行。内核空间内存太小,又存储这许多重要的资源,将page cache这么大的资源存储进来显然是不合适的。那么使用用户空间合不合适呢?那占用用户空间的虚拟内存显然是更不合适的。所以,内核访问page cache的方式实际上是通过高端内存访问。

关于高端内存的讲解,这里不说明,有兴趣的同学可以去了解下。

高端内存有一个问题,就是内核映射的高端内存是非常少的,内核在高端内存处建立的映射不能一直存在。我们看下处理page cache时,是怎么做的。

我们以Linux 2.4.0代码为例:(我看了下3.10.0的代码处理方式都一样)

文件的读写是通过sys_write系统调用实现的,这里我们省略大部分的流程:

|-sys_write()
    |--generic_file_write()
        |--while()        //循环处理每一个page cache
            |--__grab_cache_page()    //寻找page cache,找不到分配一个
            |--block_prepare_write()  //将数据读入page cache ,并和buffer head建立映射
            |--generic_commit_write() //往page cache中写入数据
//sys_write->generic_file_write->block_prepare_write 
 static int __block_prepare_write(struct inode *inode, struct page *page,        //做准备就是使有关记录块缓冲区的                                                                  
          unsigned from, unsigned to, get_block_t *get_block)                     //内容与设备上相关记录块的内容相一致                                                              
  {                                                                                                                                                                                 
      unsigned block_start, block_end;                                                                                                                                              
      unsigned long block;                                                                                                                                                          
      int err = 0;                                                                                                                                                                  
      unsigned blocksize, bbits;                                                                                                                                                    
      struct buffer_head *bh, *head, *wait[2], **wait_bh=wait;                                                                                                                      
      char *kaddr = kmap(page);                     //开始了高端内存的映射                                                                                                                                                 
                                                                                                                                                                                    
      blocksize = inode->i_sb->s_blocksize;                                                                                                                                         
      if (!page->buffers)                                                                                                                                                           
          create_empty_buffers(page, inode->i_dev, blocksize);                    //创建并初始化buffer_head                                                                         
      head = page->buffers;                                                                                                                                                         
                                                                                                                                                                                    
      bbits = inode->i_sb->s_blocksize_bits;                                      //数据块大小, 一般为10 ,1024                                                                     
      block = page->index << (PAGE_CACHE_SHIFT - bbits);                          //对于i386 为12-10 = 2, block为逻辑记录块号                                                       
                                                                                                                                                                                    
      for(bh = head, block_start = 0; bh != head || !block_start;                 //                                                                                                
          block++, block_start=block_end, bh = bh->b_this_page) {                                                                                                                   
          if (!bh)                                                                //                                                                                                
              BUG();                                                                                                                                                                
          block_end = block_start+blocksize;                                                                                                                                        
          if (block_end <= from)                                                                                                                                                    
              continue;                                                                                                                                                             
          if (block_start >= to)                                                                                                                                                    
              break;                                                                                                                                                                
          if (!buffer_mapped(bh)) {                                               //如果没有建立起映射                                                                              
              err = get_block(inode, block, bh, 1);                               //ext2_get_block                                                                                  
              if (err)                                                                                                                                                              
                  goto out;                                                                                                                                                         
              if (buffer_new(bh)) {                                                                                                                                                 
                  unmap_underlying_metadata(bh);                                                                                                                                    
                  if (Page_Uptodate(page)) {                                                                                                                                        
                      set_bit(BH_Uptodate, &bh->b_state);                                                                                                                           
                      continue;                                                                                                                                                     
                  }                                                                                                                                                                 
                  if (block_end > to)                                                                                                                                               
                      memset(kaddr+to, 0, block_end-to);                                                                                                                            
                  if (block_start < from)                                                                                                                                           
                      memset(kaddr+block_start, 0, from-block_start);                                                                                                               
                  if (block_end > to || block_start < from)                                                                                                                         
                      flush_dcache_page(page);                                                                                                                                      
                  continue;                                                                                                                                                         
              }                                                                                                                                                                     
          }                                                                                                                                                                         
          if (Page_Uptodate(page)) {                                                                                                                                                
              set_bit(BH_Uptodate, &bh->b_state);                                                                                                                                   
              continue;                                                                                                                                                             
          }                                                                                                                                                                         
          if (!buffer_uptodate(bh) &&     //对于已经建立起映射,检查记录块是否一致, 不一致就通过ll_rw_block从磁盘读取                                                               
               (block_start < from || block_end > to)) {                                                                                                                            
              ll_rw_block(READ, 1, &bh);                                                                                                                                            
              *wait_bh++=bh;                                                                                                                                                        
          }                                                                                                                                                                         
      }                                                                                                                                                                             
      /*                                                                                                                                                                            
       * If we issued read requests - let them complete.                                                                                                                            
       */                                                                                                                                                                           
      while(wait_bh > wait) {                                                                                                                                                       
          wait_on_buffer(*--wait_bh);                                                                                                                                               
          err = -EIO;                                                                                                                                                               
          if (!buffer_uptodate(*wait_bh))                                                                                                                                           
              goto out;                                                                                                                                                             
      }                                                                                                                                                                             
      return 0;                                                                                                                                                                     
  out:                                                                                                                                                                              
      return err;                                                                                                                                                                   
  }     

block_prepare_write是为文件的写入,准备好已经从磁盘读入数据的page cache,在创建好了page cache后,通过kmap建立高端内存的映射,那么该映射是如何解除的呢

我们来看下面的函数

//sys_write->generic_file_write->generic_commit_write 
 int generic_commit_write(struct file *file, struct page *page,                                                                                                                    
          unsigned from, unsigned to)                                                                                                                                               
  {                                                                                                                                                                                 
      struct inode *inode = page->mapping->host;                                                                                                                                    
      loff_t pos = ((loff_t)page->index << PAGE_CACHE_SHIFT) + to;                                                                                                                  
      __block_commit_write(inode,page,from,to);                                                                                                                                     
      kunmap(page);               //解除高端内存映射                                                                                                                                                  
      if (pos > inode->i_size) {                                                                                                                                                    
          inode->i_size = pos;                                                                                                                                                      
          mark_inode_dirty(inode);                                                                                                                                                  
      }                                                                                                                                                                             
      return 0;                                                                                                                                                                     
  }        

在实际写入page cache后,便解除了高端内存映射

也就是说处理一个page cache,就要建立一次高端内存映射,处理完成后,就解除对高端内存的映射

以上是自己的个人理解,如果有不对的地方,望指出,谢谢^  ^

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值