SCST基础概念深度解析

概述

SCST (SCSI Target Subsystem for Linux) 是Linux内核中用于构建SCSI目标设备(Target)的高性能框架。它允许Linux系统将本地存储资源通过各种SCSI传输协议(如iSCSI、FC、SRP等)导出给远程主机使用。本文将深入解析SCST的核心概念、架构设计和关键数据结构。

SCST架构概览

SCST采用分层架构设计,主要包含三个核心层次:

graph TB
    subgraph "Target Drivers Layer"
        A[iSCSI Target]
        B[FC Target]
        C[SRP Target]
        D[User Space Target]
    end

    subgraph "SCST Core Layer"
        E[Command Processing]
        F[Session Management]
        G[Device Management]
        H[Memory Management]
    end

    subgraph "Device Handlers Layer"
        I[VDISK Handler]
        J[SCST_USER Handler]
        K[Pass-through Handler]
        L[CDROM Handler]
    end

    subgraph "Backend Storage"
        M[Block Devices]
        N[Files]
        O[User Space Programs]
    end

    A --> E
    B --> E
    C --> E
    D --> E

    E --> I
    E --> J
    E --> K
    E --> L

    I --> M
    I --> N
    J --> O
    K --> M
    L --> M

三层架构详解

  1. Target Drivers Layer (目标驱动层)

    • 负责具体传输协议的实现(iSCSI、FC、SRP等)
    • 处理协议特定的连接管理和数据传输
    • 将接收到的SCSI命令转换为SCST内部格式
  2. SCST Core Layer (核心层)

    • 命令处理引擎:管理命令的完整生命周期
    • 会话管理:维护initiator与target之间的会话
    • 设备管理:管理导出的SCSI设备
    • 内存管理:高效的scatter-gather缓冲区管理
  3. Device Handlers Layer (设备处理层)

    • 实现具体的SCSI设备类型逻辑
    • VDISK:将文件或块设备模拟为SCSI磁盘
    • SCST_USER:允许用户空间程序实现SCSI设备
    • Pass-through:直接将命令传递给后端SCSI设备

核心数据结构

1. scst_cmd - SCSI命令结构

scst_cmd是SCST中最核心的数据结构,代表一个正在处理的SCSI命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
struct scst_cmd {
/* 命令链表入口,用于各种队列 */
struct list_head cmd_list_entry;

/* 指向命令处理线程池 */
struct scst_cmd_threads *cmd_threads;

/* 分配的处理线程(提升CPU亲和性) */
struct scst_cmd_thread_t *cmd_thr;

/* 所属会话 */
struct scst_session *sess;

/* 引用计数,用于生命周期管理 */
atomic_t cmd_ref;

/* 命令状态,SCST_CMD_STATE_* 常量之一 */
enum scst_cmd_state state;

/* 上次状态更新时间(100纳秒单位) */
ktime_t last_state_update;

/* 命令标志位 */
unsigned int sent_for_exec:1; /* 已发送执行 */
unsigned int sn_set:1; /* 序列号已设置 */
unsigned int completed:1; /* 命令已完成 */
unsigned int atomic:1; /* 原子上下文处理 */
unsigned int internal:1; /* 内部生成命令 */

/* SCSI CDB(命令描述块) */
uint8_t cdb[SCST_MAX_CDB_SIZE];
unsigned int cdb_len;

/* 数据方向:READ、WRITE、NONE */
scst_data_direction data_direction;

/* 数据缓冲区(scatter-gather list) */
struct scatterlist *sg;
int sg_cnt;
unsigned int bufflen;

/* 关联的目标设备 */
struct scst_tgt_dev *tgt_dev;

/* SCSI状态和sense数据 */
uint8_t status;
uint8_t msg_status;
uint8_t sense[SCST_SENSE_BUFFERSIZE];
int sense_valid_len;
};

关键字段说明:

  • state:命令当前所处的状态机状态,驱动整个命令处理流程
  • cmd_ref:引用计数器,防止命令在异步处理过程中被过早释放
  • sg/sg_cnt:scatter-gather列表,支持高效的零拷贝数据传输
  • tgt_dev:关联的目标设备,连接命令与具体的后端设备

2. scst_tgt - Target结构

