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

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/fudan_abc/article/details/6919253

我们刚刚跟着storage_probe()几乎完整地走了一遍,貌似一切都该结束了,可是你不觉得你到目前为止还根本没有看明白设备究竟怎么工作的吗?U盘,不仅仅是USB设备,还是“盘”,它还需遵守USB Mass Storage协议,以及Transparent SCSI规范。从驱动程序的角度来看,它和一般的SCSI磁盘差不多。正是因为如此,所以U盘的工作真正需要的是四个模块,usbcore,scsi_mod,sd_mod,以及咱们这里的usb-storage,其中sd_mod恰恰就是SCSI硬盘的驱动程序。没有它,你的SCSI硬盘就别想在Linux下面转起来。

那么我们从哪里开始去接触这些SCSI命令呢?别忘了我们现在的主题,内核守护进程,别忘了我们曾经有一段代码只讲到一半就没讲了。没错,那就是usb_stor_control_thread(),当初我们用kthread_create创建它时就说了,从此以后一个进程变成两个进程。而我们刚才沿着storage_probe讲完的是父进程,父进程最终返回了,而子进程则没有那么简单,我们已经说过,usb_stor_control_thread()中的死循环注定了这个子进程是一个永恒的进程,只要这个模块还没有被卸载,或者说还没有被要求卸载,这个子进程就必将永垂不朽地战斗下去。于是让我们推开记忆的门,回过来看这个函数,当初我们讲到了308行,由于us->sema一开始就是锁着的,所以down_interruptible这里一开始就进入睡眠了,只有在接到唤醒的信号或者锁被释放了释放锁的进程来唤醒它,它才会醒过来。那么谁来释放这把锁呢?

有两个地方,一个是这个模块要卸载了,这个我们稍后来看。另一个就是有SCSI命令发过来了。SCSI命令从哪里发过来?很简单,SCSI核心层,硬件上来说,SCSI命令就是SCSI主机到SCSI设备,而从代码的角度来说,SCSI核心层要求为每一个SCSI主机实现一个queuecommand命令,每一次应用层发送来了SCSI命令了,比如你去读写/dev/sda,最终SCSI核心层就会调用与该主机相对应queuecommand,(确切地说是structScsi_Host结构体中的成员structscsi_host_template中的成员指针queuecommand,这是一个函数指针。)那么我们来看,当初我们定义的struct scsi_host_template usb_stor_host_template,其中的确有一个queuecommand,我们赋给它的值也是queuecommand,即我们让queuecommand指向一个叫做queuecommand的函数,在struct scsi_host_template的定义中,函数指针的原型来自include/scsi/scsi_host.h:

124    int (*queuecommand)(struct scsi_cmnd *,

125                       void (*done)(struct scsi_cmnd *));

而我们所定义的queuecommand()函数又在哪里呢?在drivers/usb/storage/scsiglue.c中:

208 /* queue a command */

209 /* This is always called with scsi_lock(host)held */

210 static int queuecommand(struct scsi_cmnd *srb,

211                        void (*done)(struct scsi_cmnd *))

212 {

213   struct us_data *us =host_to_us(srb->device->host);

214

215    US_DEBUGP("%scalled\n", __FUNCTION__);

216

217     /*check for state-transition errors */

218   if (us->srb != NULL) {

219        printk(KERN_ERR USB_STORAGE "Error in %s: us->srb =%p\n",

220                        __FUNCTION__, us->srb);

221        return SCSI_MLQUEUE_HOST_BUSY;

222   }

223

224   /* fail the command if we aredisconnecting */

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

226        US_DEBUGP("Fail command during disconnect\n");

227        srb->result = DID_NO_CONNECT << 16;

228        done(srb);

229       return 0;

230 }

231

232    /* enqueuethe command and wake up the control thread */

233    srb->scsi_done= done;

234   us->srb = srb;

235   up(&(us->sema));

236

237    return 0;

238 }

这个函数不长,它的使命也很简单,就是为了唤醒那个沉睡中的守护进程,告诉它不能再沉睡。

我们来仔细看一下,213行,us的身影无处不在。

218行,判断us->srb,事到如今,我们不得不去面对一个新的数据结构,它就是struct scsi_cmnd。queuecommand()函数的第1个参数就是structscsi_cmnd指针,而structus_data中也有一个structscsi_cmnd *srb,也是一个指针。

那么我们来看structscsi_cmnd,这个数据结构的意义很明显,就是代表一个SCSI命令。它定义于include/scsi/scsi_cmnd.h中。如果你感兴趣可以去看一下,我们只要知道有这么一个数据结构就可以了,同时需要知道在us中有一个成员srb,由它来指向scsi命令。

继续说218行,看一下us->srb是不是为空,如果为空我们才可以继续往下走去唤醒那个守护进程,否则就说明之前的一个命令还没有执行完。它会执行完一个命令就会把它设为空。显然us->srb为空,因为还没有任何人为它赋过值,只是初始化us时把所有元素都初始化为0了,所以这第一次来到这里时肯定是空。这里如果不为空就返回SCSI_MLQUEUE_HOST_BUSY给SCSI Core,这样核心层就知道,这边主机正忙着呢,先不急着执行下面的命令。

225行, US_FLIDX_DISCONNECTING这个flag我们已经不是第一次遇见了,无须多讲,现在只是不知道究竟是哪里设置了这个flag,日后我们看到storage_disconnect就知道了。这里和以往一样,如果这个flag设置了,赶紧结束吧。设置srb->result让scsi core知道这里已经断开连接了。而queuecommand命令本身就返回0。不过我们需要注意的是228行这个done函数,仔细看这个done是queuecommand()函数的第2个参数,是一个函数指针,实际上scsi core调用queuecommand时传递的参数名字就叫做scsi_done,这就是一个函数名,SCSI核心层定义了一个叫做scsi_done的函数。SCSI核心层要求当低层的驱动程序完成一个命令后要调用这个函数去通知SCSI核心层,这实际上就相当于一种中断机制。scsi核心层调用了queuecommand()之后它就不管事了,它就去干别的了,等底层的代码把这个queuecommand执行完了之后,或者准确地说当底层把命令执行完了之后,就调用scsi_done从而scsi核心层就知道这个命令完成了,然后它就会接着做一些它该做的事情比如清理这个命令,或者别的一些收尾的工作。

所以这里我们看到,如果设备已经设置了断开的flag,那么这里就执行done,如果没有断开那就在下面的233行设置srb->scsi_done等于这个done,实际上就是等于scsi_done,这两个scsi_done,一个是struct scsi_cmnd *srb的成员指针,一个是SCSI核心层的函数名。虽然它们同名,但是是两个不同的东西。

最后,234行,令us->srb等于这个srb。而235行,这正是我们苦苦寻找的代码,正是这个up(&us->sema),唤醒了我们的守护进程。之后,237行,这个函数本身就结束了。而我们显然就该去看那个usb_stor_control_thread()了。因为,它醒了,它终于醒了。
展开阅读全文

没有更多推荐了,返回首页