发布时间:2014-09-05 16:44:51作者:知识屋
我们刚刚跟着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 } www.zhishiwu.com
这个函数不长,它的使命也很简单,就是为了唤醒那个沉睡中的守护进程,告诉它不能再沉睡。
我们来仔细看一下,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()了。因为,它醒了,它终于醒了。
linux一键安装web环境全攻略 在linux系统中怎么一键安装web环境方法
Linux网络基本网络配置方法介绍 如何配置Linux系统的网络方法
Linux下DNS服务器搭建详解 Linux下搭建DNS服务器和配置文件
对Linux进行详细的性能监控的方法 Linux 系统性能监控命令详解
linux系统root密码忘了怎么办 linux忘记root密码后找回密码的方法
Linux基本命令有哪些 Linux系统常用操作命令有哪些
Linux必学的网络操作命令 linux网络操作相关命令汇总
linux系统从入侵到提权的详细过程 linux入侵提权服务器方法技巧
linux系统怎么用命令切换用户登录 Linux切换用户的命令是什么
在linux中添加普通新用户登录 如何在Linux中添加一个新的用户
2012-07-10
CentOS 6.3安装(详细图解教程)
Linux怎么查看网卡驱动?Linux下查看网卡的驱动程序
centos修改主机名命令
Ubuntu或UbuntuKyKin14.04Unity桌面风格与Gnome桌面风格的切换
FEDORA 17中设置TIGERVNC远程访问
StartOS 5.0相关介绍,新型的Linux系统!
解决vSphere Client登录linux版vCenter失败
LINUX最新提权 Exploits Linux Kernel <= 2.6.37
nginx在网站中的7层转发功能