scst_tgt代表一个SCSI Target端口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct scst_tgt {
/* 该target下的会话列表,受scst_mutex保护 */
struct list_head sess_list;

/* 在sysfs中注册的会话列表 */
struct list_head sysfs_sess_list;

/* 在template的tgts_list中的链表入口 */
struct list_head tgt_list_entry;

/* 对应的target template */
struct scst_tgt_template *tgtt;

/* 默认访问控制组 */
struct scst_acg *default_acg;

/* Target ACG组列表 */
struct list_head tgt_acg_list;

/* Target名称 */
char *tgt_name;

/* SG表大小限制 */
int sg_tablesize;

/* Target驱动私有数据 */
void *tgt_priv;

/* DIF(数据完整性字段)支持标志 */
unsigned tgt_dif_supported:1;
unsigned tgt_hw_dif_type1_supported:1;
unsigned tgt_hw_dif_type2_supported:1;
};

设计要点:

  • 一个target可以有多个session(多个initiator连接)
  • 通过ACG(Access Control Group)实现细粒度的访问控制
  • sg_tablesize限制单个I/O的scatter-gather条目数量

3. scst_session - 会话结构

scst_session代表initiator与target之间的一个会话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct scst_session {
/* 初始化阶段,SCST_SESS_IPH_* 常量 */
int init_phase;

/* 所属target */
struct scst_tgt *tgt;

/* Target驱动私有数据 */
void *sess_tgt_priv;

/* 会话异步标志 */
unsigned long sess_aflags;

/* 保护sess_tgt_dev_list修改的互斥锁 */
struct mutex tgt_dev_list_mutex;

/* 该会话的tgt_dev哈希表 */
#define SESS_TGT_DEV_LIST_HASH_SIZE (1 << 5)
struct list_head sess_tgt_dev_list[SESS_TGT_DEV_LIST_HASH_SIZE];

/* 会话中的命令列表,受sess_list_lock保护 */
struct list_head sess_cmd_list;

/* Initiator名称 */
char *initiator_name;

/* 会话ID */
uint64_t sid;

/* 命令序列号跟踪 */
atomic_t num_cmds;

/* 引用计数 */
struct kref sess_kref;
};

会话生命周期:

  1. scst_register_session() - 注册会话
  2. 命令处理阶段 - initiator通过会话发送命令
  3. scst_unregister_session() - 注销会话,清理资源

4. scst_dev - 设备结构

scst_dev代表一个SCSI逻辑设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct scst_dev {
/* 设备处理器 */
struct scst_dev_type *handler;

/* 设备名称 */
char *virt_name;

/* 设备私有数据 */
void *dh_priv;

/* 设备类型(DISK、TAPE、CDROM等) */
int type;

/* 设备块大小 */
int block_size;

/* 设备总大小(扇区数) */
uint64_t nblocks;

/* SCSI设备标识符 */
uint8_t t10_dev_id[20];

/* 设备标志位 */
unsigned int dev_thin_provisioned:1;
unsigned int dev_dif_type:3;
unsigned int dev_dif_mode:2;

/* 命令线程池 */
struct scst_cmd_threads cmd_threads;

/* 正在执行的命令计数 */
atomic_t on_dev_cmd_count;
};

设备类型:

  • TYPE_DISK (0) - 直接访问块设备
  • TYPE_TAPE (1) - 顺序访问设备
  • TYPE_CDROM (5) - CD-ROM设备
  • TYPE_PROCESSOR (3) - 处理器设备

命令状态机

SCST使用复杂的状态机来管理命令处理流程。状态分为”主动状态”(active states)和”被动状态”(passive states):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enum scst_cmd_state {
/* 主动状态 - SCST主动推进命令处理 */
SCST_CMD_STATE_PARSE = 0, /* 解析命令 */
SCST_CMD_STATE_PREPARE_SPACE, /* 分配数据缓冲区 */
SCST_CMD_STATE_PREPROCESSING_DONE, /* 预处理完成 */
SCST_CMD_STATE_RDY_TO_XFER, /* 准备接收数据 */
SCST_CMD_STATE_TGT_PRE_EXEC, /* Target驱动预执行 */
SCST_CMD_STATE_EXEC_CHECK_SN, /* 执行序列号检查 */
SCST_CMD_STATE_PRE_DEV_DONE, /* 设备完成前检查 */
SCST_CMD_STATE_DEV_DONE, /* 设备处理完成 */
SCST_CMD_STATE_PRE_XMIT_RESP, /* 发送响应前检查 */
SCST_CMD_STATE_XMIT_RESP, /* 发送响应 */
SCST_CMD_STATE_FINISHED, /* 命令完成 */

/* 被动状态 - 等待外部事件 */
SCST_CMD_STATE_INIT_WAIT, /* 等待初始化 */
SCST_CMD_STATE_INIT, /* LUN转换 */
SCST_CMD_STATE_DATA_WAIT, /* 等待数据从initiator */
SCST_CMD_STATE_EXEC_WAIT, /* 等待CDB执行完成 */
SCST_CMD_STATE_XMIT_WAIT, /* 等待响应传输完成 */
};

