2010-3-25 do_mpage_readpage 代码注释

本文深入解析Linux内核中的读页机制,介绍如何通过映射磁盘块构造尽可能大的I/O请求,并处理非连续块的情况。重点讲解了利用buffer_head进行磁盘映射的过程,包括如何处理文件洞及连续块的判断。

/*
 * 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.
 */
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;
    //将块的位数赋给blkbits
    const unsigned blkbits = inode->i_blkbits;
    //计算一个页面中的数据块数目
    const unsigned blocks_per_page = PAGE_CACHE_SIZE >> blkbits;
    //计算block 的大小
    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;


    //如果是一个缓存区页(PG_private被置位),跳转到confused
    //据目前的理解,PG_private置位意味着page中的缓冲区段在磁盘上不连续
    if (page_has_buffers(page))
        goto confused;

    //通过页的索引获得相对于文件头的首个块号
    block_in_file = (sector_t)page->index << (PAGE_CACHE_SHIFT - blkbits);
    //计算要传输的最后一个块号,这里nr_pages是1,即该页中的最后一块
    last_block = block_in_file + nr_pages * blocks_per_page;
    //计算文件的最后一块块号
    last_block_in_file = (i_size_read(inode) + blocksize - 1) >> blkbits;
    //应该传输的最后一块的块号
    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.
     */
    //nblock使用来干什么的啊?是指一个buffer_head所能指向的块数吗?
    //这么说来,buffer_head中的b_size所指的块大小和inode中的块大小不一样?
     nblocks = map_bh->b_size >> blkbits;
     
    //如果该缓冲区首部已经映射到磁盘(b_bdev、b_blocknr已经赋值),且……
    //注:首次传入的map_bh是经过clear_buffer_mapped()处理过的   
    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;
   
    //page_block初值为0,block_per_page是一页中的块数,即循环次数为页中块数,即对页中所有块进行处理
    //即:一次调用get_block()获得的连续的磁盘的块数不够一页的话,就反复调用
    while (page_block < blocks_per_page) {
        map_bh->b_state = 0;
        map_bh->b_size = 0;

        if (block_in_file < last_block) {
            //设置buffer_head中的size,这个buffer_head指向连续的块的块数,是指期望获得的最大连续块数?
            //每次计算这个,是因为要反复使用这个结构来获取b_blocknr字段
            map_bh->b_size = (last_block-block_in_file) << blkbits;
           
            //get_block()函数将会buffer_head的映射和size的赋值
            if (get_block(inode, block_in_file, map_bh, 0))
                goto confused;
            *first_logical_block = block_in_file;
        }

        //??map_bh没有映射,这应该就对应文件洞了
        if (!buffer_mapped(map_bh)) {
            //设置页全部映射到磁盘的标志为0
            fully_mapped = 0;
            //将文件洞记录下来
            if (first_hole == blocks_per_page)
                first_hole = page_block;
            page_block++;
            block_in_file++;
            clear_buffer_mapped(map_bh);
            //继续处理下一个块
            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;
        }
   
        //走到这步来了说明遇到了一个文件洞,但是之后的块又映射了,这时应该将遇到洞以前的块处理掉,故跳至confused
        if (first_hole != blocks_per_page)
            goto confused;        /* hole -> non-hole */

        /* Contiguous blocks? */
        //通过比较blocks数组中的上一个元素中的值和这次获得的(b_blocknr-1)比较,判断是否连续
        if (page_block && blocks[page_block-1] != map_bh->b_blocknr-1)
            goto confused;
        //计算出这个buffer_head指向的连续的块的块数
        nblocks = map_bh->b_size >> blkbits;
       
        //这个循环用来获取 块在磁盘上连续的页 中所有块 在磁盘中的块号
        //循环的结束条件是buffer_head所指向的块数减一至0 或 page中所有块在磁盘中的块号都以得出
        for (relative_block = 0; ; relative_block++) {
            if (relative_block == nblocks) {
                clear_buffer_mapped(map_bh);
                break;
            } else if (page_block == blocks_per_page)
                break;
            //为page中连续的块计算在磁盘中的编号,放入blocks数组中
            blocks[page_block] = map_bh->b_blocknr+relative_block;
            page_block++;
            block_in_file++;
        }
        bdev = map_bh->b_bdev;
    }

    if (first_hole != blocks_per_page) {
        char *kaddr = kmap_atomic(page, KM_USER0);
        memset(kaddr + (first_hole << blkbits), 0,
                PAGE_CACHE_SIZE - (first_hole << blkbits));
        flush_dcache_page(page);
        kunmap_atomic(kaddr, KM_USER0);
        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
    //如果bio不为空,而其中最后一个block与现在的不连续,则提交以前的bio
    if (bio && (*last_block_in_bio != blocks[0] - 1))
        bio = mpage_bio_submit(READ, bio);

alloc_new:
    if (bio == NULL) {
        //通过mpage_alloc创建新的bio
        // 2**9即512,blocks[0] << (blkbits - 9)得出首个扇区号
        //min_t(int, nr_pages, bio_get_nr_vecs(bdev))即,这个bio中的iovec个数
        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;
    }

    if (buffer_boundary(map_bh) || (first_hole != blocks_per_page))
        bio = mpage_bio_submit(READ, bio);
    else
        *last_block_in_bio = blocks[blocks_per_page - 1];
out:
    return bio;

confused:
    if (bio)
        bio = mpage_bio_submit(READ, bio);
    if (!PageUptodate(page))
            block_read_full_page(page, get_block);
    else
        unlock_page(page);
    goto out;
}

