do_mpage_readpage函数详细分析

本文介绍了一个用于优化文件系统中页读取效率的函数do_mpage_readpage。该函数通过检查页内所有物理块是否连续来决定是批量提交读取请求还是逐块读取。文章详细解释了函数的工作流程及关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


/* 
 * This is the worker routine which does all the work of mapping the disk 
 * blocks and constructs largest possible bios, submits them for IO if the 
 * blocks are not contiguous on the disk. 
 * 
 * We pass a buffer_head back and forth and use its buffer_mapped() flag to 
 * represent the validity of its disk mapping and to decide when to do the next 
 * get_block() call. 
 */  
 
/*这个函数试图读取文件中的一个page大小的数据,最理想的情况下就是这个page大小 
的数据都是在连续的物理磁盘上面的,然后函数只需要提交一个bio请求就可以获取 
所有的数据,这个函数大部分工作在检查page上所有的物理块是否连续,检查的方法 
就是调用文件系统提供的get_block函数,如果不连续,需要调用block_read_full_page 
函数采用buffer 缓冲区的形式来逐个块获取数据*/  
/* 
     1、调用get_block函数检查page中是不是所有的物理块都连续 
     2、如果连续调用mpage_bio_submit函数请求整个page的数据 
     3、如果不连续调用block_read_full_page逐个block读取 
*/  
static   struct  bio * 
do_mpage_readpage( struct  bio *bio,  struct  page *page,  unsigned  nr_pages, 
                  sector_t *last_block_in_bio,  struct  buffer_head *map_bh, 
                   unsigned   long  *first_logical_block, get_block_t get_block) 

     struct  inode *inode = page->mapping->host; 
     const   unsigned  blkbits = inode->i_blkbits; 
     const   unsigned  blocks_per_page = PAGE_CACHE_SIZE >> blkbits; 
     const   unsigned  blocksize =  1  << blkbits; 
    sector_t block_in_file; 
    sector_t last_block; 
    sector_t last_block_in_file; 
    sector_t blocks[MAX_BUF_PER_PAGE]; 
     unsigned  page_block; 
     unsigned  first_hole = blocks_per_page; 
     struct  block_device *bdev = NULL; 
     int  length; 
     int  fully_mapped =  1
     unsigned  nblocks; 
     unsigned  relative_block; 
 
     if  (page_has_buffers(page)) 
         goto  confused; 
     /* 
    block_in_file 本page中的第一个block number 
    last_block 本page中最后一个block 的大小 
    last_block_in_file 文件大小求出文件的最后一个block 大小*/  
    block_in_file = (sector_t)page->index << (PAGE_CACHE_SHIFT - blkbits); 
    last_block = block_in_file + nr_pages *blocks_per_page; 
    last_block_in_file = (i_size_read(inode) + blocksize -  1 ) >> blkbits; 
 
     /*last_block 最后等于本次对这个page操作的最后一个block大小*/  
     if  (last_block > last_block_in_file) 
        last_block = last_block_in_file; 
    page_block =  0
 
     /* 
     * Map blocks using the result from the previous get_blocks call first. 
     */  
    nblocks = map_bh->b_size >> blkbits; 
     /*对于普通情况mpage_readpage调用下,map_bh只是一个临时变量不会走到 
    下面的分支*/  
     if  (buffer_mapped(map_bh) && block_in_file > *first_logical_block && 
            block_in_file < (*first_logical_block + nblocks)) 
    { 
         unsigned  map_offset = block_in_file - *first_logical_block; 
         unsigned  last = nblocks - map_offset; 
 
         for  (relative_block =  0 ; ; relative_block++) 
        { 
             if  (relative_block == last) 
            { 
                clear_buffer_mapped(map_bh); 
                 break
            } 
             if  (page_block == blocks_per_page) 
                 break
            blocks[page_block] = map_bh->b_blocknr + map_offset + 
                                 relative_block; 
            page_block++; 
            block_in_file++; 
        } 
        bdev = map_bh->b_bdev; 
    } 
 
     /* 
     * Then do more get_blocks calls until we are done with this page. 
     */  
    map_bh->b_page = page; 
     /*这个循环是比较关键的路径,理解这个函数至关重要 
    1、page_block从0开始循环,它表示在这个page内的block大小 
    2、调用get_block  函数查找对应逻辑块的物理块号是多少 
    3、如果遇到了文件空洞、page上的物理块不连续就会跳转到confused 
    4、将这个page中每个逻辑块对应的物理块都保存到临时的数组blocks[] 中*/  
     while  (page_block < blocks_per_page) 
    { 
        map_bh->b_state =  0
        map_bh->b_size =  0
 
         if  (block_in_file < last_block) 
        { 
            map_bh->b_size = (last_block - block_in_file) << blkbits; 
             if  (get_block(inode, block_in_file, map_bh,  0 )) 
                 goto  confused; 
            *first_logical_block = block_in_file; 
        } 
 
         if  (!buffer_mapped(map_bh)) 
        { 
            fully_mapped =  0
             if  (first_hole == blocks_per_page) 
                first_hole = page_block; 
            page_block++; 
            block_in_file++; 
             continue
        } 
 
         /* some filesystems will copy data into the page during 
         * the get_block call, in which case we don't want to 
         * read it again.  map_buffer_to_page copies the data 
         * we just collected from get_block into the page's buffers 
         * so readpage doesn't have to repeat the get_block call 
         */  
         if  (buffer_uptodate(map_bh)) 
        { 
            map_buffer_to_page(page, map_bh, page_block); 
             goto  confused; 
        } 
 
         if  (first_hole != blocks_per_page) 
             goto  confused;         /* hole -> non-hole */  
 
         /* Contiguous blocks? */  
         if  (page_block && blocks[page_block- 1 ] != map_bh->b_blocknr -  1
             goto  confused; 
        nblocks = map_bh->b_size >> blkbits; 
         for  (relative_block =  0 ; ; relative_block++) 
        { 
             if  (relative_block == nblocks) 
            { 
                clear_buffer_mapped(map_bh); 
                 break
            } 
             else   if  (page_block == blocks_per_page) 
                 break
            blocks[page_block] = map_bh->b_blocknr + relative_block; 
            page_block++; 
            block_in_file++; 
        } 
        bdev = map_bh->b_bdev; 
    } 
 
     /*如果发现文件中有洞,将整个page清0,因为文件洞的区域 
    物理层不会真的去磁盘上读取,必须在这里主动清零,否则 
    文件洞区域内容可能随机*/  
     if  (first_hole != blocks_per_page) 
    { 
        zero_user_segment(page, first_hole << blkbits, PAGE_CACHE_SIZE); 
         if  (first_hole ==  0
        { 
            SetPageUptodate(page); 
            unlock_page(page); 
             goto  out; 
        } 
    } 
     else   if  (fully_mapped) 
    { 
        SetPageMappedToDisk(page); 
    } 
 
     /* 
     * This page will go to BIO.  Do we need to send this BIO off first? 
     */  
     /*bio 为NULL,目前分析的场景可以跳过去*/  
     if  (bio && (*last_block_in_bio != blocks[ 0 ] -  1 )) 
        bio = mpage_bio_submit(READ, bio); 
 
alloc_new: 
     if  (bio == NULL) 
    { 
         /*重新分配一个bio结构体 
        blocks[0] << (blkbits - 9) 这个是page中第一个逻辑块的物理块号, 
        转换成物理扇区号*/  
        bio = mpage_alloc(bdev, blocks[ 0 ] << (blkbits -  9 ), 
                          min_t( int , nr_pages, bio_get_nr_vecs(bdev)), 
                          GFP_KERNEL); 
         if  (bio == NULL) 
             goto  confused; 
    } 
 
    length = first_hole << blkbits; 
     if  (bio_add_page(bio, page, length,  0 ) < length) 
    { 
        bio = mpage_bio_submit(READ, bio); 
         goto  alloc_new; 
    } 
 
    relative_block = block_in_file - *first_logical_block; 
    nblocks = map_bh->b_size >> blkbits; 
     if  ((buffer_boundary(map_bh) && relative_block == nblocks) || 
            (first_hole != blocks_per_page)) 
        bio = mpage_bio_submit(READ, bio); 
     else  
        *last_block_in_bio = blocks[blocks_per_page -  1 ]; 
out: 
     /*一切顺利,整个page中的物理块是相连的,返回一个bio*/  
     return  bio; 
 
confused: 
     if  (bio) 
        bio = mpage_bio_submit(READ, bio); 
     /*page 中的物理块不相连,没有办法一个一个buffer去读取出来 
    */  
     if  (!PageUptodate(page)) 
        block_read_full_page(page, get_block); 
     else  
        unlock_page(page); 
     goto  out; 
参考资源链接:[深入解析Linux内核文件Cache机制](https://wenku.youkuaiyun.com/doc/4mnxp5gp9h?utm_source=wenku_answer2doc_content) 在Linux系统中,预读机制是为了减少文件访问时的延迟,提升效率的关键技术之一。要了解这一机制,首先需要熟悉几个关键函数和数据结构,例如`__do_page_cache_readahead`,这是内核预读操作的核心函数。在具体分析之前,建议您查阅《深入解析Linux内核文件Cache机制》一书,它将为您提供关于cache机制的全面视角和深入理解。 当文件系统执行读操作时,若所需数据不在缓存中,则会触发预读机制。`__do_page_cache_readahead`函数就是在此时被调用,用于异步地从磁盘读取连续的数据块到页面缓存中。这一过程涉及到几个步骤: 1. 检查文件的`address_space`结构,以确定所需数据是否在缓存中。 2. 如果数据不在缓存中,`__do_page_cache_readahead`会通过文件系统提供的读取函数,从磁盘中加载数据块到缓存。 3. 在数据加载过程中,该函数会更新文件的预读状态,并安排后续的预读操作,以便于在后续的读操作中可以快速访问到这些数据。 为了确保数据正确加载和维护缓存的一致性,`__do_page_cache_readahead`还会与`address_space_operations`中的相关函数,如`readpages`和`mpage_readpages`等,紧密协作。这样,当应用程序再次请求这些数据时,可以直接从内存中的缓存读取,大大减少了I/O操作的次数和延迟。 通过合理配置预读参数和理解`__do_page_cache_readahead`的工作原理,我们可以显著提高文件访问的效率,尤其是在处理大文件或顺序读取频繁的场景下。对Linux系统性能优化感兴趣的话,《深入解析Linux内核文件Cache机制》将是一个宝贵的资源,它不仅阐述了预读机制,还包括了文件读写I/O流程和内存管理的其他重要方面。 参考资源链接:[深入解析Linux内核文件Cache机制](https://wenku.youkuaiyun.com/doc/4mnxp5gp9h?utm_source=wenku_answer2doc_content)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值