Golang中如何向驱动器发送SCSI命令

Golang中如何向驱动器发送SCSI命令 我刚接触Go语言编程,有几个问题需要请教。我已经查阅了许多关于SCSI命令的论坛,但我的需求有所不同。

  1. 向驱动器发送SCSI命令 a) 由于是专有的SCSI命令,无法使用现有的 sg3_utils。 b) 我能够构建16字节的CDB,但不知道如何将CDB发送到 /dev/ 设备。 c) 如果必须使用 exec.command,第一个参数必须是 可执行文件,因此很遗憾无法使用这个命令。

如何将CDB发送到设备并获取返回状态?

谢谢

11 回复

你会在终端上写什么?

更多关于Golang中如何向驱动器发送SCSI命令的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


@NobbZ

抱歉,我没有理解你的问题。 你是在说命令吗?

是的。exec.Commandexec.Cmd 是关于启动程序的,用于模拟你通常在终端中执行的操作。

如果您想运行一个程序,请使用 exec.Cmd;然而,如果您想写入 /dev 下的设备文件,则必须使用 Go 中的文件和 IO 相关包来打开它以进行写入。

@NobbZ

抱歉回复晚了。 我能够使用ioctl向驱动器发送命令。由于我们正在构建自己的命令发送给驱动器,因此无法在exec.Command()中使用像sg3_utils这样的现有工具。所以将代码改为打开设备并通过ioctl发送命令。使用sg_inq进行了测试。

如果能够从驱动器读取数据,我将很快发布我的代码。非常感谢@NobbZ。 我会尽快更新。

Sandy

感谢Norbert,

我查看了cmd结构,有一个问题需要澄清。

type Cmd struct {
    // Path是要运行的命令的路径。
    // 这是唯一一个必须设置为非零值的字段。

    Path string
    …
}
  1. “Path” 是否代表一个字节的操作码 (opcode)?

  2. 目前我已经硬编码了CDB[16] cdb[0] = op_code cdb[1] = wrprotect 等等…

    ret = exec.Command(…) // 不确定如何将cdb作为命令的第一个参数传递,后面跟着输入文件和dev/

你可以使用在终端上使用的任何工具,通过 exec.Cmd/exec.Command() 来实现。

sandychris: 这可能听起来很天真,但我尝试将 CDB 作为第一个参数传递给 exe.Command,然后得到了以下消息: this exec: “cdb”: executable file not found in $PATH

cdb 不是一个字节……所以我不确定你在说什么。也许展示一下真实的代码?

无论如何,如果你无法使用系统中已经存在的可执行文件,你就必须模仿它们的行为。

它们可能直接调用内核,可能自己只是通过 shell 调用另一个工具,也可能直接写入 /dev/…,我不了解你提到的工具……

目前正在Ubuntu环境下编写程序。

我使用了sg3_utils中的一个工具(sg_compare_write)

cmd := exec.Command("sg_compare_write", ...)

并且成功执行了该命令。

但由于需求发生了一些变化,不能再使用这个命令了。

所以,要么我自己编写一个可执行文件,要么找到一种向设备发送CDB的方法。

这听起来可能很天真,但我尝试将CDB作为exe.Command的第一个参数传递,并收到了以下消息:

this exec: "cdb": executable file not found in $PATH

因此,要使用exec.Command(),第一个参数必须是一个可执行文件。

如果不使用exec.Command(),是否有其他方法可以向驱动器发送CDB(例如使用ioctl?)

感谢回复。希望我能尽快解决这个问题。

sandychris:

“Path” 代表的是单字节操作码(opcode)吗?

Path 是您想要运行的可执行文件或命令的路径。基本上就是您在终端里会写的内容,区别在于它不会进行 PATH 环境变量的查找。

有一个辅助函数可以用来创建一个预填充了 PathArgsCmd 结构体:exec.Command()

您可以在我的使用 ls 命令的 Playground 示例中看到它的用法示例。

func main() {
    cmd := exec.Command("ls", "-l")
    out, err := cmd.Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("The output is:\n%s\n", out)
}

我是编程新手,这是我的源代码。目前为了测试,我硬编码了 sw_cdb

