linux那些事儿之我是block层(12)传说中的内存映射(下)...
时间:2010-08-09 来源:victorzhangl
下面我们来看另一个 ” 映射 ” 函数 ,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() 申请的内存本身就是映射好了的内存 , 不过这都无所谓 .)