Linux那些事儿之我是U盘(27)第一次亲密接触(三)

让我们单刀直入,进入usb_stor_msg_common()函数.

首先看145,us->flagsABORTING_OR_DISCONNECTING相与,ABORTING_OR_DISCONNECTING宏定义于drivers/usb/storage/usb.h:

78 /* Dynamic flag definitions: used in set_bit() etc. */
79 #define US_FLIDX_URB_ACTIVE 18 /* 0x00040000 current_urb is in use */
80 #define US_FLIDX_SG_ACTIVE 19 /* 0x00080000 current_sg is in use */
81 #define US_FLIDX_ABORTING 20 /* 0x00100000 abort is in progress */
82 #define US_FLIDX_DISCONNECTING 21 /* 0x00200000 disconnect in progress */
83 #define ABORTING_OR_DISCONNECTING ((1UL << US_FLIDX_ABORTING) | /
84 (1UL << US_FLIDX_DISCONNECTING))
85 #define US_FLIDX_RESETTING 22 /* 0x00400000 device reset in progress */
86 #define US_FLIDX_TIMED_OUT 23 /* 0x00800000 SCSI midlayer timed out */
她只是一个flag,咱们知道,每一个usb mass storage设备,会有一个struct us_data的数据结构,us,所以,在整个probe的过程来看,她相当于一个"全局"的变量,因此咱们可以使用一些flags来标记一些事情.比如,此处,对于提交urb的函数来说,显然她不希望设备此时已经处于放弃或者断开的状态,因为那样就没有必要提交urb了嘛不是.

而下一个函数init_completion(),只是一个队列操作函数,她被定义于include/linux/completion.h:

13 struct completion {
14 unsigned int done;
15 wait_queue_head_t wait;
16 };
24 static inline void init_completion(struct completion *x)
25 {
26 x->done = 0;
27 init_waitqueue_head(&x->wait);
28 }
她只是调用了init_waitqueue_head去初始化一个等待队列.struct completion的定义也在上面已经列出.关于init_waitqueue_head咱们将在下面的故事中专门进行描述.

而接下来,都是在设置uscurrent_urb结构,咱们看161,transfer_flags被设置成了URB_ASYNC_UNLINK | URB_NO_SETUP_DMA_MAP,其中URB_ASYNC_UNLINK表明usb_unlink_urb()函数将被异步调用,不懂异步调用也没有关系,因为内核的发展是如此的迅速,这一点完全可以和上海的发展速度有得一拼,最新的内核中,人们已经很少用这个宏了,因为usb_unlink_urb已经是异步调用的函数了,如果是同步调用,则可以使用另一个函数usb_kill_urb().这两个函数的作用就是取消一个urb请求.同步调用用于这样一种情况,即函数执行过程中可以进入睡眠,满足一定条件再醒来继续执行,而异步调用则不会睡眠,那么这里之所以要设置URB_ASYNC_UNLINK,其目的就在于让usb_unlink_urb是异步的执行,原因是,有的时候我们取消一个urb request的时候是处在一种不能睡眠的上下文,比如后面我们会看到的处理超时的函数.我们在互联网上下载一个很大文件的时候,常常会遇到超时的情况,然后我们的请求就被中止了.对于usb系统同样有这个问题,我们会给一个urb设置超时,提交上去到了一定时间还没能传输好的话就意味着可能传输有问题,这种情况我们通常也会把这个urb给取消掉.于是这里有两种情况,第一,我们提交了urb之后就不管事了,我们等待,怎么等待?睡眠,我们的进程进入睡眠,换句话说在我们这个上下文情景中也就是storage_probe函数睡了.如果不超时,那么万事大吉,urb执行完了之后我们的进程被唤醒,继续往下走.那么天下平安.但是第二种情况是,我们进入了睡眠,可是时间到了,urb还没有执行完,那么超时函数会被执行,它就会去取消这个urb,如果说这个超时函数也可以睡眠,那就不得了了,天下大乱了,都睡了,谁干活?整个驱动不就瘫痪了!所以,这种情况下我们要调用的函数是不能睡眠的,也就是说必须是异步的.usb_unlink_urb内部会通过判断是否有URB_ASYNC_UNLINK这么一个flag被设置在了urb->transfer_flags,来选择是异步执行还是同步执行,实际上它的同步执行就是调用usb_kill_urb而已.不过刚才说的,最新版的内核,比如2.6.21,它里边就把这两个函数彻底分开了,不用设这么一个flag,usb_unlink_urb就是异步,usb_kill_urb就是同步.