状态转换示例(READ命令):

stateDiagram-v2
    [*] --> INIT_WAIT: 命令到达
    INIT_WAIT --> INIT: scst_cmd_init_done()
    INIT --> PARSE: LUN解析完成
    PARSE --> PREPARE_SPACE: CDB解析完成
    PREPARE_SPACE --> TGT_PRE_EXEC: 缓冲区分配完成
    TGT_PRE_EXEC --> EXEC_CHECK_SN: 预执行完成
    EXEC_CHECK_SN --> REAL_EXEC: 序列号检查通过
    REAL_EXEC --> EXEC_WAIT: 提交I/O
    EXEC_WAIT --> DEV_DONE: I/O完成
    DEV_DONE --> PRE_XMIT_RESP: 设备处理完成
    PRE_XMIT_RESP --> XMIT_RESP: 检查通过
    XMIT_RESP --> XMIT_WAIT: 开始传输
    XMIT_WAIT --> FINISHED: 传输完成
    FINISHED --> [*]

执行上下文(Execution Context)

SCST支持三种执行上下文,以平衡性能和灵活性:

1
2
3
4
5
enum scst_exec_context {
SCST_CONTEXT_DIRECT, /* 直接在调用者上下文执行(中断或进程) */
SCST_CONTEXT_TASKLET, /* 在tasklet上下文执行 */
SCST_CONTEXT_THREAD /* 在内核线程上下文执行 */
};

上下文选择策略:

  1. DIRECT - 最快,但有限制:

    • 不能睡眠
    • 不能执行可能阻塞的操作
    • 适合简单的命令处理路径
  2. TASKLET - 软中断上下文:

    • 延迟执行但仍在中断上下文
    • 不能睡眠
    • 适合需要延迟但不阻塞的操作
  3. THREAD - 最灵活:

    • 可以睡眠和执行阻塞操作
    • 可以进行复杂的I/O操作
    • 性能开销最大

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 设备处理器可以返回SCST_CMD_STATE_NEED_THREAD_CTX */
static int vdisk_parse(struct scst_cmd *cmd)
{
struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv;

/* 如果在原子上下文但需要睡眠 */
if (cmd->atomic && need_sleep_operation) {
return SCST_CMD_STATE_NEED_THREAD_CTX; /* 切换到线程上下文 */
}

/* 正常处理 */
return SCST_CMD_STATE_DEFAULT;
}

内存管理 - SGV Pool

SCST使用SGV(Scatter-Gather Vector)池来高效管理内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct sgv_pool {
/* 回收列表,按大小分类 */
struct list_head recycling_lists[SGV_POOL_MAX_CACHES];

/* 池锁 */
spinlock_t sgv_pool_lock;

/* 最大缓存级别 */
int max_caches;

/* 统计信息 */
atomic_t alloc_count;
atomic_t hit_count;
atomic_t miss_count;
};

工作原理:

  1. 分配时

    • 首先尝试从相应大小的缓存中获取
    • 如果缓存为空,则分配新的sgv_pool_obj
    • 更新hit/miss统计
  2. 释放时

    • 将sgv_pool_obj放回相应的recycling_list
    • 避免频繁的内存分配/释放
  3. 刷新时(我们刚修复的死锁问题相关):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void sgv_pool_flush(struct sgv_pool *pool)
    {
    for (i = 0; i < pool->max_caches; i++) {
    spin_lock_bh(&pool->sgv_pool_lock);
    while (!list_empty(&pool->recycling_lists[i])) {
    obj = list_first_entry(&pool->recycling_lists[i],
    struct sgv_pool_obj, recycling_list_entry);
    __sgv_purge_from_cache(obj);
    spin_unlock_bh(&pool->sgv_pool_lock);
    sgv_dtor_and_free(obj);
    spin_lock_bh(&pool->sgv_pool_lock);
    }
    spin_unlock_bh(&pool->sgv_pool_lock);
    }
    }

