open-iscsi/scst 追踪三(scsi总线扫描)

来源:互联网 发布:无网络免费单机斗地主 编辑:程序博客网 时间:2024/05/21 19:23
open-iscsi注册为一个scsi host driver,我们需要了解scsi这部分设备扫描的原理,剖析了一下。

1、写在前面

       Scsi总线在扫描磁盘设备后生成的盘符与设备通道之间的关系是不固定的,其最主要的原因是设计者考虑到scsi总线在系统中不会静态、唯一存在,会动态生成,而盘符空间在全局只有一个,因此,盘符与设备通道之间很难实现绑定,至少这种绑定关系会随着系统中scsi总线的增加而遭到破坏。所以,设计者采用了动态映射的方法维护盘符与设备通道之间的关系。

 

       盘符与设备通道之间的动态映射会影响到存储设备的管理。例如,一个存储设备由于某种原因拆除了一个磁盘,重新启动之后,所有的盘符将会重新生成,从而会导致磁盘上层的存储软件无法正常启动,除非这些存储软件能够自动识别磁盘设备,然后进行重构。显而易见,盘符与设备通道之间的动态映射增加了存储设备管理的难度。

 

       为了降低存储设备的管理难度,需要固定盘符与磁盘通道之间的映射关系。考虑到存储设备的硬件资源相对固定,所以,这种映射关系在理论上是可以固定的。为此,本文从Linux SCSI层磁盘扫描的角度对这个问题进行分析、总结。

 

2、磁盘扫描算法描述

       通常SCSI总线适配器作为PCI设备的形式存在,其在计算机体系结构中的位置描述如下图所示:


图1 scis host及device在计算机体系结构中的位置

       在系统初始化时会扫描系统PCI总线,由于scsi host adapter挂接在pci总线上,因此会被pci扫描软件扫描得到,并且生成一个pci device(PDO)。然后扫描软件需要为该pcidevice加载相应的驱动程序。在linux系统中,扫描软件会遍历pci bus上存在的所有驱动程序,检查是否有符合要求的驱动程序存在。这里假设scsihost是marwell的设备,那么,如果存在marwell提供的scsi host driver,就会被成功调用。加载scsi host驱动时,pci扫描程序会调用scsi host driver提供的probe函数,该probe函数是scsi host driver在初始化驱动时注册到pci-driver上的(Linux的总线驱动都是采用的这种思路)。在scsi host具体的probe函数中会初始化scsi host,注册中断处理函数,并且调用scsi_host_alloc函数生成一个scsi host,然后添加到scsi middle level,最后调用scsi_scan_host函数扫描scsi host adapter所管理的所有scsi总线。

 

       一个scsi host adapter可能拥有多个channel,每个channel拥有一条scsi总线。传统scsi总线是并行共享总线,现有的SATA、SAS等P2P接口在逻辑上可以理解成总线的一种特例,所以scsi middle level驱动程序是通用的。由于一个scsi host可能存在多个channel,因此依次扫描每个channel。按照spec,传统scsi bus上最多可以连接16个scsi target,因此,scsi扫描程序会依次探测target。一个scsi target可以存在多种功能,每种功能称之为LUN,对于单功能设备(例如磁盘),其LUN通常为0。

 

       Scsihost的扫描过程可以简单采用如下伪码进行描述:

For (channel = 0; channel < max_channel;channel++) {

       /*对一个适配器的每个通道中的设备进行识别 */

       For (id=0; id<max_id; id++) {

              /* 对一个通道中的每个ID对应设备进行识别 */

...

              For (lun=1; lun<max_dev_lun; lun++) {

                     /* 对一个ID对应设备的每个LUN进行识别 */

...

}

}

}

 

       通过上述扫描过程可以知道,在系统中可以采用如下方法对一个scsi device进行描述:host_id : channel_id : target_id : lun_id

 

       其中,host_id是系统动态分配的,这与PCI总线的扫描顺序相关,对于固定硬件的系统host_id扫描得到的结果不会改变,但是,如果动态添加一个scsi host(PCI device),系统的host_id可能会发生变化,这一点需要注意。

 

