Linux那些事儿之我是U盘(40)彼岸花的传说(八) – fudan

对于use_sg为0的情况,我们接下来再看206行,offset是函数调用传递进来的参数,注释里说的很清楚,就是用来标志偏移量的,每次copy几个字节她就增加几,最大她也不能超过request_bufflen,这是显然的.usb_stor_access_xfer_buf()这个函数所做的事情就是从srb->request_buffer往buffer里边copy数据,或者反过来从buffer往srb->request_buffer,然后返回copy了多少个字节.对于offset大于等于request_bufflen的情况,当然就直接返回0了,因为request_buffer已经满了.

参数enum xfer_buf_dir dir标志的正是传输方向,这个数据类型是在某个猥琐的角落里被定义的,drivers/usb/storage/protocol.h中:

68 /* struct scsi_cmnd transfer buffer access utilities */ 69 enum xfer_buf_dir {TO_XFER_BUF, FROM_XFER_BUF};

其实是很简单的一个枚举数据类型,含义也很简单,一个表示向srb->request_buffer里边copy,TO_XFER_BUF,另一个表示从srb->request_buffer里边往外copy,FROM_XFER_BUF.(题外话:XFER就是TRANSFER的意思,老外喜欢这样缩写.刚进Intel的时候老板专门给了我一个excel文件,里边全是Intel内部广泛使用的英文缩写,不在Intel呆一段时间基本没法理解.)这里定义成枚举数据类型也是很有必要的,因为数据传输肯定得有且仅有一个方向,就像思念,只有一个方向,因为我思念的人未必思念我. 而此情此景,咱们传进来的是前者,所以209行判断之后会执行210行,从buffer里边copy cnt个字节到(unsigned char *) srb->request_buffer + *offset去.cnt在208行确定,min函数不用说也知道,取最小值,认真学过谭浩强老师那本书的同志们应该不会不熟悉这样的函数,不过linux内核里边确实有定义这个函数,include/linux/kernel.h中,不妨也列出来,仅供娱乐,毕竟咱们的故事太严肃了一点,娱乐性不够强:

203 /* 204 * min()/max() macros that also do 205 * strict type-checking.. See the 206 * "unnecessary" pointer comparison. 207 */ 208 #define min(x,y) ({ / 209 typeof(x) _x = (x); / 210 typeof(y) _y = (y); / 211 (void) (&_x == &_y); / 212 _x < _y ? _x : _y; }) 213 214 #define max(x,y) ({ / 215 typeof(x) _x = (x); / 216 typeof(y) _y = (y); / 217 (void) (&_x == &_y); / 218 _x > _y ? _x : _y; })

而208行比较的是一个buflen,一个srb->request_bufflen-*offset,咱们这次要传送的数据长度是buflen,但是显然也不能够超过后者,所以就取其中小的那个,调用memcpy copy,然后215行*offset加上copy的字节cnt,对于这种不采用scatter gather方式的传输,那么到这里就可以返回了,直接就到278行,返回cnt即可.

但是对于使用scatter gather方式的传输,情况当然不一样了.从224行开始往下看,显然如咱们所说,得定义一个struct scatterlist结构体的指针,由于struct scatterlist是和体系结构有关的,作为曾经的Intel人,我没有任何犹豫的应该以i386的为例,include/i386/scatterlist.h:

4 struct scatterlist { 5 struct page *page; 6 unsigned int offset; 7 dma_addr_t dma_address; 8 unsigned int length; 9 };

这个结构并不复杂,其中page指针通常指向将要进行scatter gather操作的buffer.而length表示buffer的长度,offset表示buffer在page内的偏移量.225行,定义一个struct scatterlist指针sg,然后令她指向(struct scatterlist*)srb->request_buffer+*index,搜索一下内核代码就可以知道,每次*index都是被初始化为0然后才调用usb_stor_access_xfer_buf()的,233行,cnt设为0,234行开始进入循环,循环的条件是cnt小于buflen,同时*index小于srb->use_sg, srb->use_sg咱们刚才说过了,只要她不是0,那么她里边的冬冬就代表了scatter gather传输时数组元素的个数.