<think>首先,用户的问题是:我想了解C语言中语句 &#39;Speed_data[Mpage + 1] -= 50&#39; 的含义。我需要解释这个C语言语句。 关键部分是:Speed_data[Mpage + 1] -= 50 让我分解这个语句: - Speed_data:这看起来像是一个数组名。 - [Mpage + 1]:这是一个数组索引。索引是 Mpage + 1,所以它访问数组 Speed_data 的第 (Mpage + 1) 个元素。 - -= 50:这是一个赋值运算符,意思是“减法赋值”。它等价于 Speed_data[Mpage + 1] = Speed_data[Mpage + 1] - 50。 所以,整体含义是:将数组 Speed_data 中索引为 Mpage + 1 的元素减去 50,并将结果存回该元素。 现在,回忆C语言的基本知识: - 在C语言中,数组索引从0开始,所以索引 Mpage + 1 指的是从当前位置偏移1的元素。 - Mpage 很可能是一个变量,可能是整数类型。 - Speed_data 应该是一个数组,元素类型可能是整数或浮点数,但根据上下文,它可能是一个数值类型,因为我们在做减法。 用户提到了一个引用,但引用是关于AIX系统的进程查看命令,与C语言无关。用户说“上述引用仅供参考,并不是用户真正的需求”,所以我应该忽略那个引用,专注于当前问题。 我的回答需要: - 使用中文回答。 - 结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 - 对于数学表达式,使用$...$格式行内,或$$...$$独立公式。但这里是C语言语句,可能不需要数学公式,除非是解释表达式。 语句本身是代码,不是数学公式。所以,我应该直接解释它。 步骤解释: 1. 识别语句类型:这是一个赋值语句,使用了复合赋值运算符。 2. 解释组成部分: - Speed_data:数组名。 - Mpage:变量,用作索引的一部分。 - [Mpage + 1]:数组索引。 - -=:减法赋值运算符。 - 50:整数字面量。 3. 整体效果:将 Speed_data[Mpage + 1] 的值减少50。 4. 潜在含义:取决于上下文,但一般来说,它是在修改数组中的一个元素的值,减半百。 用户可能想知道为什么这样做,但由于没有更多上下文,我应该基于一般C语言知识解释。 最后,生成相关问题。系统指令说“生成相关问题”,所以我需要创建2-3个相关问题。 相关问题应该基于这个主题,比如C语言运算符、数组等。 例如: 1. -= 运算符在C语言中有什么作用? 2. 如何访问数组元素? 3. 在C语言中,数组索引是如何工作的? 但要让它们更相关。 可能的问題: 1. 除了 -=,C语言中还有哪些复合赋值运算符? 2. 如果数组索引越界会发生什么? 3. 如何声明和使用数组在C语言中? 现在,构建回答: - 先解释语句。 - 然后逐步分解。 - 确保语言清晰、易懂。 - 结尾添加相关问题。 </think> ### C语言语句 &#39;Speed_data[Mpage + 1] -= 50&#39; 的含义解释 在C语言中,语句 `Speed_data[Mpage + 1] -= 50` 是一个复合赋值语句,用于修改数组元素的值。下面我将逐步解释其含义,确保解释清晰可靠。 #### 1. **语句的总体含义** - 这条语句的作用是将数组 `Speed_data` 中索引为 `Mpage + 1` 的元素值减少50。 - 简单来说,它等价于: ```c Speed_data[Mpage + 1] = Speed_data[Mpage + 1] - 50; ``` - 这意味着:先读取数组 `Speed_data` 的第 `Mpage + 1` 个元素的值,减去50后,再将结果存回同一个位置。 #### 2. **分解语句的关键部分** - **`Speed_data`**:这是一个数组名。在C语言中,数组是存储多个相同类型元素的连续内存空间。例如: - 如果 `Speed_data` 被声明为 `int Speed_data[100];`,则它表示一个整数数组。 - 数组索引从0开始计数,所以索引 `Mpage + 1` 指向数组中的特定位置。 - **`[Mpage + 1]`**:这是数组索引表达式。 - `Mpage` 是一个变量(假设为整数类型),`Mpage + 1` 表示索引值比 `Mpage` 大1。 - 例如,如果 `Mpage` 的值是5,则 `Mpage + 1` 为6,语句会操作 `Speed_data[6]`。 - **`-= 50`**:这是一个复合赋值运算符。 - `-=` 是“减法赋值”运算符,它将右侧的值(这里是50)从左侧变量或元素中减去。 - 运算符优先级:`-=` 的优先级较低,所以表达式从左到右计算。先计算索引,再执行减法赋值。 - 数据类型:50 是一个整数字面量,因此 `Speed_data` 的元素类型通常应为整数(如 `int` 或 `float`),否则可能发生隐式类型转换,但建议确保类型一致以避免错误。 #### 3. **实际效果示例** 假设有以下代码片段: ```c int Speed_data[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; // 声明一个整数数组 int Mpage = 4; // 假设 Mpage 的值为4 Speed_data[Mpage + 1] -= 50; // 执行语句 ``` - 计算索引:`Mpage + 1 = 4 + 1 = 5`,因此操作 `Speed_data[5]`。 - 初始值:`Speed_data[5]` 是60。 - 执行后:`Speed_data[5]` 变为60 - 50 = 10。 - 数组变为:`{10, 20, 30, 40, 50, 10, 70, 80, 90, 100}`。 #### 4. **潜在注意事项** - **索引范围**:如果 `Mpage + 1` 超出数组边界(例如,数组大小为10,但索引为10或更大),会导致未定义行为(如程序崩溃或数据损坏)。C语言不检查数组越界,编程时需确保索引有效。 - **数据类型**:如果 `Speed_data` 是非整数类型(如 `float`),50会被转换为相应类型。但减法可能引入精度问题,例如浮点数减法。 - **上下文依赖**:在实际应用中,`Speed_data` 可能表示速度数据数组,`Mpage` 可能表示页面或位置索引。这条语句常用于调整数据值,例如在嵌入式系统中修改速度参数。但具体含义需结合程序上下文确定。 - **性能**:操作是O(1)时间复杂度,高效且常用。 #### 5. **C语言基础回顾** - **复合赋值运算符**:C语言支持 `+=`、`-=`、`*=` 等复合运算符,用于简化代码。例如: - `a -= b` 等价于 `a = a - b`。 - **数组索引**:索引表达式可以包含变量和计算,如 `array[index + offset]`。 如果您提供更多上下文(如 `Speed_data` 和 `Mpage` 的声明),我可以给出更精确的分析。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值