Target驱动接口

Target驱动通过scst_tgt_template结构向SCST注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct scst_tgt_template {
/* Target驱动名称 */
const char *name;

/* 启用回调 */
int (*enable_target)(struct scst_tgt *tgt, bool enable);

/* 检查initiator是否允许访问 */
int (*is_target_enabled)(struct scst_tgt *tgt,
const uint8_t *lun, int lun_len);

/* 数据传输准备 */
int (*rdy_to_xfer)(struct scst_cmd *cmd);

/* 发送响应 */
int (*xmit_response)(struct scst_cmd *cmd);

/* 任务管理 */
void (*on_hw_pending_cmd_timeout)(struct scst_cmd *cmd);
void (*on_abort_cmd)(struct scst_cmd *cmd);

/* 会话管理 */
int (*report_aen)(struct scst_session *sess,
const struct scst_aen *aen);

/* 特性标志 */
unsigned use_clustering:1;
unsigned mq_supported:1;
};

关键回调:

  • xmit_response() - 将SCSI响应发送给initiator
  • rdy_to_xfer() - 通知initiator可以发送WRITE数据
  • on_abort_cmd() - 处理命令中止请求

设备处理器接口

设备处理器通过scst_dev_type结构注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct scst_dev_type {
/* 设备类型名称 */
const char *name;

/* SCSI设备类型代码 */
int type;

/* 命令解析 */
int (*parse)(struct scst_cmd *cmd);

/* 命令执行 */
int (*exec)(struct scst_cmd *cmd);

/* 执行完成回调 */
void (*dev_done)(struct scst_cmd *cmd);

/* 任务管理 */
void (*task_mgmt_fn_done)(struct scst_mgmt_cmd *mcmd);

/* 设备容量查询 */
void (*get_block_size)(struct scst_device *dev,
unsigned int *block_size);

/* 特性标志 */
unsigned int exec_sync:1; /* 同步执行 */
unsigned int pass_through:1; /* 透传模式 */
unsigned int cluster_support:1; /* 集群支持 */
};

性能优化设计

1. Per-CPU命令队列

1
2
3
4
5
6
7
8
9
10
struct scst_cmd_threads {
/* 每CPU的命令队列 */
struct scst_percpu_info *percpu_infos;

/* 活跃命令列表 */
struct list_head active_cmd_list;

/* 等待队列 */
wait_queue_head_t cmd_list_waitQ;
};

优势:

  • 减少锁竞争
  • 提升CPU缓存命中率
  • 支持NUMA架构

2. 命令引用计数

1
2
3
4
5
6
7
8
9
10
static inline void cmd_get(struct scst_cmd *cmd)
{
atomic_inc(&cmd->cmd_ref);
}

static inline void cmd_put(struct scst_cmd *cmd)
{
if (atomic_dec_and_test(&cmd->cmd_ref))
scst_cmd_free(cmd);
}

作用:

  • 防止命令在异步处理中被过早释放
  • 支持多个模块同时引用同一命令

3. 零拷贝数据传输

使用scatter-gather列表直接映射用户页或块设备页:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 直接映射块设备页 */
static int scst_map_sg(struct scst_cmd *cmd, struct bio *bio)
{
struct bio_vec bvec;
struct bvec_iter iter;
int i = 0;

bio_for_each_segment(bvec, bio, iter) {
sg_set_page(&cmd->sg[i++], bvec.bv_page,
bvec.bv_len, bvec.bv_offset);
}
cmd->sg_cnt = i;
return 0;
}

总结

SCST通过精心设计的分层架构、高效的数据结构和灵活的状态机,提供了一个高性能、可扩展的SCSI Target框架:

  1. 分层设计 - 清晰分离传输协议、核心逻辑和设备处理
  2. 状态机驱动 - 精确控制命令处理流程
  3. 多上下文支持 - 平衡性能与灵活性
  4. 高效内存管理 - SGV池减少分配开销
  5. 可扩展接口 - 支持各种传输协议和设备类型

理解这些基础概念是深入研究SCST实现和进行性能优化的关键。在下一篇文章中,我们将详细分析SCST的命令处理流程和状态转换机制。

参考资料

  • SCST源代码:scst/include/scst.h
  • SCST核心实现:scst/src/scst_main.c
  • Linux SCSI子系统文档:Documentation/scsi/