概述
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_device
struct list_head __targets; // 管理Host下的所有scsi_target
struct 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; // 标识是否是单Lun
unsigned 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设备信息
文章来源:https://www.toymoban.com/news/detail-678677.html
IO请求队列初始化
分配scsi_device结构时,也会初始化设备的IO请求队列。根据主机适配器是否支持多队列,初始化函数也会不同。对于单队列,SCSI使用scsi_old_alloc_queue函数分配IO请求队列。
文章来源地址https://www.toymoban.com/news/detail-678677.html
相关参考
- 《存储技术原理分析:基于Linux 2.6内核源代码分析》
到了这里,关于浅析Linux SCSI子系统:设备管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!