首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

read 系统调用剖析(4)Page cache 层的处理

read 系统调用剖析(4)Page cache 层的处理

从上文得知:ext2_readpage 函数是该层的入口点。该函数调用 mpage_readpage 函数,清单6显示了 mpage_readpage 函数的代码。
清单6 mpage_readpage 函数的代码
1
2
3
4
5
6
7
8
9
10
11
int mpage_readpage(struct page *page, get_block_t get_block)
{
    struct bio *bio = NULL;
    sector_t last_block_in_bio = 0;

    bio = do_mpage_readpage(bio, page, 1,
                         &last_block_in_bio, get_block);
    if (bio)
        mpage_bio_submit(READ, bio);
    return 0;
}




该函数首先调用函数 do_mpage_readpage 函数创建了一个 bio 请求,该请求指明了要读取的数据块所在磁盘的位置、数据块的数量以及拷贝该数据的目标位置——缓存区中 page 的信息。然后调用 mpage_bio_submit 函数处理请求。 mpage_bio_submit 函数则调用 submit_bio 函数处理该请求,后者最终将请求传递给函数 generic_make_request ,并由 generic_make_request 函数将请求提交给通用块层处理。
到此为止, page cache 层的处理结束。
通用块层的处理generic_make_request 函数是该层的入口点,该层只有这一个函数处理请求。清单7显示了函数的部分代码
清单7 generic_make_request 函数部分代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void generic_make_request(struct bio *bio)
{
    ……
    do {
        char b[BDEVNAME_SIZE];

        q = bdev_get_queue(bio->bi_bdev);
        ……
        block_wait_queue_running(q);

        /*
        * If this device has partitions, remap block n
        * of partition p to block n+start(p) of the disk.
        */
        blk_partition_remap(bio);

        ret = q->make_request_fn(q, bio);
    } while (ret);
}




主要操作:
  • 根据 bio 中保存的块设备号取得请求队列 q
  • 检测当前 IO 调度器是否可用,如果可用,则继续;否则等待调度器可用
  • 调用 q->make_request_fn 所指向的函数将该请求(bio)加入到请求队列中
到此为止,通用块层的操作结束。
IO 调度层的处理对 make_request_fn 函数的调用可以认为是 IO 调度层的入口,该函数用于向请求队列中添加请求。该函数是在创建请求队列时指定的,代码如下(blk_init_queue 函数中):
1
2
q->request_fn    = rfn;
blk_queue_make_request(q, __make_request);




函数 blk_queue_make_request 将函数 __make_request 的地址赋予了请求队列 q 的 make_request_fn 成员,那么, __make_request 函数才是 IO 调度层的真实入口。
__make_request 函数的主要工作为:
  • 检测请求队列是否为空,若是,延缓驱动程序处理当前请求(其目的是想积累更多的请求,这样就有机会对相邻的请求进行合并,从而提高处理的性能),并跳到3,否则跳到2
  • 试图将当前请求同请求队列中现有的请求合并,如果合并成功,则函数返回,否则跳到3
  • 该请求是一个新请求,创建新的请求描述符,并初始化相应的域,并将该请求描述符加入到请求队列中,函数返回
将请求放入到请求队列中后,何时被处理就由 IO 调度器的调度算法决定了(有关 IO 调度器的算法内容请参见参考资料)。一旦该请求能够被处理,便调用请求队列中成员 request_fn 所指向的函数处理。这个成员的初始化也是在创建请求队列时设置的:
1
2
q->request_fn    = rfn;
blk_queue_make_request(q, __make_request);




第一行是将请求处理函数 rfn 指针赋给了请求队列的 request_fn 成员。而 rfn 则是在创建请求队列时通过参数传入的。
对请求处理函数 request_fn 的调用意味着 IO 调度层的处理结束了。
块设备驱动层的处理request_fn 函数是块设备驱动层的入口。它是在驱动程序创建请求队列时由驱动程序传递给 IO 调度层的。
IO 调度层通过回调 request_fn 函数的方式,把请求交给了驱动程序。而驱动程序从该函数的参数中获得上层发出的 IO 请求,并根据请求中指定的信息操作设备控制器(这一请求的发出需要依据物理设备指定的规范进行)。
到此为止,块设备驱动层的操作结束。
块设备层的处理接受来自驱动层的请求,完成实际的数据拷贝工作等等。同时规定了一系列规范,驱动程序必须按照这个规范操作硬件。
后续工作当设备完成了 IO 请求之后,通过中断的方式通知 cpu ,而中断处理程序又会调用 request_fn 函数进行处理。
当驱动再次处理该请求时,会根据本次数据传输的结果通知上层函数本次 IO 操作是否成功,如果成功,上层函数解锁 IO 操作所涉及的页面(在 do_generic_mapping_read 函数中加的锁)。
该页被解锁后, do_generic_mapping_read() 函数就可以再次成功获得该锁(数据的同步点),并继续执行程序了。之后,函数 sys_read 可以返回了。最终 read 系统调用也可以返回了。
至此, read 系统调用从发出到结束的整个处理过程就全部结束了。
返回列表