【转】Linux那些事儿之我是U盘(32)Scsi数据结构-像雾像雨又像风

来源:互联网 发布:生活能量软件下载 编辑:程序博客网 时间:2024/04/29 22:24
关于scsi,咱想说的是,Linux内核中,整个scsi子系统被分为三层.upper level,mid level,lower level,也许您看到这心里很烦,Linux为什么这么麻烦呢.就像某位大侠所说的:真不明白,女孩买很多很多漂亮衣服穿,就是为了吸引男孩的目光,但男孩想看的,却是不穿衣服的女孩.实际上,Linux开发者们把scsi子系统包装成很多层,是为了给您提供方便,但是您看代码的时候却会感觉很烦,要是没有那么多层该多好.

  来说说这三层吧,upper level,用伟大的汉语来讲,就是最上层,她是和操作系统打交道的,比如您要是有一块scsi硬盘,那么您就需要使用sd_mod.o这么一个模块,她实际上是与硬件无关的,是纯粹的软件上的抽象出来的数据结构组建的模块.mid level,中层,实际上这层才是真正的核心层,江湖上人称scsi-core,scsi核心层,她提供了支持scsi的核心数据结构和函数,这一层对应的模块是scsi_mod.o,系统中要想使用scsi设备,首先必须加载她,反过来,只有所有的scsi设备的模块都被卸载了才能够卸载她.陆游就曾如此形容scsi核心层,无意苦争春,一任群芳妒.

然后,lower level,底层.很不幸,如果您要写驱动,八成就是写的底层,正如现实中的我们一样,生活在社会的最底层.因为upper levelmid 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属性.这两个都是和字节对齐有关的属性,前些年像intelmicrosoft这些外企笔试面试题目中常考察字节对齐的冬冬,所以相信很多人对字节对齐并不陌生.简而言之,字节对齐是这么一回事,变量存放在内存中本来是无所谓怎么放的,因为怎么放都能访问到,但是不同的硬件访问内存的方式不一样,只有把变量按特定的规则存放在内存中,存取效率才会高,否则不按规则的存放变量将有可能降低变量存取效率.很多情况下,人们并不关心字节对齐,因为通常这些事情由编译器来处理,编译器很冰雪,她知道针对什么平台该如何对齐.但有时候,咱们需要显式的去设定对齐方式,因为有时候咱们对编译器的所作所为并不满意,或者咱们自己觉得自己指定的方式会更好,比如此处,在定义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,即将见到这个千呼万唤始出来的内核精灵. 

原创粉丝点击