Linux那些事儿之我是U盘(大结局)其实世上本有路,走的人多了,也便没了路

来源:互联网 发布:云计算国家排名 编辑:程序博客网 时间:2024/04/18 08:49

  其实信号量这东西,就像北京户口,你占了一个名额,我就可能没有了名额.但是有些事情,没有北京户口你又办不成.比如我满怀壮志的走到医院向医生表达说我希望死了以后可以捐献遗体,可得到的只是医生冷冷的回复,对不起,你没有资格,因为你没有户口.

  我们前面说过,Linux,有信号量,有自旋锁,有互斥锁,自旋锁或者互斥锁从某种意义上来说就只是一种特殊的信号量,即信号量意味着资源数量有限,但这个有限也许可能比如像每年的北京户口的名额,有若干个,而锁反映的就是更加有限,限制到了数量为一,即类似于所谓的一夫一妻制.她只属于你一个人,一旦你占有了她,如果别人还要想得到她,除非你释放.或者说除非你抛弃了她.

  具体到usb storage,我们不管信号量和锁在Linux中是怎么实现的,它们之间是否有区别对我们来说也无所谓,事实上,usb-storage中使用的都是锁,即便是信号量,也是把它当成锁来使用,可曾记得我们当初把信号量的初始化为1?当信号量初始化为1,那么对我们来说,它就相当于退化为一把锁了.而锁,只有两种状态,上锁和解锁.

  此前我们见过很多次两组函数,但我们一直绝口不提.

  她们就是scsi_lock()scsi_unlock(),以及down(&us->dev_semaphore)up(&us->dev_semaphore).

  先来看第一组.她们是我们自己定义的宏.来自drivers/usb/storage/usb.h.

174 /* The scsi_lock() and scsi_unlock() macros protect the sm_state and the
    175  * single queue element srb for write access */
    176 #define scsi_unlock(host)       spin_unlock_irq(host->host_lock)
    177 #define scsi_lock(host)         spin_lock_irq(host->host_lock)

显然,这两个函数就像牛郎织女一样,是一对.而她们的作用,就是利用自旋锁来保护资源,这把锁就是struct Scsi_Host的一个成员spinlock_t *host_lock.那么在什么情况下需要使用这两个自旋锁函数来保护资源呢?

  当你要写us->srb的时候,(不是写us->srb的元素,而是写us->srb,比如令us->srb=NULL),这种时候,你需要使用这把自旋锁.另一种情况是,当你调用scsi mid layer(scsi中层)的函数时,有时候这些函数要求调用者拥有这把锁,host_lock.

  搜索一下,发现一共有7处使用了scsi_lock.

  我们列举一些来.

  第一处,drivers/usb/storage/usb.c:usb_stor_release_resources()函数中,

839                 scsi_lock(us->host);
    840                 us->srb = NULL;
    841                 scsi_unlock(us->host);

这个不用解释了吧,赤裸裸的写us->srb,自然要用scsi_lock/scsi_unlock.

  第二处,drivers/usb/storage/transport.c:usb_stor_reset_common()函数中,

   1121         scsi_lock(us->host);
   1122         usb_stor_report_device_reset(us);
   1123         set_bit(US_FLIDX_RESETTING, &us->flags);
   1124         clear_bit(US_FLIDX_ABORTING, &us->flags);
   1125         scsi_unlock(us->host);
 
这就是第二种情况,因为这里调用了usb_stor_report_device_reset(),这个函数来自drivers/usb/storage/scsiglue.c,也是我们定义的,

308 /* Report a driver-initiated device reset to the SCSI layer.
    309  * Calling this for a SCSI-initiated reset is unnecessary but harmless.
    310  * The caller must own the SCSI host lock. */
    311 void usb_stor_report_device_reset(struct us_data *us)
    312 {
    313         int i;
    314
    315         scsi_report_device_reset(us->host, 0, 0);
    316         if (us->flags & US_FL_SCM_MULT_TARG) {
    317                 for (i = 1; i < us->host->max_id; ++i)
    318                         scsi_report_device_reset(us->host, 0, i);
    319         }
    320 }

