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

来源:互联网 发布:海澜之家淘宝旗舰店 编辑:程序博客网 时间:2024/05/16 16:14

下面我们来看另一个映射函数,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不为0blk_rq_map_kern()也不为0,就毫不犹豫的跳出函数,之所以如此果断,是因为,如果bufflen不为0,则说明这次scsi命令需要传输数据,既然需要传输数据,就需要得到bio的支持,blk_rq_map_kern的任务就是完成rqbio,biopages的那种建交.它的返回值如果不为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()来把biopages们联系起来.

说点内存管理的题外话,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-spacebuffer最初的来源就是这里这个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()来把biorq联系起来.而之后调用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()申请的内存本身就是映射好了的内存,不过这都无所谓.)

 
原创粉丝点击