3、盘符名称生成方法

3.1 盘符名称生成概述

       在SCSI Disk probe的过程中,内核会为scsi disk分配盘符名称。由于scsi disk的盘符空间是全局的,而系统中的scsi host、scsi target以及scsi lun都会动态变化,所以,Linux系统没有办法为每个scsi disk分配一个固定的、简单易用的盘符名称。因此,Linux对scsi disk的盘符采用动态分配的策略。Linux在内核中维护了一棵管理设备盘符名称的树(idr_tree),当系统每次probe到一个新设备之后,都会从idr_tree中查找一个空闲节点,并且获取该空闲节点的id号,作为盘符的索引名称。

 

       例如,当系统扫描到一个新的scsi disk之后,会从idr_tree中查找并且获取一个空闲节点的id号,然后系统再将id号映射成26个英文字母,最后与”sd”一起组合成一个新的字符串,该字符串就是scsi disk的盘符。

 

       该机制的优势在于盘符管理简单,缺点在于对于一个动态系统,盘符的名称可能发生变化,影响到上层应用系统。

 

3.2 Idr_tree算法描述

       Idr_tree是内核提供的一种算法,其实现了整型id号与一个内核指针之间的映射。Idr_tree算法的实现采用了radix_tree的思想,其数据结构定义与radix tree有着类似之处。Idr_tree在2003年的时候引入至内核,目前在scsi盘符空间的管理、i2c总线设备号与设备结构之间的映射方面都有应用。

      

       在整型id与内存指针之间的映射可以采用数组的方式,但是随着存储规模的增大,数组的方式显然不能满足应用的需求;另一种方式可以采用链表进行映射信息的管理,同样不能管理较大的映射资源。所以,在Linux中采用树进行映射资源的管理。这种树就是idr_tree。

 

采用idr_tree来管理整型id与内存指针之间的映射关系具有如下两方面对优势:

1、 具有很好的映射关系查找效率。

2、 能够很好的扩展存储规模,动态增大树的规模对查找性能影响不是很大。

 

       Idr_tree的结构示意图描述如下图所示:


图 idr_tree结构示意图

 

     Idr_tree的叶子节点用于存储用户定义的数据,通常用于存储用户需要查找的内存指针。树中的每个节点定义为idr_layer,该数据结构中包含了一个bitmap位图,用于描述下层节点的属性。当下层节点不存在空穴(空闲节点)时,上层节点bitmap位图中的对应位置位。因此在查找过程中,从顶层节点开始,判断每个节点的bitmap信息,然后遍历第一个bit位为0的下层节点,直到遍历到叶子节点为止。由此可见,Idr_tree的算法复杂度与树的层次数量相关。Idr_tree是动态生成的,随着叶子节点的增多,树的层次将会不断增大。

 

4、如何绑定盘符名称与磁盘通道

       在skysan系统中需要邦定磁盘通道与盘符名称,因此需要对现有的scsi disk设备驱动加以改进。考虑到skysan设备的磁盘不会进行大规模的变化,因此,可以采用scsi系统的host_id、channel_id、target_id和lun_id邦定盘符和磁盘通道之间的关系。

 

       Scsidisk可以采用如下所述的命名规则:

sd

Host_id

Channel_id

Target_id

Scsi disk的前缀

Scsi host的ID号,映射成字符

Scsi host通道号,数字表示

Scsi 总线上target的ID号,映射成字符

 

       由于skysan的硬件相对固定,所以,scsi host id号不容易发生变化。但是,如果通过PCI插槽扩展SCSI host,那么该ID号可能会发生变化,这一点需要注意。Channel_id和target_id为一个scsi host内部信息,对于不同的scsi host可以拥有相同的ID号。因为,磁盘设备是单功能设备,所以,在命名规则中没有加入Lun_id。

 

       采用上述命名规则,只需要改动一下scsi disk设备驱动的probe函数就可以了,这样所有的scsi disk将不再采用idr的映射机制。原来的/dev/sda设备就会被驱动成/dev/sda0a设备。

 

5、参考文献

[1]     Linux-2.6.18源代码

 

 

原创粉丝点击