《Linux那些事儿之我是USB》我是U盘(35)迷雾重重的批量传输(四)
有时候我也被这个问题所困扰,我不知道是我不明白,还是这世界变化太快。连Linux中都引入了过期这么一个概念。设置一个时间,如果时间到了该做的事情还没有做完,那么某些事情就会发生。
比如需要烤蛋糕,现在是8点30,而我们要烤45分钟,所以希望闹钟9点一刻响,当时间到了,闹钟就如期待的一样,响个不停。在计算机中,也需要做这样的事情,有些事情,需要时间控制,特别是网络、通信等,凡是涉及数据传输,就得考虑超时,换句话说,就是要定一个闹钟,你要是在这个给定的时间里还没做好你该做的事情,那么停下来,别做了,肯定有问题。比如,如果烤蛋糕烤了45分钟,发现蛋糕一点香味都没有,颜色也没变,那肯定有问题,别烤了,先检查一下烤箱是不是坏了,或者是不是停电了等。
而具体到这里,需要用一个闹钟,或者叫专业一点,定时器。如果时间到了,就执行某个函数,这个功能Linux内核的时间机制已经实现了,只需要按“说明书”调用相应的接口函数即可。看代码,175行,wait_for_completion_interruptible_timeout()函数被调用,kernel/sched.c中定义了若干个这样的函数,我们不去看它们的定义,但是可以把它们的声明“贴”出来,来自include/linux/completion.h:
45 extern void FASTCALL(wait_for_completion(structcompletion *));
46 extern intFASTCALL(wait_for_completion_interruptible(struct completion *x));
47 extern unsigned long FASTCALL(wait_for_completion_timeout(structcompletion *x,
48 unsigned long timeout));
49 extern unsigned longFASTCALL(wait_for_completion_interruptible_timeout(
50 struct completion *x,unsigned long timeout));
很显然,wait_for_completion是这一系列函数中最基本的,其他几个函数都是基于它的扩展函数。与wait_for_completion对应的一个函数是complete()。其用法和作用如下所示。
首先我们要用init_completion初始化一个struct completion的结构体变量,然后调用wait_for_completion(),这样当前进程就会进入睡眠,处于一种等待状态,而另一个进程可能会去做某事。
当它做完了某件事情之后,它会调用complete()函数,一旦它调用这个complete()函数,那么刚才睡眠的这个进程就会被唤醒。这样就实现了一种同步机制,或者叫等待机制。
而我们现在用到的这个wait_for_completion_interruptible_timeout()函数则是在简单的同步机制上作了加强。它被唤醒有两种可能,一种就是和之前一样,由调用complete()函数的进程给唤醒,或者,就是设置一个闹钟,时间一到闹钟就响了。
举例来说一下这个方案。比如小时候我如果第二天要参加考试,那么前一天晚上要么跟我妈说好让她第二天早上记得叫我,或者如果我妈不在家,那我就定闹钟,到时间了闹钟就把我唤醒。
总之,这里设置了闹钟,时间为MAX_SCHEDULE_TIMEOUT,这么做的道理就是希望别提交一个urb上去之后半天都没人理,如果真的没人理说明出问题了,别浪费时间了,先撤销这个urb重新提交吧。所以我们看到178行就清除US_FLIDX_URB_ACTIVE flag,然后如果timeleft小于等于0就调用usb_kill_urb ()函数撤销当前这个urb。
那么除了这个闹钟以外,这个同步机制在我们这个案例中又是怎么工作的呢?别忘了刚才我们那句init_completion(&urb_done),urb_done是一个struct completion 结构体变量,这个定义在usb_stor_ msg_common()函数的第1行就出现了。显然completion是Linux中同步机制的一个很重要的结构体。同时我们又把&urb_done作为第1个参数传递给了wait_for_completion_interruptible_timeout()。所以我们就等着看发送唤醒信号的complete()函数在哪里被调用的,换句话说,这里一旦睡去,何时才能醒来。
还记得在调用usb_fill_bulk_urb()填充urb时设置了一个urb->complete指针吗?没错,当时咱们就看到了urb->complete=usb_stor_blocking_completion,这相当于向USB主机控制器驱动传达了一个信息。所以,当urb传输完成了之后,USB主机控制器会唤醒它,但不会直接唤醒它,而是通过执行之前设定的urb的complete()函数指针所指向的函数,即调用usb_stor_blocking_completion()函数去真正唤醒它。usb_stor_blocking_completion()函数定义于drivers/usb/storage/transport.c中:
111 static void usb_stor_blocking_completion(structurb *urb)
112 {
113 struct completion *urb_done_ptr = (structcompletion *)urb->context;
114
115 complete(urb_done_ptr);
116 }
这个函数就两句话,但它调用了complete()函数,urb_done_ptr就被赋为urb->context,而urb->context是什么? usb_stor_ msg_common()函数中,138 行,可不就是把刚初始化好的urb_done赋给了它吗?很显然,这样做就是唤醒刚才睡眠的那个进程。换而言之,到这里,wait_for_completion()将醒来,从而继续往下走。
下面只剩下几行代码了。首先是clear_bit()清除US_FLIDX_URB_ACTIVE,表明这个urb 不再是活跃了。因为该干的事都干完了。如果是超时了,那么也是一样的,urb都要被撤销了,当然就不用设为活跃了。最后一句,usb_stor_ msg_common()函数终于该返回了,return us->current_urb->status,返回的就是urb的“status”。于是我们总算是可以离开这个函数了。也就是我们将回到usb_stor_bulk_transfer_buf()中来。剩下的几行代码无非是处理一下结果,我们暂且先不多说。
回过头来看usb_stor_Bulk_transport()函数,978行,usb_stor_bulk_transfer_buf()函数得到调用。第1个参数,us,无须多说。第2个参数,us->send_bulk_pipe,作为U盘来说,它除了有一个控制管道以外,还会有两个批量管道,一个是IN,一个是OUT。经历过此前的风风雨雨,我们已经对USB中那些名词不再有神秘感,所谓管道无非就是一个unsigned int类型的数。us->send_bulk_pipe和接下来我们立刻会遇到的us->recv_bulk_pipe都是在曾经那个令人回味的storage_probe()中调用get_pipes()函数获得的。然后第3个参数bcb,下面看仔细了。
943行,定义了这么一个指针bcb,是structbulk_cb_wrap结构体的指针,这是一个专门为Bulk-only协议特别准备的数据结构,来自drivers/usb/storage/transport.h:
50 /* command block wrapper */
51 struct bulk_cb_wrap {
52 __le32 Signature; /*contains 'USBC' */
53 __u32 Tag; /* uniqueper command id */
54 __le32 DataTransferLength; /* size ofdata */
55 __u8 Flags; /*direction in bit 0 */
56 __u8 Lun; /* LUNnormally 0 */
57 __u8 Length; /* of ofthe CDB */
58 __u8 CDB[16]; /* maxcommand */
59 };
在同一个文件中还定义了另一个数据结构:struct bulk_cs_wrap。
66 /* command status wrapper */
67 struct bulk_cs_wrap {
68 __le32 Signature; /* should = 'USBS' */
69 __u32 Tag; /* same asoriginal command */
70 __le32 Residue; /* amountnot transferred */
71 __u8 Status; /* seebelow */
72 __u8 Filler[18];
73 };
这两个数据结构对应于CBW和CSW,即Command Block Wrapper和Command Status Wrapper。事到如今,我们需要关注一下USB Mass Storage Bulk-only Transport协议了,因为U盘是按照这个协议规定的方式去传输数据的。Bulk-only传输方式是首先由主机给设备发送一个CBW,然后设备接收到了CBW,它会进行解释,然后按照CBW中定义的那样去执行它该做的事情,然后它会给主机返回一个CSW。
CBW实际上是命令的封装包,而CSW实际上是状态的封装包。(命令执行后的状态,成功还是失败呢?所以需要使用这么一个状态包)。
这时候我们就可以查看usb_stor_Bulk_transport()函数中,调用usb_stor_bulk_transfer_buf()之前的那几行究竟在干什么了。很明显,这些行都是在为usb_stor_bulk_transfer_buf()这个函数调用做准备,真正精彩的部分还是在usb_stor_bulk_transfer_buf()中。
943行,struct bulk_cb_wrap *bcb,赋值为(struct bulk_cb_wrap *)us->iobuf。944行,struct bulk_cs_wrap *bcs,也赋值为(struct bulk_cb_wrap *)us->iobuf,然后定义一个unsignedint的变量transfer_length,赋值为srb->request_bufflen。然后接下来就开始为bcb的各成员赋值了。如图4.36.1和图4.36.2所示,分别描述了CBW和CSW的数据格式。
Byte / bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 0-3 | dCBWSignature | 4-7 | dCBWTag | 8-11 | dCBWDataTransferLength | 12 | bmCBWFlags | 13 | Reserved (0) | bCBWLUN | 14 | Reserved(0) | bCBWCBLength | 15-30 | CBWCB 图4.36.1 CBW
|