Linux那些事儿之我是Block层(12)传说中的内存映射(下)

下面我们来看另一个 映射 函数 ,blk_rq_map_kern(). 当我们在设备驱动内部或者 scsi mid-level 要发送 scsi 命令给设备的时候 , 我们会调用这个函数 . 回首往事 , 当年在讲 scsi 命令的时候 , scsi_execute_req() 调用了 scsi_execute() 之后 ,scsi_execute() 中就会调用 blk_rq_map_kern() 函数 . 正常情况下它应该返回 0, 在当年的 scsi_execute() ,189 , 判断如果 bufflen 不为 0 blk_rq_map_kern() 也不为 0, 就毫不犹豫的跳出函数 , 之所以如此果断 , 是因为 , 如果 bufflen 不为 0, 则说明这次 scsi 命令需要传输数据 , 既然需要传输数据 , 就需要得到 bio 的支持 , blk_rq_map_kern 的任务就是完成 rq bio,bio pages 的那种建交 . 它的返回值如果不为 0, 本身就说明出错了 , 那么既然它出错了 ,scsi 命令也就没必要往下执行了 .

Ok, 来看具体的代码吧 ,blk_rq_map_kern(), 来自 block/ll_rw_blk.c:

   2543 /**

   2544  * blk_rq_map_kern - map kernel data to a request, for REQ_BLOCK_PC usage

   2545  * @q:          request queue where request should be inserted

   2546  * @rq:         request to fill

   2547  * @kbuf:       the kernel buffer

   2548  * @len:        length of user data

   2549  * @gfp_mask:   memory allocation flags

   2550  */

   2551 int blk_rq_map_kern(request_queue_t *q, struct request *rq, void *kbuf,

   2552                     unsigned int len, gfp_t gfp_mask)

   2553 {

   2554         struct bio *bio;

    2555

   2556         if (len > (q->max_hw_sectors << 9))

   2557                 return -EINVAL;

   2558         if (!len || !kbuf)

   2559                 return -EINVAL;

   2560

   2561         bio = bio_map_kern(q, kbuf, len, gfp_mask);

   2562          if (IS_ERR(bio))

   2563                 return PTR_ERR(bio);

   2564

   2565         if (rq_data_dir(rq) == WRITE)

   2566                 bio->bi_rw |= (1 << BIO_RW);

   2567

   2568         blk_rq_bio_prep(q, rq, bio);

   2569         blk_queue_bounce(q, &rq->bio);

   2570         rq->buffer = rq->data = NULL;

   2571         return 0;

   2572 }

blk_rq_map_user() 不同的是 , 这里的 kbuf 是内核空间的 buffer. 这是一个让人大跌隐形眼镜的函数 , 因为既然 kbuf 是内核空间的 buffer, request 也是存在于内核空间 , 那么大家都是一条道上混的 , 何来映射之说 ? 事实上 , 虽然这个函数自称 ”map”, 但它和 map 根本没有关系 , 一个更合适的做法是把 map 这个词换成 associate, 没必要用 map 这么一个欺骗性的词 . 不过写代码的人这么做我们也没办法 , 毕竟在这个很黄很暴力的时代 , 整个社会系统都在鼓励谎言 , 掩盖真相 . 就像 CCTV, 虽然它声称自己代表民意 , 虽然它总是善于假借民意 , 但是它从来就没有代表过任何民意 . 它为了给 << 互联网视听节目服务管理规定 >> 出台造势 , 不惜借助并诱导张殊凡小朋友向全国人民说谎 , 以此来说明它们所鼓吹的是伟大光荣正确的 . 但最终只是让这个 13 岁的孩子受到伤害 , 只是让网络暴民们同仇敌忾 , 只是让大家更清楚的认识到那个所谓的全国收视率最高的节目不过是由一帮骗子导演的谎言恶剧 .

