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

  金城武说:不知道从什么时候开始,在什么东西上面都有个日期,秋刀鱼会过期,肉罐头会过期,连保鲜纸都会过期,我开始怀疑,在这个世界上,还有什么东西是不会过期的?

有时候我也被这个问题所困扰,我不知道是我不明白还是这世界变化太快.Linux中都引入了过期这么一个概念.说文雅一点就是超时.设置一个时间,如果时间到了该做的事情还没有做完,那么某些事情就会发生.

比如,咱们需要做这样一些事情,定个闹钟,比如咱们需要烤蛋糕,现在是830,而咱们要烤45分钟,所以咱们希望闹钟9点一刻响,然后当时间到了,闹钟就如咱们期待的一样,响个不停.在计算机中,咱们也需要做这样的事情,有些事情,咱们需要时间控制,特别是网络,通信,等等,凡是涉及数据传输的事儿,就得考虑超时,换句话说,定个闹钟,你要是在这个给定的时间里还没做好你该做的事情,那么停下来,别做了,肯定有问题,比如,咱们如果烤蛋糕45分钟,发现蛋糕一点香味都没有,颜色也没变,那肯定有问题,别烤了,先检查一下烤箱是不是坏了,是不是停电了,等等.而具体到咱们这里,需要用一个闹钟,或者叫专业一点,定时器,如果时间到了,就执行某个函数,这个功能Linux内核的时间机制已经实现了,咱们只需要按"说明书"调用相应的接口函数即可.看代码,190,如果timeout>0,也就是说需要设置闹钟,那么首先需要定义一个struct timer_list结构体的变量,咱们这里定义的变量叫做to_timer(usb_stor_msg_common一开始就定义了的),然后用init_timer()函数和add_timer()函数来真正实现设置闹钟,init_timer()是初始化,然后设置好之后调用add_timer才能让闹钟生效.具体怎么设置的呢?add_timer()之前,to_timer.expires赋值为jiffies+timeout,to_timer.function赋值为timeout_handler,to_timer.data赋值为us.这表示,超时时间点为当前时间加上一个timeout,(jiffies,Linux内核中赫赫有名的全局变量,表示当前时间),timeout咱们前面调用usb_stor_msg_common的时候给设置成了HZ,也就是1.当时间到了之后,timeout_handler函数会被执行,us作为参数传递给她.不妨来看一下timeout_handler函数吧,她定义于drivers/usb/storage/transport.c:

    119 /* This is the timeout handler which will cancel an URB when its timeout
    120  * expires.
    121  */
    122 static void timeout_handler(unsigned long us_)
    123 {
    124         struct us_data *us = (struct us_data *) us_;
    125
    126         if (test_and_clear_bit(US_FLIDX_URB_ACTIVE, &us->flags)) {
    127                 US_DEBUGP("Timeout -- cancelling URB/n");
    128                 usb_unlink_urb(us->current_urb);
    129         }
    130 }
  看得出,其实也没做什么,就是清除US_FLIDX_URB_ACTIVE flag,然后调用usb_unlink_urb()函数撤销当前这个urb.还记得刚才说的那个同步异步了吗?这正是刚才说的那个异步的情形,显然此刻这个函数不能睡眠,否则整个driver就挂了...

紧接着,199,非常重要的一句wait_for_completion(&urb_done),这句话会使本进程进入睡眠.别忘了刚才我们那句init_completion(&urb_done),urb_done是一个struct completion结构体变量,这个定义在usb_stor_msg_common()函数的第一行就出现了.显然completionLinux中同步机制的一个很重要的结构体.wait_for_completion对应的一个函数是complete().其用法和作用是这样的:首先我们要用init_completion初始化一个struct completion的结构体变量,然后调用wait_for_completion()这样当前进程就会进入睡眠,处于一种等待状态,而另一个进程可能会去做某事,当它做完了某件事情之后,它会调用complete()函数,一旦它调用这个complete函数,那么刚才睡眠的这个进程就会被唤醒.这样就实现了一种同步机制,或者叫等待机制.那么我们来看complete函数在哪里被调用的,换句话说,咱们这里一旦睡去,何时才能醒来.

 还记得在调用usb_fill_control_urb()填充urb的时候咱们设置了一个urb->complete指针吗?没错,当时咱们就看到了,urb->complete=usb_stor_blocking_completion,这相当于向usb host controller driver传达了一个信息.所以,urb传输完成了之后,usb host controller会唤醒她,但不会直接唤醒她,而是通过执行之前设定的urbcomplete函数指针所指向的函数,即调用usb_stor_blocking_completion()函数去真正唤醒她.usb_stor_blocking_completion()函数定义于drivers/usb/storage/transport.c:

