今宵酒醒何处?
杨柳岸,晓风残月.
此去经年,应是良辰好景虚设.
便纵有,千种风情,更与何人说?
伴随着婉约派才子,词坛浪子柳永的这首令人肝肠寸断的<<雨霖铃>>,我们来到了最后一个重要的函数,storage_disconnect.
usb设备的热插拔特性注定了我们应该在设备插入的时候做一些事情,在设备拔出的时候做一些事情.主机和usb设备的暧昧关系体现在,需要她的时候,要多缠绵有多缠绵,如胶似漆,如鱼得水.但是,有爱就有痛,有一天usb设备必定要离开主机.对于主机来说,人生没有usb设备并不会不同.而且,事实上,usb的即插即用特性也让主机知道,usb设备并不曾真的离去,他们还会再相逢.前面见面的时候调用了storage_probe来让彼此接受对方,现在就该调用storage_disconnect函数来分手.分手也许需要分财产,而usb设备离开主机也需要处理一些后事.
相比probe,disconnect函数就简单多了,造人需要辛苦30分钟,人流只要3分钟,看过杨千嬅的那部<<饺子>>之后就知道人流并不是一件麻烦事,难怪北京的街边电线杆上墙壁上到处写着无痛人流轻松搞定的广告词. storage_disconnect()函数定义在drivers/usb/storage/usb.c中,这个函数不长,
1027 /* Handle a disconnect event from the USB core */
1028 static void storage_disconnect(struct usb_interface *intf)
1029 {
1030 struct us_data *us = usb_get_intfdata(intf);
1031
1032 US_DEBUGP("storage_disconnect() called/n");
1033
1034 /* Prevent new USB transfers, stop the current command, and
1035 * interrupt a device-reset delay */
1036 set_bit(US_FLIDX_DISCONNECTING, &us->flags);
1037 usb_stor_stop_transport(us);
1038 wake_up(&us->dev_reset_wait);
1039
1040 /* Interrupt the SCSI-device-scanning thread's time delay, and
1041 * wait for the thread to finish */
1042 wake_up(&us->scsi_scan_wait);
1043 wait_for_completion(&us->scsi_scan_done);
1044
1045 /* Wait for the current command to finish, then remove the host */
1046 down(&us->dev_semaphore);
1047 up(&us->dev_semaphore);
1048 scsi_remove_host(us->host);
1049
1050 /* Wait for everything to become idle and release all our resources */
1051 usb_stor_release_resources(us);
1052 dissociate_dev(us);
1053 }
如果直到现在你还不知道1030行在干嘛,那我想问一下你他妈的是不是在耍我?虽然usb_get_intfdata()这个函数的确是第一次露面,但是这里的含义已然是司马昭之心路人皆知.usb_get_infdata()我们是没有讲过,但是我们讲过usb_set_intfdata().想当年,associate_dev()中,我们调用usb_set_intfdata(intf,us),当时我们分析了,这样做的结果就是使得%intf->dev->driver_data=us,而现在我们调用usb_get_intfdata(intf)的作用就是把us从中取出来,赋给我们这里的临时指针us.
1036行,全文中唯一一处设置US_FLIDX_DISCONNECTING这个flag的地方就在这里.
1037行, usb_stor_stop_transport(us),这个函数我们可是刚刚才讲过,你别说你就忘记了,就在command_abort()里调用的.目的就是停掉当前的urb和sg,如果有的话.
1038行, wake_up(&us->dev_reset_wait),我们也已经讲过了,就是在讲device_reset()讲到的,当时在usb_stor_reset_common()中,会使用wait_event_interruptible_timeout()来进入睡眠,睡眠的目的是给6秒钟来让设备从reset状态恢复过来,但是如果在这期间我们要断开设备了,那么当然就没有必要再让那边继续睡眠了,设备都要断开了,还有什么恢复的意义呢?所以对于这种情况,我们回过头来看usb_stor_reset_common(),会发现之后该函数立马从睡眠中醒来,然后清除掉为reset而设置的flag,US_FLIDX_RESETTING,然后就返回了,返回值是FAILED.
1042行, wake_up(&us->scsi_scan_wait),和上面这种情况几乎相同,不同的是这次唤醒的是usb_stor_scan_thread,这个函数里边也会因为delay_use的设置而调用wait_event_interrruptible_timeout去等待去睡眠,所以这里机理是一样的.而与此同时,1043行, wait_for_completion(&us->scsi_scan_done),恰恰是是等待对方的结束,我们注意到,在usb_stor_scan_thread()中最后一句话, complete_and_exit(&us->scsi_scan_done, 0),即唤醒咱们这里这个storage_disconnect()同时结束它自己.应该说这样就实现了一个同步机制.就是说因为我们之后马上要做的就是清理门户了,把一些不要的资源都释放掉,所以我们首先必须保证我们的进程都退出来,资源都不再被人使用,这样我们才可以放心的去做我们的清理工作.
1048行,scsi_remove_host()被调用,这是和最早的scsi_add_host相对应的.都是调用scsi core提供的函数.
1051行,usb_stor_release_resources(us),这个则是和我们当初那个usb_stor_acquire_resources(us)相对应.而1052行的dissociate_dev(us)则是和当初那个associate_dev()相对应.我们来看一下具体代码,来自drivers/usb/storage/usb.c,把这两个函数的代码都一并贴出来:
815 /* Release all our dynamic resources */
816 void usb_stor_release_resources(struct us_data *us)
817 {
818 US_DEBUGP("-- %s/n", __FUNCTION__);
819
820 /* Kill the control thread. The SCSI host must already have been
821 * removed so it won't try to queue any more commands.
822 */
823 if (us->pid) {
824
825 /* Wait for the thread to be idle */
826 down(&us->dev_semaphore);
827 US_DEBUGP("-- sending exit command to thread/n");
828
829 /* If the SCSI midlayer queued a final command just before
830 * scsi_remove_host() was called, us->srb might not be
831 * NULL. We can overwrite it safely, because the midlayer
832 * will not wait for the command to finish. Also the
833 * control thread will already have been awakened.
834 * That's okay, an extra up() on us->sema won't hurt.
835 *
836 * Enqueue the command, wake up the thread, and wait for
837 * notification that it has exited.
838 */
839 scsi_lock(us->host);
840 us->srb = NULL;
841 scsi_unlock(us->host);
842 up(&us->dev_semaphore);
843
844 up(&us->sema);
845 wait_for_completion(&us->notify);
846 }
847
848 /* Call the destructor routine, if it exists */
849 if (us->extra_destructor) {
850 US_DEBUGP("-- calling extra_destructor()/n");
851 us->extra_destructor(us->extra);
852 }
853
854 /* Finish the host removal sequence */
855 if (us->host)
856 scsi_host_put(us->host);
857
858 /* Free the extra data and the URB */
859 if (us->extra)
860 kfree(us->extra);
861 if (us->current_urb)
862 usb_free_urb(us->current_urb);
863
864 }
865
866 /* Dissociate from the USB device */
867 static void dissociate_dev(struct us_data *us)
868 {
869 US_DEBUGP("-- %s/n", __FUNCTION__);
870
871 /* Free the device-related DMA-mapped buffers */
872 if (us->cr)
873 usb_buffer_free(us->pusb_dev, sizeof(*us->cr), us->cr,
874 us->cr_dma);
875 if (us->iobuf)
876 usb_buffer_free(us->pusb_dev, US_IOBUF_SIZE, us->iobuf,
877 us->iobuf_dma);
878
879 /* Remove our private data from the interface */
880 usb_set_intfdata(us->pusb_intf, NULL);
881
882 /* Free the structure itself */
883 kfree(us);
884 }
823行,判断us的pid,这个pid是哪来的?很显然,usb_stor_release_resources和咱们前面说过的usb_stor_acquire_resources函数是一对,us->pid也正是来自usb_stor_acquire_resources()函数,当时在调用kernel_thread启动usb_stor_control_thread的时候,记下了kernel_thread()的返回值,并把她赋给了us->pid,实际上kernel_thread()对于父进程来说,返回值就是子进程的pid,也就是说当年创建的精灵进程的pid是被记录下来了的.写代码的人老辣的编程功底可见一斑.
840行,设置us->srb为NULL.
844行和845行,up(&us->sema),以及wait_for_completion(&us->notify),知道这两句干嘛的吗?还记得usb_stor_control_thread(),当初讲到down_interruptible(&us->sema),咱们就说该守护进程进入了睡眠,那么谁能把她唤醒,除了前面讲的queuecommand之外,这里同样也是唤醒她的代码,并且这个函数在唤醒别人之后,自己执行wait_for_completion函数来进入等待.好,再次回到那个函数吧,usb_stor_control_thread(),303行,被up()唤醒的down_interruptible返回0.然后312行到316行的这个if小段当然就会执行了,因为us->srb这时候毫无疑问,等于NULL,所以break会被执行,从而结束for死循环,从而usb_stor_control_thread函数到了最后一行,最终调用complete_and_exit(&(us->notify),0)来结束精灵进程自己并且唤醒我们这里的进入等待的usb_stor_release_resources.很显然,这里两个进程通过四个函数,或者说两个组合,来实现了进程的同步,这正是Linux中内核同步机制的典型应用.(两大组合指的是:up和down,wait_for_completion和complete.)
继续往下,849行,struct us_data还有一个元素,extra_data_destructor extra_destructor,这是一个函数指针,实际上对某些设备来说,她们自己定义了一些额外的函数,在退出之前需要执行,比如Datafab USB 紧凑读卡器,不过像这样的变态的设备很少,对于大多数设备来说,这个函数指针都为空.如果定义了,那么就去执行她.
然后856行,scsi_host_put,只是对这个host的引用计数减1,如果该host的引用计数达到0了,那么将释放其对应的Scsi_Host数据结构所占的空间.一个scsi卡在其调用scsi_host_alloc的时候会被设置引用计数为1,引用计数就是表征有多少进程使用了这个资源,我记得外企面试的时候也常常会问到这个,2005年初,大四下的时候,去张江软件科技园,参加SAP的面试,当时谈到一个无盘工作站的话题,面试官就问我,既然客户端都没有硬盘,那么如果多个人同时读写服务器上的同一个文件,那岂不是乱套了,当时这个问题一问我就傻了.后来想想,也许这个和引用计数用点关系吧,再用上那个所谓的写拷贝(copy-on-write)技术.不过我还是挺喜欢SAP,可惜,人家看不上我.
接下来859行,判断us->extra,us->extra就是为前面那个extra_data_destructor函数准备的参数,一般不会申请,如果申请过,那就释放她.
再接下来,current_urb也没用了,释放吧.释放urb需要调用专门的函数usb_free_urb,她来自drivers/usb/core/urb.c中.
到这里咱们前面占有的资源基本上就释放掉了.
再看下一个函数,dissociate_dev(),很显然,这个函数和咱们前面讲的associate_dev()函数是一对.在associate_dev函数中,咱们调用了usb_buffer_alloc()函数,先后为us->cr和us->iobuf分配了空间,所以这里首先调用相对应的函数usb_buffer_free()来释放这两段空间,然后,然后咱们曾经调用usb_set_intfdata来令us->pusb_intf的所对应的设备的driver_data指向了us,所以这里usb_set_intfdata()再次调用从而让us->pusb_intf的driver_data指向空.最后,调用kfree释放us.好了,终于,世界清静了.
最后的最后,我们还有一点点内容要讲,那就是分析一下我们故事中的锁机制,至少我们曾经承诺过,要到最后才能讲锁机制,理由很简单,只有我们把整个故事都弄明白了,我们才可能弄清楚为什么在某个地方要加锁,因为锁机制永远都是牵连着多处的代码的,它不是一个人在战斗!