观众朋友们请注意,225行这里让sg等于srb->request_buffer,(当然还要加上*index,如果index不为0的话),那么request_buffer究竟是什么?对于使用scatter/gather传输的情况,request_buffer里边实际上是一个数组,每一个元素都是一个struct scatterlist的指针,而每一个scatterlist指针的page里边包含了一些page(而不是一个page),而offset里边包含的是每一个DMA buffer的总的偏移量,她由两部分组成,高位部分标志着page号,低位部分标志着具体某个page中的偏移量,高位低位由PAGE_SHIFT宏来划分,不同的硬件平台PAGE_SHIFT值不一样,因为不同的硬件平台page的大小也不一样,即这里的PAGE_SIZE不一样,目前比较前卫的硬件平台其page size有4k或者8k的,而PAGE_SHIFT也就是12或者13,换言之,sg->offset去掉低12位或者低13位就是page号,而低12位或者低13位恰恰是在该page内的偏移量.之所以可以把一个sg->offset起两个作用,正是因为page size只需要12位或者13位就足够了,或者说偏移量本身只有12位或者13位,而一个int型变量显然可以包含比12位或13位更多的信息.我们最终是要把buffer(即前面说的那个36个bytes的标准的INQUIRY data buffer)里边的冬冬copy至DMA buffer中,buffer我们已经知道,她就是usb_stor_access_xfer_buf()函数传递进来的参数,而DMA buffer在哪呢?只要我们知道她在哪个page中,知道她的offset,那么有一个函数可以帮助我们获得她对应的内核虚拟地址,而这个地址正是我们需要的,有了她,我们就能调用memcpy函数来copy数据了.所以235行到238行,就是计算出究竟是哪个page,究竟是多少offset,后者用被赋给了unsigned int变量poff,*offset是usb_stor_access_xfer_buf()函数传递进来的参数,她也可以控制我们要传送数据的DMA buffer对应的offset,不过这里我们传递进来的是0.所以不去care.

239行对unsigned int sglen赋值,sg->length实际上就是DMA buffer的长度.所以显然我们copy冬冬不能超过这个长度,如果我们还指定了*offset,就表明DMA buffer中从*offset开始装,那么就不能超过sg->length-*offset.

241行,由于我们现在还在while循环中,所以先看第一次执行到241行,这时cnt等于0,buflen就是那个data buffer的长度,传进来的是36.sglen表征了DMA buffer里边可以装多少,如果sglen比buflen要大,那么很好,一次就可以装满,因为这就好比buflen是一吨沙子,而sglen则表示装沙车载重两吨或者更多,比如三吨.这样244行245行,其作用就是做个标记,比如sglen被用来记录实际装载了多少重量的沙子,而*offset则表征了装沙车用了多少了,如果还没卸货下次又要继续往里装那就装吧,反正没满就可以装.那么如果sglen比buflen要小或者刚好够大,那么*offset肯定就被设为0,因为这一车必然会被装满,要再装沙子只能调用下一辆车,所以同时sg和*index也自加.

然后来看257行了,sglen一开始肯定应该大于0吧,她表征的是实际装了多少,但是内存管理机制有一个限制,memcpy传输不能够跨页,也就是说不能跨page,一次最多就是把本page的冬冬给copy走,如果你的数据超过了一个page,那你还得再copy一次或者多次,因为这里咱们用了循环.每一次真正copy的长度用plen来表征,所以271行和272行,cnt是计数的,所以她要加上一个plen,而sglen也是一个计数的,但是她是反着计,所以她每次要减掉一个plen.而poff和page一个设为0,一个自加,这个道理很简单,copy.而258行再次调用强大的min()函数也正是为了保证每次copy不能跨页.260和266行这对冤家的出现,kmap()和kunmap(),道理也很简单.这对冤家是内核中的内存管理部门提供给咱们的重要函数,其中kmap()函数的作用就是传递给她一个struct page指针,她能返回这个page指针所指的那个page的内核虚拟地址,而有了这个page对应的虚拟地址,加上咱们前面已经知道的偏移量,咱们就可以调用memcpy函数来copy数据了.至于kunmap,古书有云:凡是kmap()建立起来的良好关系必须由kunmap()来摧毁.