func scsi_cmd(){

var sw_cdb[16] int64

sw_cdb[0]	= opcode //专有代码
sw_cdb[1] 	= 10
sw_cdb[2]	= 01
sw_cdb[3] 	= FF
sw_cdb[4]	= FF
sw_cdb[5] 	= FF
sw_cdb[6]	= FF
sw_cdb[7] 	= FF
sw_cdb[8]	= FF
sw_cdb[9] 	= FF
sw_cdb[10]	= 00
sw_cdb[11] 	= 00
sw_cdb[12]	= 00
sw_cdb[13] 	= 03	//NBLK
sw_cdb[14]	= 02
sw_cdb[15] 	= 00

if (NLBK > 0) {

	var out bytes.Buffer
	var stderr bytes.Buffer

	s_ret := exec.Command(??,"-i","inputF1.bin","--v","-l","400","/dev/sg2","-g","1","-x","512")

            // 问题1:如何将 sw_cdb 命令发送到驱动器?没有等效的可执行文件来填充 '??'
            // 问题2:要么我模仿 sg_compare_write 可执行文件,但我不知道如何创建一个可执行文件
            // 问题3:还有什么其他可能的方法可以将这个 sw_cdb 发送到驱动器?

        	s_ret.Stdout = &out
	s_ret.Stderr = &stderr

	c_error := s_ret.Run()

}

谢谢。

在Go中向驱动器发送SCSI命令,可以使用os.OpenFile打开设备文件,然后通过syscall.Syscall调用ioctl系统调用。以下是具体实现:

package main

import (
    "fmt"
    "os"
    "syscall"
    "unsafe"
)

const (
    SG_IO = 0x2285  // Linux SCSI通用IOCTL
    DXFER_NONE = -1 // 无数据传输
    DXFER_FROM_DEVICE = -2 // 从设备读取数据
    DXFER_TO_DEVICE = -3   // 向设备写入数据
)

type sgIOHdr struct {
    interfaceID    int32
    dxferDirection int32
    cmdLen         uint8
    mxSbLen        uint8
    iovecCount     uint16
    dxferLen       uint32
    dxferp         uintptr
    cmdp           uintptr
    sbp            uintptr
    timeout        uint32
    flags          uint32
    packID         int32
    usrPtr         uintptr
    status         uint8
    maskedStatus   uint8
    msgStatus      uint8
    sbLenWr        uint8
    hostStatus     uint16
    driverStatus   uint16
    resid          int32
    duration       uint32
    info           uint32
}

func sendSCSICommand(device string, cdb []byte, data []byte, direction int32) error {
    // 打开设备文件
    f, err := os.OpenFile(device, os.O_RDWR, 0666)
    if err != nil {
        return fmt.Errorf("打开设备失败: %v", err)
    }
    defer f.Close()

    // 准备sgIOHdr结构
    var sgHdr sgIOHdr
    sgHdr.interfaceID = 'S'
    sgHdr.dxferDirection = direction
    sgHdr.cmdLen = uint8(len(cdb))
    sgHdr.mxSbLen = 32
    sgHdr.timeout = 10000 // 10秒超时

    // 设置CDB指针
    sgHdr.cmdp = uintptr(unsafe.Pointer(&cdb[0]))

    // 设置数据缓冲区
    if len(data) > 0 {
        sgHdr.dxferLen = uint32(len(data))
        sgHdr.dxferp = uintptr(unsafe.Pointer(&data[0]))
    }

    // 准备sense缓冲区
    senseBuf := make([]byte, 32)
    sgHdr.sbp = uintptr(unsafe.Pointer(&senseBuf[0]))
    sgHdr.mxSbLen = uint8(len(senseBuf))

    // 调用ioctl
    _, _, errno := syscall.Syscall(
        syscall.SYS_IOCTL,
        f.Fd(),
        SG_IO,
        uintptr(unsafe.Pointer(&sgHdr)),
    )

    if errno != 0 {
        return fmt.Errorf("IOCTL调用失败: %v", errno)
    }

    // 检查SCSI状态
    if sgHdr.status != 0 {
        return fmt.Errorf("SCSI命令失败, 状态: 0x%02x, sense: %v", 
            sgHdr.status, senseBuf[:sgHdr.sbLenWr])
    }

    return nil
}

func main() {
    // 示例:发送INQUIRY命令
    device := "/dev/sg0"
    
    // INQUIRY CDB (16字节)
    cdb := []byte{
        0x12, // INQUIRY操作码
        0x00, // EVPD=0, 标准INQUIRY
        0x00, // 页码
        0x00, // 分配长度高字节
        0x24, // 分配长度低字节(36字节)
        0x00, // 控制
    }
    
    // 数据缓冲区
    data := make([]byte, 36)
    
    // 发送命令
    err := sendSCSICommand(device, cdb, data, DXFER_FROM_DEVICE)
    if err != nil {
        fmt.Printf("错误: %v\n", err)
        return
    }
    
    fmt.Printf("INQUIRY响应: %v\n", data[:8])
}

对于专有SCSI命令,只需修改CDB内容即可:

// 专有SCSI命令示例
func sendVendorCommand(device string) error {
    // 构建专有CDB
    cdb := []byte{
        0xC0, // 厂商特定操作码
        0x01, // 子命令
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, // 控制字节
    }
    
    // 响应数据
    response := make([]byte, 64)
    
    return sendSCSICommand(device, cdb, response, DXFER_FROM_DEVICE)
}

编译运行需要权限:

sudo go run scsi_command.go

这种方法直接通过Linux的SG_IO接口发送SCSI命令,不依赖外部工具,适用于专有SCSI命令的实现。

回到顶部