109 /* This is the completion handler which will wake us up when an URB
    110  * completes.
    111  */
    112 static void usb_stor_blocking_completion(struct urb *urb, struct pt_regs *regs)
    113 {
    114         struct completion *urb_done_ptr = (struct completion *)urb->context;
    115
    116         complete(urb_done_ptr);
    117 }
  这个函数就两句话,但她调用了complete()函数,urb_done_ptr就被赋为urb->context,urb->context是什么? usb_stor_msg_common()函数中,152,可不就是把刚初始化好的urb_done赋给了它么?这个函数可是Linux内核的核心函数,不要问她从哪里来,她会告诉你她来自内核底层,没错,她的户口在kernel/sched.c,很显然,她就是唤醒刚才睡眠的那个进程.换言之,到这,wait_for_completion()将醒来,从而继续往下走.

如果你足够好奇,你会问如果超时,那么timeout_handler会被调用,于是usb_unlink_urb会被调用,然后呢?其实usb_stor_blocking_completion还是会被调用,而且会设置urb->status以告诉大家这个urbcancel.

下面只剩下几行代码了.首先是clear_bit()清除US_FLIDX_URB_ACTIVE,表明这个urb不再是active.因为该干的事都干完了,就好比您的包裹已经寄到了,那显然您填的那个单子就没有用了.至少她上面应该有标志表明这份单子对应的包裹已经送过了,不要再送了.如果是超时了,那么也是一样的,urb都被cancel,当然就不用设为active.

然后下一行,如果这时timeout还大于0,那么说明刚才您设的那个超时闹钟还没到过期,而您该做的事情却已经做完了,所以这个闹钟就不需要设了,就好比邮局承诺您三天寄到,完了您记住了,三天她要没寄到,您就去索赔,所以您自个儿就订了个闹钟,三天真到期了您就可以去索赔,但是如果人家两天就给您寄到了,那您这个闹钟就没意义了嘛不是,所以这样您就得取消这个闹钟,省得她那弦老紧绷着,这里您也得删除刚才那个to_timer,这样您可以调用Linux内核为您提供的函数del_timer_sync(),她的参数就是刚才这个to_timer的地址.最后一句,usb_stor_msg_common()函数终于该返回了,return us->current_urb->status,返回的就是urbstatus.于是我们总算是可以离开这个函数了.

  返回之后,又回到usb_stor_control_msg()中来,如果status0,那么说明成功传输了,对于成功传输的情况,urbactual_length将被赋值为实际传输长度,然后usb_stor_control_msg()也返回了,要么是实际长度,要么就是不成功的具体status.因此我们也不得不离开这个函数.诚然,快乐要有悲伤作陪,雨过应该就有天晴.但是如果雨后还是雨,如果忧伤之后还是忧伤.请让我们从容面对这离别之后的离别.

  于是,走过了千山万水,经历了千辛万苦,咱们再一次回到了久违的usb_stor_Bulk_max_lun()函数.这样一次真正的控制传输就这么开始就这么结束了.

接下来,我们继续看,控制传输的结果返回给了result,我们说过,单纯的U盘一般来说这个结果总是0,即它必然只有一个lun.这里判断的是result大于0,道理很简单,result是一个int型的数据,而返回的给它的实际上是iobuf[0]这么一个char类型的变量,所以是字符’0’,保存成int型当然就大于0,所以这里打印出来结果是0,但是result实际上是大于0.945行我们同样注意到,如果result是一些奇怪的值,正如注释所说,有的设备它就不认GetMaxLUN这个命令,就像在她的心里潜伏着一个深渊,扔下巨石也发不出声音来,或者它干脆就返回一个0长度的结果来,那么这种情况那么我们就只能把这种设备当作只有一个LUN,所以也就返回0得了.

不过933935这三行需要说一下.这也是专门为一些变态的设备准备的.一般的设备用不着.只是usb_stor_clear_halt这个函数是我们自己定义的,并且今后我们也会用到,所以我们还是讲一下.不过,在这个函数中,我们将再一次见到控制传输,但是毕竟不再是我们的第一次亲密接触了,所以,虽然我们依然还是在usb_stor_Bulk_max_lun,但还是让我们下一节在讲吧.

最后需要解释一下的是,init_timer(),add_timer(),del_timer_sync()这几个函数都是Linux内核中的核心函数,包括结构体struct timer_list,他们都来自include/linux/timer.hkernel/timer.c,我们只需要知道调用就可以了,不用知道究竟怎么实现的,只需要知道这样设置了超时的话,我们注册的超时处理函数就会被执行.而至于它究竟如何去调用的,如何计时如何去判断超时这些内核自会处理,不用我们担心,我们瞎操心也没用.对于内核来说,时间是怎样划破她的皮肤,只有她自己最清楚.而对于写设备驱动的人来说,这些核心代码就像是天空中的云朵,你看着它往某一个方向飘,却什么也做不了.正如令狐冲所说的那样:”有些事情本身我们无法控制,只好控制自己.

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值