URB_NO_SETUP_DMA_MAP表明,如果使用DMA传输,urbsetup_dma指针所指向的缓冲区是DMA缓冲区,而不是setup_packet所指向的缓冲区.接下来再或上URB_NO_TRANSFER_DMA_MAP则表明,如果本urb有一个DMA缓冲区需要传输,则该缓冲区是transfer_dma指针所指向的那个缓冲区,而不是transfer_buffer指针所指向的那一个缓冲区.换句话说,如果没设置这两个DMAflag,那么usb core就会使用setup_packettransfer_buffer作为数据传输的缓冲区,然后下面两行就是把usiobuf_dmacr_dma赋给了urbtransfer_dmasetup_dma.(157160的注释表明,只要transfer_buffer被赋了值,那就假设有DMA缓冲区需要传输,于是就去设URB_NO_TRANSFER_DMA_MAP.)关于DMA这一段,因为比较难理解,所以我们多说几句.

首先,这里是两个DMA相关的flag,一个是URB_NO_SETUP_DMA_MAP,而另一个是URB_NO_TRANSFER_DMA_MAP.主意这两个是不一样的,前一个是专门为控制传输准备的,因为只有控制传输需要有这么一个setup阶段需要准备一个setup packet.而我们把让setup_packet指向了us->cr别忘了我们当初为us->cr申请内存的时候用的是下面这句:

449 /* Allocate the device-related DMA-mapped buffers */
450 us->cr = usb_buffer_alloc(us->pusb_dev, sizeof(*us->cr),
451 GFP_KERNEL, &us->cr_dma);

别忘了这里的us->cr_dma,这个函数虽然返回值是赋给了us->cr,但与此同时,us->cr_dma中记录的可以该地址所映射的dma地址,那么刚才这里设置了URB_NO_SETUP_DMA_MAP这么一个flag,就说明,如果是DMA方式的传输,那么usb core就应该使用us->cr_dma里边的冬冬去进行dma传输,而不要用us->cr里边的冬冬了.换句话说,也就是urb里边的setup_dma而不是setup_buffer.

同样transfer_buffertransfer_dma的关系也是如此,我们当初同样用类似的方法申请了us->iobuf的内存:

457 us->iobuf = usb_buffer_alloc(us->pusb_dev, US_IOBUF_SIZE,
458 GFP_KERNEL, &us->iobuf_dma);

这里就有us->iobufus->iobuf_dma这两个咚咚,但是我们注意到,163164,我们在设置URB_NO_TRANSFER_DMA_MAP这个flag的时候,先做了一次判断,判断us->current_urb->transfer_buffer是否等于us->iobuf,这是什么意思呢?我们在什么地方对transfer_buffer赋过值? 答案是usb_fill_control_urb,我们把us->iobuf传递了过去,它被赋给了urb->transfer_buffer,这样做就意味着我们这里将使用DMA传输,所以这里就设置了这个flag,倘若我们不希望进行DMA传输,那很简单,我们在调用usb_stor_msg_common之前,不让urb->transfer_buffer指向us->iobuf就是了,反正这都是我们自己设置的,别人管不着.需要知道的是,transfer_buffer是给各种传输方式中真正用来数据传输的,setup_packet仅仅是在控制传输中发送setup的包的,控制传输除了setup阶段之外,也会有数据传输阶段,这一阶段要传输数据还是得靠transfer_buffer,而如果使用dma方式,那么就是使用transfer_dma.

Ok,下一句,169,终于到了提交urb这一步了,usb_submit_urb得到调用,作为usb设备驱动程序,我们不需要知道这个函数究竟在做什么,只要知道怎么使用就可以了,无需关注代码背后的哲学.它的定义在drivers/usb/core/urb.c,我们得知道它有两个参数,一个就是要提交的urb,一个是内存申请的flag,这里我们使用的是GFP_NOIO,意思就是不能在申请内存的时候进行IO操作,道理很简单,咱们这个是存储设备,调用usb_submit_urb很可能是因为我们要读些磁盘或者U,那这种情况如果申请内存的函数又再一次去读写磁盘,那就有问题了,什么问题?嵌套呗.什么叫申请内存的函数也会读写磁盘?玩过Linux的人不会不知道swap,交换分区,干嘛要交换啊,可不就是因为内存不够么.使用磁盘作为交换分区不就方便了,所以申请内存的时候可能要的内存在磁盘上,那就得交换回来.这不就读写磁盘了么?所以我们为了读写硬盘而提交urb,那么这个过程中就不能再次有IO操作了,这样做的目的是为了杜绝嵌套死循环.

于是我们调用了169行就可以往下走了,剩下的事情usb coreusb host会去处理,至于这个函数本身的返回值,如果一切正常,status将是0.所以这里判断如果status不为0那么就算出错了.

177,一个urb被提交了之后,通常我们会把us->flags中置上一个flag, US_FLIDX_URB_ACTIVE,让我们记录下这个urb的状态是活着的.

180,这里我们再次判断us->flags,看是不是谁置了aborting或者disconnectedflag.稍后我们会看到谁会置这些flag,显然如果已经置了这些flag的话,咱们就没必要往下了,这个urb可以cancel.

190,一个新的故事将被引出,这就是伟大的时间机制.

能冲刷一切的,除了眼泪,就是时间.所以Linux中引入了时间机制.

你可能感兴趣的:(linux)