《Linux那些事儿之我是USB》我是U盘(24)彼岸花的传说(三)

来源:岁月联盟 编辑:exp 时间:2011-10-26

 

前面已经说了,回到usb_stor_acquire_resources()函数中,返回了0。于是咱们终于回到了storage_probe()函数中来。

 

1008行,scsi_add_host()函数被执行,之前申请的us->host被作为参数传递给它,同时,intf->dev也被传递给它,这个东西是被用来注册sysfs的。前面已经说过,在scsi_host_alloc之后,必须执行scsi_add_host(),这样,SCSI核心层才能够知道有这么一个host存在。scsi_add_host()成功则返回0,否则返回出错代码。如果一切顺利,将走到1009行,别急,先把代码“贴”出来,这就是storage_probe()函数的最后一小段了:

 

1014

 

1015    /* Startup the thread for delayed SCSI-device scanning */

 

1016    th =kthread_create(usb_stor_scan_thread, us, "usb-stor-scan");

 

1017   if (IS_ERR(th)) {

 

1018       printk(KERN_WARNING USB_STORAGE

 

1019                       "Unable to start the device-scanning thread/n");

 

1020       quiesce_and_remove_host(us);

 

1021        result = PTR_ERR(th);

 

1022        goto BadDevice;

 

1023   }

 

1024

 

1025   /* Take a reference to the hostfor the scanning thread and

 

1026    * count it among all the threads we have launched.  Then

 

1027    * start it up. */

 

1028   scsi_host_get(us_to_host(us));

 

1029    atomic_inc(&total_threads);

 

1030   wake_up_process(th);

 

1031

 

1032   return 0;

 

1033

 

1034   /* We come here if there are anyproble ms */

 

1035 BadDevice:

 

1036    US_DEBUGP("storage_probe()failed/n");

 

1037   release_everything(us);

 

1038   return result;

 

1039 }

 

又一次见到了kthread_create,不需要更多解释,这里自然还是创建一个内核守护进程,只不过这次是usb_stor_scan_thread,而上次是usb_stor_control_thread。usb_stor_scan_thread()函数也是定义于drivers/usb/storage/usb.c中:

 

904 /* Thread to carry out delayed SCSI-devicescanning */

 

905 static int usb_stor_scan_thread(void * __us)

 

906 {

 

907    structus_data *us = (struct us_data *)__us;

 

908

 

909    printk(KERN_DEBUG

 

910           "usb-storage: device found at %d/n",us->pusb_dev->devnum);

 

911

 

912     /*Wait for the timeout to expire or for a disconnect */

 

913    if(delay_use > 0) {

 

914         printk(KERN_DEBUG "usb-storage: waitingfor device "

 

915                                "to settle before scanning/n");

 

916 retry:

 

917        wait_event_interruptible_timeout(us->delay_wait,

 

918                         test_bit(US_FLIDX_DISCONNECTING,&us->flags),

 

919                           delay_use * HZ);

 

920         if (try_to_freeze())

 

921               goto retry;

 

922    }

 

923

 

924     /*If the device is still connected, perform the scanning */

 

925   if (!test_bit(US_FLIDX_DISCONNECTING,&us->flags)) {

 

926

 

927        /* For bulk-only devices, determine the max LUN value */

 

928        if (us->protocol == US_PR_BULK &&

 

929                                !(us->flags & US_FL_SINGLE_LUN)) {

 

930             mutex_lock(&us->dev_mutex);

 

931             us->max_lun =usb_stor_Bulk_max_lun(us);

 

932            mutex_unlock(&us->dev_mutex);

 

933         }

 

934        scsi_scan_host(us_to_host(us));

 

935          printk(KERN_DEBUG "usb-storage: devicescan complete/n");

 

936

 

937        /* Should we unbind if no devices weredetected? */

 

938    }

 

939

 

940   scsi_host_put(us_to_host(us));

 

941    complete_and_exit(&threads_gone,0);

 

942 }

 

913行,delay_use哪来的?同一个文件中,最开始的地方,定义了一个静态变量:

 

110 static unsigned int delay_use = 5;

 

111 module_param(delay_use, uint, S_IRUGO |S_IWUSR);

 

