Linux iscsi_queuecommand scsi命令派发与ris段构建
2026/6/13 16:56:55 网站建设 项目流程

Linux iscsi_queuecommand scsi命令派发与ris段构建

iscsi_queuecommand是软件iSCSI传输层SCSI命令派发的入口函数,位于drivers/scsi/iscsi_tcp.c。当SCSI中层通过scsi_dispatch_cmd调用到LLD的queuecommand回调时,该函数负责将SCSI命令封装为iSCSI PDU并通过TCP连接发送到目标端。

函数实现:

```c
static int iscsi_queuecommand(struct Scsi_Host *shost,
struct scsi_cmnd *sc)
{
struct iscsi_cls_session *cls_session;
struct iscsi_session *session;
struct iscsi_conn *conn;
struct iscsi_cmd *cmd;
struct iscsi_tcp_conn *tcp_conn;
struct iscsi_sw_tcp_conn *sw_tcp_conn;
int rc;

cls_session = starget_to_session(scsi_target(sc->device));
session = cls_session->dd_data;
conn = session->leadconn;

if (!conn || conn->stop_stage)
return SCSI_MLQUEUE_HOST_BUSY;

/* 从命令池中分配一个cmd结构 */
spin_lock(&session->lock);
cmd = __iscsi_get_cmd(session);
spin_unlock(&session->lock);
if (!cmd)
return SCSI_MLQUEUE_HOST_BUSY;

cmd->sc = sc;
cmd->session = session;
cmd->conn = conn;
cmd->data_count = 0;
cmd->r2t_enabled = 0;
sc->scsi_done = iscsi_scsi_cmd_done;

/* 构建iSCSI SCSI Command PDU */
rc = iscsi_prep_scsi_cmd_pdu(cmd);
if (rc) {
iscsi_put_cmd(cmd);
return SCSI_MLQUEUE_HOST_BUSY;
}

/* 将命令加入会话队列并发送 */
spin_lock_bh(&session->lock);
list_add_tail(&cmd->list, &session->sess_cmds);
session->queued_cmdsn++;
spin_unlock_bh(&session->lock);

rc = iscsi_xmit_pdu(cmd);
if (rc) {
iscsi_conn_failure(conn, ISCSI_ERR_XMIT_FAILED);
iscsi_put_cmd(cmd);
return SCSI_MLQUEUE_HOST_BUSY;
}

return 0;
}
```

iscsi_prep_scsi_cmd_pdu负责构建iSCSI协议头部,将SCSI CDB封装进iSCSI命令PDU:

```c
static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd *cmd)
{
struct scsi_cmnd *sc = cmd->sc;
struct iscsi_session *session = cmd->session;
struct iscsi_scsi_req *hdr;
unsigned int hdrlen = sizeof(struct iscsi_scsi_req);
int rc;

/* 分配PDU头部缓冲 */
if (iscsi_alloc_pdu(cmd, hdrlen))
return -ENOMEM;

hdr = (struct iscsi_scsi_req *)cmd->pdu;
memset(hdr, 0, hdrlen);

hdr->opcode = ISCSI_OP_SCSI_CMD | ISCSI_OP_IMMEDIATE;
hdr->flags = ISCSI_ATTR_SIMPLE;
hdr->cmdsn = cpu_to_be32(session->cmdsn);
hdr->exp_statsn = cpu_to_be32(session->exp_statsn);
session->cmdsn++;
hdr->itt = cpu_to_be32(cmd->cmd_it_id);

/* 拷贝SCSI CDB */
memcpy(hdr->cdb, sc->cmnd, sc->cmd_len);

/* 设置读写标志 */
if (sc->sc_data_direction == DMA_TO_DEVICE) {
hdr->flags |= ISCSI_FLAG_CMD_WRITE;
} else if (sc->sc_data_direction == DMA_FROM_DEVICE) {
hdr->flags |= ISCSI_FLAG_CMD_READ;
}

/* 设置数据传输长度 */
hdr->data_length = cpu_to_be32(scsi_bufflen(sc));

/* 构建数据段描述符(ris - residual/segment) */
rc = iscsi_build_segment_descriptors(cmd, sc);
if (rc)
return rc;

return 0;
}
```