Ok, 甭管假不假 , 只有看代码是王道 . 首先 ,bio_map_kern() 来自 fs/bio.c:

    848 /**

    849  *      bio_map_kern    -       map kernel address into bio

    850  *      @q: the request_queue_t for the bio

    851  *      @data: pointer to buffer to map

    852  *       @len: length in bytes

    853  *      @gfp_mask: allocation flags for bio allocation

    854  *

    855  *      Map the kernel address into a bio suitable for io to a block

    856  *      device. Returns an error pointer in case of error.

    857  */

    858 struct bio *bio_map_kern(request_queue_t *q, void *data, unsigned int len,

    859                          gfp_t gfp_mask)

    860 {

    861         struct bio *bio;

    862

    863         bio = __bio_map_kern(q, data, len, gfp_mask);

    864          if (IS_ERR(bio))

    865                 return bio;

    866

    867         if (bio->bi_size == len)

    868                 return bio;

    869

    870         /*

    871          * Don't support partial mappings.

    872          */

    873          bio_put(bio);

    874         return ERR_PTR(-EINVAL);

    875 }

__bio_map_kern() 亦来自 fs/bio.c:

    811 static struct bio *__bio_map_kern(request_queue_t *q, void *data,

    812                                   unsigned int len, gfp_t gfp_mask)

    813 {

    814         unsigned long kaddr = (unsigned long)data;

    815         unsigned long end = (kaddr + len + PAGE_SIZE - 1) >> PAGE_SHIFT;

    816         unsigned long start = kaddr >> PAGE_SHIFT;

    817         const int nr_pages = end - start;

    818         int offset, i;

    819         struct bio *bio;

    820

    821          bio = bio_alloc(gfp_mask, nr_pages);

    822         if (!bio)

    823                 return ERR_PTR(-ENOMEM);

    824

    825         offset = offset_in_page(kaddr);

    826         for (i = 0; i < nr_pages; i++) {

    827                 unsigned int bytes = PAGE_SIZE - offset;

    828

    829                 if (len <= 0)

    830                         break;

    831

    832                 if (bytes > len)

    833                         bytes = len;

    834

    835                 if (bio_add_pc_page(q, bio, virt_to_page(data), bytes,

    836                                     offset) < bytes)

    837                         break;

    838

    839                 data += bytes;

    840                 len -= bytes;

    841                 offset = 0;

    842         }

    843

    844         bio->bi_end_io = bio_map_kern_endio;

    845         return bio;

    846 }

仔细对比一下这个函数与 __bio_map_user_iov(), 不难发现 , 本质的不同就是差了那个 get_user_page() 函数 , 而其它方面基本上是一样的 . 一样调用 bio_alloc 来申请 bio 的内存 , 一样调用 bio_add_pc_page() 来把 bio pages 们联系起来 .

说点内存管理的题外话 ,virt_to_page(), 它就是把一个虚拟地址转化为一个 page. 注意这里的 data 实际上就是前面 blk_rq_map_kern() 传下来的那个 kbuf, 如果我们追溯过去 , 去看 scsi_execute() 甚至回到 scsi_execute_req(), 我们去看那些调用 scsi_execute_req() 的地方 , 比如在 sd 模块中 ,sd_revalidate_disk() 函数中 , 有这么一行 ,

   1518         buffer = kmalloc(SD_BUF_SIZE, GFP_KERNEL | __GFP_DMA);

还有这么一行 ,

   1540                 sd_read_capacity(sdkp, buffer);

而我们知道 sd_read_capacity() 会调用 scsi_execute_req() 来执行 Read Capacity 命令 . 所以这个 kernel-space buffer 最初的来源就是这里这个 kmalloc. 对于 x86 系统来说 , 这段内存就是永久映射在内核空间的那个 896M 以下的内存 . 因为 virt_to_page 这个宏有硬性要求 , 它的参数必须是这个范围内的内存 .

最后 ,844 ,bio 的成员 bi_end_io 指向的是一个函数 , 这个函数将在这个 bio 对应的 io 操作结束的时候被调用 . 所以我们知道 , 在不久的可以看见的将来的某一天 ,bio_map_kern_endio() 函数会被调用 . 不过这个函数不干什么正经事罢了 , 来自 fs/bio.c:

    801 static int bio_map_kern_endio(struct bio *bio, unsigned int bytes_done, int err)

    802 {

     803         if (bio->bi_size)

    804                 return 1;

    805

    806         bio_put(bio);

    807         return 0;

    808 }