注意到,她里边调用了scsi_report_device_reset(),后者来自drivers/scsi/scsi_error.c,这正是scsi mid layer定义的函数,这个函数的注释说得很清楚,调用她时必须要拥有host lock.

  有人说还有第三种情况,drivers/usb/storage/usb.c,usb_stor_control_thread()函数中,

318                 /* lock access to the state */
    319                 scsi_lock(host);
    320
    321                 /* has the command timed out *already* ? */
    322                 if (test_bit(US_FLIDX_TIMED_OUT, &us->flags)) {
    323                         us->srb->result = DID_ABORT << 16;
    324                         goto SkipForAbort;
    325                 }
    326
    327                 /* don't do anything if we are disconnecting */
    328                 if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) {
    329                         US_DEBUGP("No command during disconnect/n");
    330                         goto SkipForDisconnect;
    331                 }
    332
    333                 scsi_unlock(host);

您真是太有才了,这段代码都被您注意到了.的确看起来,这里无非是调用了test_bit,但是却使用了scsi_lock/scsi_unlock这对冤家,这是什么原因?其实是这样的,我们注意到这里有两个goto语句,

387 SkipForAbort:
    388                         US_DEBUGP("scsi command aborted/n");
    389                 }
    390
    391                 /* If an abort request was received we need to signal that
    392                  * the abort has finished.  The proper test for this is
    393                  * the TIMED_OUT flag, not srb->result == DID_ABORT, because
    394                  * a timeout/abort request might be received after all the
    395                  * USB processing was complete. */
    396                 if (test_bit(US_FLIDX_TIMED_OUT, &us->flags))
    397                         complete(&(us->notify));
    398
    399                 /* finished working on this command */
    400 SkipForDisconnect:
    401                 us->srb = NULL;
    402                 scsi_unlock(host);

401,us->srb=NULL,还是老一套.这才是为什么之前要用scsi_lock的真正原因.因为我要跳转,而跳过去以后需要写us->srb,所以要获得host lock,当然我们可以把scsi_lock写在test_bit()之后,比如

    321                 /* has the command timed out *already* ? */
    322                 if (test_bit(US_FLIDX_TIMED_OUT, &us->flags)) {
    323                         us->srb->result = DID_ABORT << 16;

这里插入一行==>         scsi_lock(host);
    324                         goto SkipForAbort;

这样改动代码也可以,显得更加严谨,不过改不改意义并不大.

  当然,这一段测试超时的代码本身被使用上的可能性也不大,因为这里命令还没有开始执行呢,试想,一个命令还没有开始就超时,这种情况论几率,和中国国家男足杀进世界杯差不多吧.

  再来看第二组,关于us->dev_semaphore,它更多的体现出来是一种进程间的同步机制.2.6.10的内核代码中一共有6处使用了这个信号量.分别是在device_reset(),bus_reset(),usb_stor_control_thread(),usb_stor_acquire_resources(),usb_stor_release_resources(),storage_disconnect().这几样东西之间是存在一种相互制约的关系.

  比如,一种情景是,device_reset()或者bus_reset()中上了锁,所以usb_stor_control_thread()这边就不可以在这个时候执行命令,usb_stor_acquire_resources()中的GET_MAX_LUN也不能执行,因为很显然,正在reset,就好比你的电脑正在重起你当然不可能执行一个打开浏览器的操作.

  而另一种情景,usb_stor_control_thread()正在执行命令,那么当然usb_stor_release_resources()函数就不能释放资源了,甚至storage_disconnect()也得等待,得等你当前这个命令执行完了,它才会去执行断开的代码.就好比你正在考场上战战兢兢的答题,老师却强行把你的试卷收上去,你说你会不会很愤怒?(当然,如果是因为你作弊被发现,那就另当别论了.)

  总之,这种关系都是相互的,相互制约,同时也保证了整个系统正常运转,如果谁违规了,那么伤害的是大家的利益.这就是Linux内核的同步机制.

  好了.我的故事讲完了.蓦然回首, 发现, 其实, 我一直在寻觅, 寻觅这个故事的结局, 寻觅自己灵魂的出路, 最终, 追寻到了前者, 却一直没有找到后者.