ris段构建的核心函数iscsi_build_segment_descriptors将scsi_cmnd的scsi_data_buffer映射到iSCSI协议的数据段描述符中:

```c
static int iscsi_build_segment_descriptors(struct iscsi_cmd *cmd,
struct scsi_cmnd *sc)
{
struct iscsi_conn *conn = cmd->conn;
struct scatterlist *sg;
unsigned int sg_count, i;
int rc;

if (scsi_bufflen(sc) == 0)
return 0;

/* 获取scatter-gather列表 */
sg = scsi_sglist(sc);
sg_count = scsi_sg_count(sc);

/* 建立发送数据段 */
if (sc->sc_data_direction == DMA_TO_DEVICE || sc->sc_data_direction == DMA_BIDIRECTIONAL) {
cmd->data_seg.sg = sg;
cmd->data_seg.sg_count = sg_count;
cmd->data_seg.total_size = scsi_bufflen(sc);
cmd->data_seg.sent = 0;
cmd->data_seg.offset = 0;

for_each_sg(sg, sg, sg_count, i) {
cmd->data_seg.size += sg->length;
}
}

/* 建立接收数据段 */
if (sc->sc_data_direction == DMA_FROM_DEVICE || sc->sc_data_direction == DMA_BIDIRECTIONAL) {
cmd->data_seg.recv_sg = sg;
cmd->data_seg.recv_sg_count = sg_count;
cmd->data_seg.recv_total_size = scsi_bufflen(sc);
cmd->data_seg.recv_offset = 0;
}

return 0;
}
```

iscsi_xmit_pdu通过TCP socket发送PDU和负载数据:

```c
static int iscsi_xmit_pdu(struct iscsi_cmd *cmd)
{
struct iscsi_conn *conn = cmd->conn;
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
struct iscsi_sw_tcp_conn *sw_tcp_conn = tcp_conn->dd_data;
struct socket *sock = sw_tcp_conn->sock;
struct kvec iov;
struct msghdr msg = {
.msg_flags = MSG_DONTWAIT | MSG_NOSIGNAL,
};
int rc;

if (!sock)
return -ENOTCONN;

/* 发送PDU头部 */
iov.iov_base = cmd->pdu;
iov.iov_len = cmd->pdu_len;
rc = kernel_sendmsg(sock, &msg, &iov, 1, iov.iov_len);
if (rc < 0)
return rc;

/* 发送数据负载(如果有) */
if (cmd->data_seg.total_size > 0) {
rc = iscsi_tcp_send_data(cmd);
if (rc < 0)
return rc;
}

/* 记录发送统计 */
conn->txdata_octets += cmd->pdu_len + cmd->data_seg.sent;

return 0;
}
```

对于写命令(WRITE),数据负载通过iscsi_tcp_send_data逐段发送,该函数将scatterlist中的物理页数据通过TCP零拷贝发送:

```c
static int iscsi_tcp_send_data(struct iscsi_cmd *cmd)
{
struct iscsi_conn *conn = cmd->conn;
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
struct socket *sock = tcp_conn->sock;
struct scatterlist *sg;
unsigned int offset, remaining;
int rc;

remaining = cmd->data_seg.total_size - cmd->data_seg.sent;
offset = cmd->data_seg.offset;
sg = cmd->data_seg.sg;

while (remaining > 0) {
unsigned int sg_offset = sg->offset + offset;
unsigned int sg_len = min(sg->length - offset, remaining);

rc = tcp_conn->sendpage(sock, sg_page(sg), sg_offset,
sg_len, MSG_DONTWAIT | MSG_NOSIGNAL);
if (rc <= 0) {
if (rc == -EAGAIN) {
/* TCP发送缓冲满,调度重试 */
iscsi_requeue_cmd(cmd);
return 0;
}
return rc;
}

cmd->data_seg.sent += rc;
remaining -= rc;
offset += rc;

if (offset >= sg->length) {
offset = 0;
sg = sg_next(sg);
if (!sg && remaining > 0)
return -EIO;
}
}

return 0;
}
```

iSCSI命令完成后,目标端返回SCSI Response PDU,驱动通过iscsi_scsi_cmd_done回调通知SCSI中层命令完成,释放命令槽,更新CMDSN/STATSN等会话级序列号。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询