结束了 bio_map_kern() 之后 , 回到 blk_rq_map_kern(). 一样要调用 blk_rq_bio_prep() 来把 bio rq 联系起来 . 而之后调用 blk_queue_bounce() 是为了建立 bounce buffer, buffer pages 不适合这次 I/O 操作的时候需要利用 bounce buffer, 比如设备本身有限制 , 只能访问某些 pages.  

用我一个懂 Linux 的同事 Hugh Dickins 的话说就是 ,it is substituting bounce buffers if the buffer pages are unsuited to this I/O,e.g. device limited in the address range of pages it can access. 关于 blk_queue_bounce 我们就不多说了 . 毕竟是少数情况需要用到 . 如果需要 bounce buffer, 那么在 struct request_queue 中可以设置 , 因为它有一个成员 ,unsigned long bounce_pfn, 需要设置的可以调用函数 blk_queue_bounce_limit() 来设置 . 比如我们前面看到的 __scsi_alloc_queue() 函数 , 就调用了 blk_queue_bounce_limit().

   1581          blk_queue_bounce_limit(q, scsi_calculate_bounce_limit(shost));

如果你具有十足的八卦精神 , 如果你具有专业的八卦水准 , 那么你可以去看看这个 scsi_calculate_bounce_limit, 这个来自 drivers/scsi/scsi_lib.c 中的函数 .

   1547 u64 scsi_calculate_bounce_limit(struct Scsi_Host *shost)

   1548 {

   1549         struct device *host_dev;

   1550         u64 bounce_limit = 0xffffffff;

   1551

   1552         if (shost->unchecked_isa_dma)

   1553                 return BLK_BOUNCE_ISA;

   1554         /*

   1555          * Platforms with virtual-DMA translation

   1556           * hardware have no practical limit.

   1557          */

   1558         if (!PCI_DMA_BUS_IS_PHYS)

   1559                 return BLK_BOUNCE_ANY;

   1560

   1561         host_dev = scsi_get_device(shost);

   1562         if (host_dev && host_dev->dma_mask)

   1563                 bounce_limit = *host_dev->dma_mask;

   1564

   1565         return bounce_limit;

   1566 }

基本上对于 scsi 设备来说 , 需要不需要 bounce buffer, 主要得由 scsi host 说了算 , 因为 scsi 的世界里 ,host 是一家之主 ,device 是从属于 host . 就好比张斌的那些女人们能不能被扶正 , 能不能从第五者变成第四者 , 能不能从第四者变成第三者 , 关键还得张斌说了算 , 因为在紫薇大闹央视发布会这台戏后 , 真正的主角还是张斌 .

最后总结一下 ,blk_rq_map_user() blk_rq_map_kern(), 其实我还是那句话 ,map 这个词用得不是很合适 , 更好一点应该叫 associate, 因为在这两个函数中 , 映射并不是最主要的 , 最主要的是联系 , 就是说甭管你是用户空间的 buffer 还是内核空间的 buffer, Block 层都不认 , 我只认 bio, 我的这些函数只和 bio 打交道 . 这种情况生活中也很常见 , 就比如火车上的乘务员和列车长们在查票的时候 , 如果遇到残疾人 , 他们的态度一定是只认证不认人 . 我想我们没有理由忘记当年那辆开往西安的火车上 , 那位列车长面对那个只有半个脚掌 , 那个买了一张和残疾人票一样价格的票的中年人时 , 说的那句铿锵有力的话 :” 我们只认证不认人 ! 有残疾证就是残疾人 , 没有残疾证怎么能证明你是残疾人啊 ?”

好在开源社区的人没有这么无情 , 在他们看来 , 虽然我们要的是 bio, 不是 buffer, 但是毕竟 bio 可以和 page 有联系 ,page 可以和线性地址有联系 , 所以最终我们的解决方案就是通过这两个函数让 buffer 或者说让 buffer 所对应的地址和 bio 联系起来 , 这才是根本 , 而映射只是达到这一目的所采取的手段 , 并且只是用户空间的 buffer 才有此需求 .( 当然如果你喜欢钻牛角尖 , 那你也可以说内核空间的 buffer 也是映射好了的 , 因为 kmalloc() 申请的内存本身就是映射好了的内存 , 不过这都无所谓 .)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值