分类: LINUX
2012-03-30 14:40:40
有时候我也被这个问题所困扰,我不知道是我不明白,还是这世界变化太快。连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
Byte \ bit |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
0-3 |
dCBWSignature ("LaMS") |
|
|||||||
4-7 |
dCBWTag |
|
|||||||
8-11 |
dCSWDataResidue |
|
|||||||
12 |
bCSWStatus |
|
图4.36.2 CSW
959行,bcb->Signature=cpu_to_le32(US_BULK_CB_SIGN),Signature对应USB Mass Storage spec中CBW的前4个Bytes,即dCBWSignature,US_BULK_CB_SIGN这个宏定义于drivers/usb /storage/transport.h中:
62 #define US_BULK_CB_SIGN 0x43425355 /*spells out USBC */
也不知道是谁规定的,只有把dCBWSignature里边写上43425355h才能标志着个数据包是一个CBW。另外,CBW的传输全是遵守Little Endian的,所以cpu_to_le32()这个宏需要使用,来转换数据格式。
然后bcb->DataTransferLength对应CBW中的dCBWDataTransferLength。这个就是标志ho主机希 望这个端点传输多少个Bytes的数据。这里把cpu_to_le32(transfer_length)赋给了它。而transfer_length刚 才已经说了,就是srb->request_bufflen。其实这几个变量名换来换去最重要记录的还是同一样东西。
bcb->Flags,对应于CBW中的bmCBWFlags,bcb->Flags =srb->sc_data_direction == DMA_FROM_DEVICE ? 1 << 7 : 0;表明的是数据传输的方向,DMA_FROM_DEVICE在前面讲过,表示数据是从设备传向主存。而bmCBWFlags是8位的,其中bit7表示 方向;0表示Data-Out,即from host to the device;1表示Data-in,即from the device to the host。所以这里如果是1的话要左移7位。
bcb->Tag = srb->serial_number,这个Tag对应CBW中的dCBWTag,dCBWTag的意义在于,主机会send出去,而设备将会把这 个Tag的内容给打印出来。确切地说,设备会回送一个CSW回来,而在CSW中会有一个dCSWTag,它的内容和这个dCBWTag是一样的,所以实际 上这就跟接头暗号似的。每一个SCSI命令都会被赋上一个serial_number,这里把它用在了Tag上。
bcb->Lun = srb->device->lun,很简单,对应CBW中的bCBWLUN,就是表示这个命令是发给哪个LUN的,我们知道一个设备如果支持 多个LUN,那么显然每个LUN会有一个编号。比如要读写U盘上的某个分区,那么当然得指明是哪个分区了。如果设备不支持多个LUN,那么这儿会被设置为 0。不过需要注意,这里bcb->Lun和CBW中的bCBWLUN并不完全对应,bCBWLUN只有4个bit,而咱们这中定义时,LUN是有8 位的,低4位用来对应bCBWLUN,而高4位实际上是用来表示target id的。所以接下来判断us->flags里边设了US_FL_SCM_MULT_TARG这个标志没有,如果有,说明是支持多个target的, 于是就要记录下是哪个target。
bcb->Length = srb->cmd_len,这个对应于CBW中的bCBWCBLength,即命令的有效长度,单位是Bytes。SCSI命令的有效长度只能是1 到16之间。接下来有个CDB数组,数组共16个元素,理由在前面讲struct scsi_cmnd中的cmnd就已经说过了。而969行,970行正是把命令srb->cmnd数组的内容复制至bcb->CDB中。
这时候,usb_stor_bulk_transfer_buf正式被调用了。传递给它的第三个参数正是bcb,而第四个参数是US_BULK_CB_WRAP_LEN,它也定义于drivers/usb/storage/transport.h中:
61 #define US_BULK_CB_WRAP_LEN 31
31就是CBW的长度,CBW正是31个Bytes。而usb_stor_bulk_transfer_buf无非就是提交一个urb,然后就不用管事 了,就等结果呗。而最终的结果是由interpret_urb_result()返回的,传输正确那么会返回USB_STOR_XFER_GOOD,而如 果不正确,那么usb_stor_Bulk_transport()中就直接返回了,返回值是USB_STOR_TRANSPORT_ERROR。如果正 确,那么继续往下走,这才到真正的数据传输阶段。在真正开始将数据传输阶段之前,我们先来查看interpret_urb_result()函数。