然后,262到265行的两个memcpy无需再讲了.

278行,返回实际传输的字节数.

至此,usb_stor_access_xfer_buf()这个函数的每一点每一滴都讲完了.让我们欢呼吧!这一刻,我们不是一个人在战斗.(注:应该说如果不是很有悟性的话,这段代码要看懂还是挺难的,虽说不至于让你哭得花枝乱颤,但也足以令你看得眼花缭乱.正式版中关于这个函数应该有一个比较好的图解,来描述这个双重循环,须知外循环是按sg entry来循环,即一个sg entry循环一次,而内循环是按page来循环,我们说过,一个sg entry可以有多个page,而因为我们没有办法跨page映射,所以只能每个page映射一次,所以才需要循环.)

回到usb_stor_set_xfer_buf()中来,也只剩下一句话了,如果我们要传递的36个字节还不足以填满这辆装沙车的话,那就让我们记录下这辆车还能装多少沙子吧,srb->resid正是扮演着这个记录者的角色.

然后我们回到fill_inquiry_response()中来,一看,嗬,这个函数也结束了.我们再一次回到了那个不死的精灵进程,usb_stor_control_thread()中来,这个函数俨然就像当年圣斗士星矢中的不死鸟一辉,总也死不了,隔一会又会出现.对于INQUIRY命令,咱们在fill_inquiry_response()之后,把srb->result设为了SAM_STAT_GOOD,她是scsi系统里边定义的一个宏,include/scsi/scsi.h中:

117 /* 118 * SCSI Architecture Model (SAM) Status codes. Taken from SAM-3 draft 119 * T10/1561-D Revision 4 Draft dated 7th November 2002. 120 */ 121 #define SAM_STAT_GOOD 0x00其实就是0,咱们完成了工作之后把srb->result设为0,以后自有scsi那边的函数会去检测.不用咱们管了.

然后咱们进入到381行,更确切的是,385行,srb->scsi_done()函数会被调用,srb->scsi_done()实际上是一个函数指针,咱们在queuecommand中令她等于scsi_done,咱们被要求在完成了该做的事情之后调用这个函数,剩下的事情scsi核心层会去处理.

酱紫,针对queuecommand传递进来INQUIRY命令的情况,该做的就都做了.

最后单独解释一下,给那些喜欢打破沙锅问到底的无聊人士:

First of all, 好端端的传输数据为什么要分散成好多个scatter gather list,这不是自找麻烦吗?关于这个问题,曾经我单纯的以为,也许只有支离破碎才是美.后来我意识到,SCSI层包括usb-storage之所以要使用scatter gather是因为这一特性允许系统在一个SCSI命令中传输多个物理上不连续的buffers.

关于kmap()和kunmap().这两个函数是干嘛的?为什么要映射?小时候妈妈没有告诉过你吗?将来你长大了,等你熟悉了Linux内核中的内存管理部分你就会知道,这个世界上有一个地址,叫做物理地址,还有一个地址叫做内核地址,struct page代表的是物理地址,内核用这个结构体来代表每一个物理page,或者说物理页,显然我们代码中不能直接操作物理地址,memcpy这个函数根本就不认识物理地址,它只能使用内核地址.所以我们需要把物理地址映射到内核地址空间中来.kmap()从事的正是这项伟大的工作.不过写过代码的人了解kmap()更多的和high memory打交道的时候认识的,此乃题外话,不表.

读书须用意,一字值千金。

Linux那些事儿之我是U盘(40)彼岸花的传说(八) – fudan

相关文章:

你感兴趣的文章:

标签云: