文章目录
- 概述
- 设备管理数据结构
- scsi_host_template:SCSI主机适配器模板
- scsi_host:SCSI主机适配器
- 主机适配器支持DIF
- scsi_target:SCSI目标节点
- scsi_device:SCSI设备
- 添加主机适配器
- 构建sysfs目录
- 添加SCSI设备
- 挂载Lun
- IO请求队列初始化
- 相关参考
概述
Linux SCSI子系统通过SCSI主机适配器(HBA)接入所有SCSI存储设备,在Linux系统中,可以安装多种主机适配器,SCSI中层会提供主机适配器的统一抽象,这些主机适配器的厂商提供具体的低层驱动实现;主机适配器接入到SCSI子系统后,SCSI会通过扫描或者低层驱动主动上报的方式,接入主机适配器下挂的所有SCSI存储设备。
设备管理数据结构
Linux SCSI子系统通过Scsi_Host、scsi_target和scsi_device数据结构分别来描述SCSI主机适配器、目标节点和逻辑单元,它们之间的关系如下:
- Linux系统支持安装多个主机适配器,所有接入的主机适配器在SCSI中层都会有对应的Scsi_Host结构。Scsi_Host结构描述了SCSI主机适配器的通用属性和方法,由低层驱动根据scsi_host_template进行创建并注册到SCSI子系统中;
- Scsi_Host维护了两个设备链表:target链表和device链表,其中target链表管理所有的scsi_targe结构,device链表管理所有的scsi_device结构。
scsi_host_template:SCSI主机适配器模板
scsi_host_template描述了SCSI主机适配器的公共属性和接口,包括主机队列深度、命令处理回调、错误处理回调等。低层驱动自定义SCSI主机适配器模板,SCSI中层会提供接口由低层驱动调用,根据SCSI主机适配器模板信息生成相应的Scsi_Host实例。
struct scsi_host_template {struct module *module;const char *name;int (* queuecommand)(struct Scsi_Host *, struct scsi_cmnd *); // SCSI命令下发接口int (* eh_abort_handler)(struct scsi_cmnd *); // 错误恢复:取消指定的SCSI命令int (* eh_device_reset_handler)(struct scsi_cmnd *); // 错误恢复:复位SCSI设备int (* eh_target_reset_handler)(struct scsi_cmnd *); // 错误恢复:复位SCSI目标节点int (* eh_bus_reset_handler)(struct scsi_cmnd *); // 错误恢复:复位SCSI总线int (* eh_host_reset_handler)(struct scsi_cmnd *); // 错误恢复:复位SCSI主机适配器int (* slave_alloc)(struct scsi_device *); // 添加SCSI设备时,中层调用让驱动传递设备私有数据int (* slave_configure)(struct scsi_device *); void (* slave_destroy)(struct scsi_device *); // 删除SCSI设备时,中层调用让驱动释放设备私有数据int (* target_alloc)(struct scsi_target *); // 添加SCSI目标时,中层调用让驱动传递设备私有数据void (* target_destroy)(struct scsi_target *); // 删除SCSI目标时,中层让驱动释放设备私有数据int (* scan_finished)(struct Scsi_Host *, unsigned long);void (* scan_start)(struct Scsi_Host *);int (* change_queue_depth)(struct scsi_device *, int); // 调整SCSI设备的队列深度int (* map_queues)(struct Scsi_Host *shost);enum blk_eh_timer_return (*eh_timed_out)(struct scsi_cmnd *); // 低层驱动自定义IO超时处理策略int (*host_reset)(struct Scsi_Host *shost, int reset_type);int can_queue;int this_id;unsigned short sg_tablesize;unsigned short sg_prot_tablesize;unsigned int max_sectors;unsigned long dma_boundary;short cmd_per_lun;unsigned char present;int tag_alloc_policy;unsigned track_queue_depth:1;unsigned supported_mode:2;unsigned unchecked_isa_dma:1;unsigned use_clustering:1;unsigned emulated:1;unsigned skip_settle_delay:1;unsigned no_write_same:1;unsigned force_blk_mq:1;unsigned int max_host_blocked;
};
scsi_host:SCSI主机适配器
SCSI主机适配器通常也是PCI设备,由内核的PCI子系统负责扫描接入。
struct Scsi_Host {struct list_head __devices; // 管理Host下的所有scsi_devicestruct list_head __targets; // 管理Host下的所有scsi_targetstruct list_head starved_list;struct list_head eh_cmd_q;struct task_struct * ehandler; struct completion * eh_action; wait_queue_head_t host_wait;struct scsi_host_template *hostt; // 指向主机适配器模板的指针struct scsi_transport_template *transportt;union {struct blk_queue_tag *bqt;struct blk_mq_tag_set tag_set;};atomic_t host_busy; atomic_t host_blocked;unsigned int host_failed; unsigned int host_eh_scheduled; unsigned int host_no; int eh_deadline;unsigned long last_reset;unsigned int max_channel;unsigned int max_id;u64 max_lun;unsigned int unique_id;unsigned short max_cmd_len;int this_id;int can_queue;short cmd_per_lun;short unsigned int sg_tablesize;short unsigned int sg_prot_tablesize;unsigned int max_sectors;unsigned long dma_boundary;unsigned nr_hw_queues;...char work_q_name[20];struct workqueue_struct *work_q;struct workqueue_struct *tmf_work_q;unsigned int max_host_blocked;unsigned int prot_capabilities;unsigned char prot_guard_type;enum scsi_host_state shost_state;struct device shost_gendev, shost_dev;void *shost_data;unsigned long hostdata[0] __attribute__ ((aligned (sizeof(unsigned long)))); // 可用于存储低层驱动私有数据
}
主机适配器支持DIF
Scsi_Host的prot_capabilities字段描述了SCSI主机适配器支持DIF的能力。
enum scsi_host_prot_capabilities {SHOST_DIF_TYPE1_PROTECTION = 1 << 0, /* T10 DIF Type 1 */SHOST_DIF_TYPE2_PROTECTION = 1 << 1, /* T10 DIF Type 2 */SHOST_DIF_TYPE3_PROTECTION = 1 << 2, /* T10 DIF Type 3 */SHOST_DIX_TYPE0_PROTECTION = 1 << 3, /* DIX between OS and HBA only */SHOST_DIX_TYPE1_PROTECTION = 1 << 4, /* DIX with DIF Type 1 */SHOST_DIX_TYPE2_PROTECTION = 1 << 5, /* DIX with DIF Type 2 */SHOST_DIX_TYPE3_PROTECTION = 1 << 6, /* DIX with DIF Type 3 */
}
scsi_target:SCSI目标节点
struct scsi_target {struct scsi_device *starget_sdev_user;struct list_head siblings; // 用于挂接在Host的__targets链表中struct list_head devices; // 管理目标节点下的所有SCSI设备的链表struct device dev;struct kref reap_ref; unsigned int channel;unsigned int id; unsigned int create:1; unsigned int single_lun:1; // 标识是否是单Lununsigned int pdt_1f_for_no_lun:1; unsigned int no_report_luns:1;unsigned int expecting_lun_change:1; atomic_t target_busy;atomic_t target_blocked;unsigned int can_queue;unsigned int max_target_blocked;char scsi_level;enum scsi_target_state state;void *hostdata; // 驱动私有数据unsigned long starget_data[0]; // 驱动私有数据
}
scsi_device:SCSI设备
在SCSI子系统的语义中,SCSI设备对应的才是逻辑单元的概念,也就是我们常说的Lun。
struct scsi_device {struct Scsi_Host *host;struct request_queue *request_queue; // IO请求队列struct list_head siblings; // 用于链接到Host的__devices链表struct list_head same_target_siblings; atomic_t device_busy;atomic_t device_blocked; spinlock_t list_lock;struct list_head cmd_list; // 下发到设备的SCSI命令链表struct list_head starved_entry;unsigned short queue_depth; unsigned short max_queue_depth; unsigned short last_queue_full_depth; unsigned short last_queue_full_count; unsigned long last_queue_full_time;unsigned long queue_ramp_up_period;unsigned long last_queue_ramp_up;unsigned int id, channel;u64 lun;unsigned int manufacturer; unsigned sector_size; // 扇区大小void *hostdata; unsigned char type;char scsi_level;char inq_periph_qual; ...struct list_head event_list;struct work_struct event_work;unsigned int max_device_blocked; atomic_t iorequest_cnt;atomic_t iodone_cnt;atomic_t ioerr_cnt;struct device sdev_gendev,sdev_dev;struct execute_work ew; /* used to get process context on put */struct work_struct requeue_work;struct scsi_device_handler *handler;void *handler_data;unsigned char access_state;struct mutex state_mutex;enum scsi_device_state sdev_state;struct task_struct *quiesced_by;unsigned long sdev_data[0];
}
添加主机适配器
驱动添加主机适配器前,需要先调用scsi_alloc_host分配Scsi_Host结构。scsi_alloc_host根据驱动填写的主机适配器模板对Scsi_Host结构进行初始化,同时允许驱动传入特定的size,这样SCSI中层在分配Scsi_Host结构时,会在尾部申请额外的空间存储驱动的私有数据。
完成Scsi_Host的结构申请后,驱动调用scsi_add_host将主机适配器添加到系统中:
构建sysfs目录
添加主机适配器的过程中,一个很重要的部分就是在sysfs文件系统构建相关的节点,以支持应用程序访问SCSI子系统的相关信息。
添加SCSI设备
无论是SCSI总线扫描或者是驱动发现的SCSI设备,最后都需要调用scsi_add_device接口将设备添加到系统中。scsi_add_device执行流程如下:
- 确认SCSI目标节点在系统中是否存在,不存在就会创建新的scsi_target结构;
- 向SCSI目标节点中添加Lun,即scsi_device。
挂载Lun
scsi_probe_and_add_lun负责挂载Lun设备到系统中,它的执行流程如下:
- 确认SCSI设备在系统中是否存在,不存在则创建新的scsi_device结构;
- 向设备发送INQUIRY命令,设备在正常接入的情况下,会返回INQUIRY数据;
- 解析INQUIRY数据,初始化SCSI设备信息
IO请求队列初始化
分配scsi_device结构时,也会初始化设备的IO请求队列。根据主机适配器是否支持多队列,初始化函数也会不同。对于单队列,SCSI使用scsi_old_alloc_queue函数分配IO请求队列。
相关参考
- 《存储技术原理分析:基于Linux 2.6内核源代码分析》