下面我们来看另一个”映射”函数,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的同事<personname w:st="on"><span lang="EN-US" style="FONT-FAMILY: Verdana">Hugh Dickins</span></personname>的话说就是,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()申请的内存本身就是映射好了的内存,不过这都无所谓.)