112 MODULE_PARM_DESC(delay_use, "seconds todelay before using a new device");

 

设置了delay_use为5,而module_param是Linux Kernel 2.6提供的一个宏,使得delay_use可以在模块被装载时设定。(如果不设,那么它自然就是这里的值5,表示使用一个新的设备之前等待5秒延时。)为什么要延时啊?当插进去的U盘也可能立刻又被拔出来了,试想插入以后一两秒之内又拔出来,那么咱们下面也不用耽误工夫再检测了。

 

913行,判断delay_use>0,然后917行,wait_event_interruptible_timeout(),它的第一个参数是us->delay_wait。

 

在storage_probe()函数的最初,在us的初始化时,delay_wait被初始化了。975行,init_waitqueue_head(&us->delay_wait),而在定义structus_data时,有一个成员就是delay_wait,即wait_queue_head_t delay_wait,这些都是什么意思呢?

 

实际上wait_event_interruptible_timeout()是一个宏,它代表着Linux中的一种等待机制,等待某个事件的发生,函数原型中,第1个参数是一个等待队列头,即wait_queue_head_t定义的变量,在2.6内核中使用init_waitqueue_head()函数初始化这个等待队列,然后第3个参数是设置超时。比如这里设了5秒,这表示如果5秒到了,那么函数会返回0,不管其他条件如何。第2个参数是一种等待的条件,或者说等待的事件,如果条件满足了,那么函数也会返回,条件要是不满足,那么这个进程会进入睡眠,不过interruptible表明了信号可以把它中断。

 

一旦进入睡眠,那么有三种情况:一种是wake_up或者wake_up_interruptible函数被另一个进程执行,从而唤醒它,第二种是信号中断它,第三种就是刚才讲的超时,时间到了,自然就会返回。

 

那么这里具体来说,先判断US_FLIDX_DISCONNECTING这个flag有没有设置,如果没有设置才进入睡眠,否则就不需要浪费彼此的感情了。在进入睡眠之后,如果5秒之内没有把U盘拔出来,那么5秒一到,函数返回0,继续往下走,如果在5秒之前拔出来U盘了,那么后来咱们会讲,storage_disconnect()函数会执行,它会设置US_FLIDX_DISCONNECTING这个flag,并且它会调用wake_up(&us->scsi_scan_wait)来唤醒这里睡眠的进程,告诉它:“别等了,哥们儿,你没那种命!”这样函数就会提前返回,不用等到5秒再返回了。总之不管条件满不满足,5秒之内肯定会返回,所以我们继续往下看。

 

920行,try_to_freeze(),这是电源管理的内容。

 

925行,再次判断设备有没有被断开,如果还是没有,那么执行scsi_scan_host()函数扫描,扫描然后就知道这个host或者说这个SCSI卡上面接了什么设备(虽然咱们这个只是模拟的SCSI卡),然后cat/proc/scsi/scsi才能看到您的U盘。

 

928行这一段,就是对于有多个LUN的设备,调用usb_stor_Bulk_max_lun()来获得max_lun。

 

然后941行,complete_and_exit函数,它和complete函数还有一点不一样,除了唤醒别人,还得结束自己(exit)。它在kernel/exit.c中:

 

1010 NORET_TYPE void complete_and_exit(structcompletion *comp, long code)

 

1011 {

 

1012   if (comp)

 

1013      complete(comp);

 

1014

 

1015    do_exit(code);

 

1016 }

 

这个函数中最重要的是do_exit()函数,不用多说,它是内核提供的函数,结束进程。也就是说,对于上面这个scan的精灵进程,到这里它就会结束退出了。可以看出它是一个短命的守护进程。总之对于这个精灵进程来说,它的使命就是让你能在cat /proc/scsi/scsi中看到你的U盘,当然了,从此以后你在/dev目录下面也就能看到你的设备了,比如/dev/sda。

 

再来看父进程,也就是storage_probe(),在用kernel_thread()创建了usb_stor_scan_thread之后,一切正常的话,storage_probe()也走到了尽头了。1032行,return 0了。终于,这个不老的传说也终于到了老的那一刻,一切都结束了,一切都烟消云散了。