来说说这三层吧,upper level,用伟大的汉语来讲,就是最上层,她是和操作系统打交道的,比如您要是有一块scsi硬盘,那么您就需要使用sd_mod.o这么一个模块,她实际上是与硬件无关的,是纯粹的软件上的抽象出来的数据结构组建的模块.mid level,中层,实际上这层才是真正的核心层,江湖上人称scsi-core,即scsi核心层,她提供了支持scsi的核心数据结构和函数,这一层对应的模块是scsi_mod.o,系统中要想使用scsi设备,首先必须加载她,反过来,只有所有的scsi设备的模块都被卸载了才能够卸载她.陆游就曾如此形容scsi核心层,无意苦争春,一任群芳妒.
然后,是lower level,底层.很不幸,如果您要写驱动,八成就是写的底层,正如现实中的我们一样,生活在社会的最底层.因为upper level和mid level都已经基本上确定了,她们和硬件没关系.能留给您做的事情只能是底层.现实就是这样,尤其对80后的来说,生存的压力让80后不可能再如70后那样,能做的只有面对现实.
既然您需要关注的是底层,那么底层和中层或者说scsi核心层是如何打交道的呢?首先您得知道,核心层提供了很多函数供底层使用,其中上一节见到的scsi_host_alloc()函数正是核心层提供的,后面还将遇到的一些函数也来自中层,对于scsi中层提供的函数,咱们不需要去关心她究竟如何实现的,只看看她的声明即可.在include/scsi/scsi_host.h中,有这么一行,
527 extern struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *, int);
可以看到这个函数的参数有一个是struct scsi_host_template结构体的指针,而她将返回一个struct Scsi_Host结构体的指针,回想一下,前面咱们调用这个函数的那句,
790 us->host = scsi_host_alloc(&usb_stor_host_template, sizeof(us));
凭一种男人的直觉,可以猜出,这个函数申请了一个Scsi_Host结构体,并返回指向她的指针赋给us->host.从高一点的角度来说,这么一句话实际上就是在scsi核心层注册了一个scsi卡,当然这里的scsi卡是虚拟的.顺便来看看us->host.她实际上就是一个struct Scsi_Host结构体的指针,而struct Scsi_Host又是一个变态的数据结构,她的定义在include/scsi/scsi_host.h中:
376 struct Scsi_Host {
377 /*
378 * __devices is protected by the host_lock, but you should
379 * usually use scsi_device_lookup / shost_for_each_device
380 * to access it and don't care about locking yourself.
381 * In the rare case of beeing in irq context you can use
382 * their __ prefixed variants with the lock held. NEVER
383 * access this list directly from a driver.
384 */
385 struct list_head __devices;
386
387 struct scsi_host_cmd_pool *cmd_pool;
388 spinlock_t free_list_lock;
389 struct list_head free_list; /* backup store of cmd structs */
390 struct list_head starved_list;
391
392 spinlock_t default_lock;
393 spinlock_t *host_lock;
394
395 struct semaphore scan_mutex;/* serialize scanning activity */
396
397 struct list_head eh_cmd_q;
398 struct task_struct * ehandler; /* Error recovery thread. */
399 struct semaphore * eh_wait; /* The error recovery thread waits
400 on this. */
401 struct completion * eh_notify; /* wait for eh to begin or end */
402 struct semaphore * eh_action; /* Wait for specific actions on the
403 host. */
404 unsigned int eh_active:1; /* Indicates the eh thread is awake and active if
405 this is true. */
406 unsigned int eh_kill:1; /* set when killing the eh thread */
407 wait_queue_head_t host_wait;
408 struct scsi_host_template *hostt;
409 struct scsi_transport_template *transportt;
410 volatile unsigned short host_busy; /* commands actually active on low-level */
411 volatile unsigned short host_failed; /* commands that failed. */
412
413 unsigned short host_no; /* Used for IOCTL_GET_IDLUN, /proc/scsi et al. */
414 int resetting; /* if set, it means that last_reset is a valid value */
415 unsigned long last_reset;
416
417 /*
418 * These three parameters can be used to allow for wide scsi,
419 * and for host adapters that support multiple busses
420 * The first two should be set to 1 more than the actual max id
421 * or lun (i.e. 8 for normal systems).
422 */
423 unsigned int max_id;
424 unsigned int max_lun;
425 unsigned int max_channel;
426
427 /*
428 * This is a unique identifier that must be assigned so that we
429 * have some way of identifying each detected host adapter properly
430 * and uniquely. For hosts that do not support more than one card
431 * in the system at one time, this does not need to be set. It is
432 * initialized to 0 in scsi_register.
433 */
434 unsigned int unique_id;
435
436 /*
437 * The maximum length of SCSI commands that this host can accept.
438 * Probably 12 for most host adapters, but could be 16 for others.
439 * For drivers that don't set this field, a value of 12 is
440 * assumed. I am leaving this as a number rather than a bit
441 * because you never know what subsequent SCSI standards might do
442 * (i.e. could there be a 20 byte or a 24-byte command a few years
443 * down the road?).
444 */
445 unsigned char max_cmd_len;
446
447 int this_id;
448 int can_queue;
449 short cmd_per_lun;
450 short unsigned int sg_tablesize;
451 short unsigned int max_sectors;
452 unsigned long dma_boundary;
453
454 unsigned unchecked_isa_dma:1;
455 unsigned use_clustering:1;
456 unsigned use_blk_tcq:1;
457
458 /*
459 * Host has requested that no further requests come through for the
460 * time being.
461 */
462 unsigned host_self_blocked:1;
463
464 /*
465 * Host uses correct SCSI ordering not PC ordering. The bit is
466 * set for the minority of drivers whose authors actually read
467 * the spec ;)
468 */
469 unsigned reverse_ordering:1;
470
471 /*
472 * Host has rejected a command because it was busy.
473 */
474 unsigned int host_blocked;
475
476 /*
477 * Value host_blocked counts down from
478 */
479 unsigned int max_host_blocked;
480
481 /* legacy crap */
482 unsigned long base;
483 unsigned long io_port;
484 unsigned char n_io_port;
485 unsigned char dma_channel;
486 unsigned int irq;
487
488
489 unsigned long shost_state;
490
491 /* ldm bits */
492 struct device shost_gendev;
493 struct class_device shost_classdev;
494
495 /*
496 * List of hosts per template.
497 *
498 * This is only for use by scsi_module.c for legacy templates.
499 * For these access to it is synchronized implicitly by
500 * module_init/module_exit.
501 */
502 struct list_head sht_legacy_list;
503
504 /*
505 * Points to the transport data (if any) which is allocated
506 * separately
507 */
508 void *shost_data;
509 struct class_device transport_classdev;
510
511 /*
512 * We should ensure that this is aligned, both for better performance
513 * and also because some compilers (m68k) don't automatically force
514 * alignment to a long boundary.
515 */
516 unsigned long hostdata[0] /* Used for storage of host specific stuff */
517 __attribute__ ((aligned (sizeof(unsigned long))));
518 };
需要提一下最后这个元素,hostdata[0],相信您感兴趣的是__attribute__,这是gcc的关键字.她的作用是,描述函数,变量,类型的属性,很显然谭浩强大哥的C程序设计中是不会介绍这玩艺儿的,事实上大多数人也用不着知道她.不过在gcc里面,她出现的很频繁,因为她有利于代码优化和代码检查,特别是当咱们编写的是一个要在各种硬件平台上运行的操作系统的时候,这些属性是相当的有必要的.通常__attribute__(单词attribute前后各两个underscore,即下划线.)出现在定义一个变量/函数/类型的时候,她紧跟在变量/函数/类型定义的后面,此处咱们看到的__attribute__是紧跟在unsigned long hostdata[0]这样一个数组(汗,数组居然只有一个元素)后面,数组的每一个元素是一个unsigned long的类型变量,__attribute__后面的括号里则表明了属性,gcc一共支持十几种属性,其中aligned是一种,此外还有一些常用的,比如packed,noreturn,而packed其实咱们前面见过,只是没有提起.现在讲一下aligned属性和packed属性.这两个都是和字节对齐有关的属性,前些年像intel和microsoft这些外企笔试面试题目中常考察字节对齐的冬冬,所以相信很多人对字节对齐并不陌生.简而言之,字节对齐是这么一回事,变量存放在内存中本来是无所谓怎么放的,因为怎么放都能访问到,但是不同的硬件访问内存的方式不一样,只有把变量按特定的规则存放在内存中,存取效率才会高,否则不按规则的存放变量将有可能降低变量存取效率.很多情况下,人们并不关心字节对齐,因为通常这些事情由编译器来处理,编译器很冰雪,她知道针对什么平台该如何对齐.但有时候,咱们需要显式的去设定对齐方式,因为有时候咱们对编译器的所作所为并不满意,或者咱们自己觉得自己指定的方式会更好,比如此处,在定义hostdata之前的几行注释,已经很清楚地说明了为啥咱们要显式的去指定对齐方式.具体来说,unsigned long hostdata[0] __attribute__ ((aligned (sizeof(unsigned long))))就是表示hostdata[0]将以sizeof(unsigned long)字节对齐,显然不同的硬件平台sizeof(unsinged long)是不一样的.而之前咱们遇到过的那个定义于include/linux/usb_ch9.h中的struct usb_device_descriptor以及struct usb_interface_descriptor结构体,则在最后跟了这么一句:__attribute__ ((packed)),这个表示使用最小可能的对齐,从packed的字面意思也很清楚,紧凑一点,别瞎留空间,实际上这也就是给编译器的一个命令,告诉编译器,嘿,一会节省一点,别浪费空间啊.
好,介绍完Scsi_Host数据结构,咱们继续回到那曾经的usb_stor_acquire_resources()函数中来,us->host得到了她想要的,然后下面798行,只是一句赋值,把us->host->hostdata[0]赋值为(unsigned long)us,这样做有什么用咱们后面遇到了再说.
总之,scsi_host_alloc()这么一调用,就是给咱们的struct Scsi_Host结构提申请了空间,真正要想模拟一个scsi的情景,需要三个函数,scsi_host_alloc(),scsi_add_host(),scsi_scan_host().只有调用了第二个函数之后,scsi核心层才知道有这么一个host的存在,而只有第三个函数被调用了之后,真正的设备才被发现.这些函数的含义和它们的名字吻合的很好.不是吗?
最后需要指出的是,scsi_host_alloc()需要两个参数,第一个参数是struct scsi_host_template 的指针,咱们当然给了它&usb_stor_host_template,而第二个参数实际上是被称为driver自己的数据,咱们传递的是sizeof(us).这样子,scsi_host_alloc()中就会给咱们申请内存空间,即为us申请内存空间.不过有趣的是,us我们早就申请好了空间,这里多申请一份是否有必要呢?注意到struct Scsi_Host里边不是有一个hostdata么,理由是这样的,struct us_data这个冬冬是我们在usb-storage模块里边专门定义的,而一会我们和scsi层打交道,我们要注册一些函数,提供给scsi核心层,让核心层去调用,这些函数原型如何是scsi层说了算的,scsi层准备了一些函数指针,我们只是把这些指针赋好值,scsi层就知道在什么时候该调用哪个函数了.所以既然原型是人家提供的,那么人家肯定不知道我们会有一个struct us_data这么一个结构体,所以我们在定义函数的时候就不能把struct us_data当作一个参数,但是我们专门为自己这个模块准备的结构体又不可能不用,那怎么办?好办,scsi核心层是认struct Scsi_Host这个结构体的,而这个结构体在设计的时候就专门准备了一个unsigned long hostdata[0]来给别的设备驱动使用.那我们还客气什么,让这个指针指向我们的us就可以了.以后要用us再通过hostdata[0]来获得就是了.所以刚才我们看到了us->host->hostdata[0]被赋值为(unsigned long)us.这个host就是我们之后会一直用到的struct Scsi_Host结构体,hostdata[0]是一个unsigned long的变量,us是一个指针,所以这就使得这个变量的值等于这个指针指向的地址.而之后我们会经常看见scsiglue.c中的函数里边使用如下的赋值语句:
struct us_data *us = (struct us_data *) host->hostdata[0];
所以意思就很明确了.指针被定义为原来us所指向的那个地址.所以看似又定义了一个struct us_data *us,实际上只不过是原来是一个全局变量,现在是一个局部变量而已.至于在scsi_host_alloc()中又申请了一段大小为sizeof(us)的内存,既然已经有内存了就没有必要用它了,只是借用hostdata这个指针而已.当然,这样做不太合理,浪费内存,与我党长期以来坚持的艰苦朴素的作风是相违背的,在我党如今强调以艰苦奋斗为荣,以骄奢淫逸为耻的背景下,这段代码自然遭到了广大人民群众的质疑,因此毫无疑问,在最新版本的Linux内核中这段代码被修改了(用今天流行的话说就是被和谐掉了).有兴趣的同志们可以关注一下2.6.22版的内核.
最后的最后,补充一点,为什么scsi_host_alloc传递进来的是一个struct scsi_host_template指针,而返回的确是一个struct Scsi_Host指针?首先,这两个结构体包含很多相同的元素,但又不完全相同,它们协同工作,互相关联,但是各自起的作用不一样,struct Scsi_Host结构体中有一个成员,就是一个struct scsi_host_template指针,它就指向和它相关联的那个template,而struct scsi_host_template中很多个函数指针,它们的参数就是struct Scsi_Host的指针.所以这之间的关系千丝万缕,剪不断理还乱,藕断丝还连.一点不亚于曹禺先生的<<雷雨>>中那复杂的人物关系,唯一的差别只是这里没有乱伦关系罢了.好了,就这些,这种复杂的关系我们不需要去完全了解,我们要做的只是知道今后我们有且仅有一个struct Scsi_Host有且仅有一个struct scsi_host_template就可以了.
至此 , 我们终于走到了 usb_stor_acquire_resources() 中第 801 行 , 即将见到这个千呼万唤始出来的内核精灵 .