如何从 golang 正确地进行 IOCTL

Posted

技术标签:

【中文标题】如何从 golang 正确地进行 IOCTL【英文标题】:How to IOCTL properly from golang 【发布时间】:2019-01-27 12:24:47 【问题描述】:

我正在尝试将 raspberrypi's userspace 代码的一部分从 C 移植到 golang,但我遇到了一个涉及 ioctl() 的程序。

我在使用以下 C 代码时遇到了麻烦

#define MAJOR_NUM 100
#define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM, 0, char *)
static int mbox_property(int file_desc, void *buf)

   int ret_val = ioctl(file_desc, IOCTL_MBOX_PROPERTY, buf);
   return ret_val;

我的 go 等价物是

func mBoxProperty(f *os.File, buf [256]int64) 
        err := Ioctl(f.Fd(), IOWR(100, 0, 8), uintptr(unsafe.Pointer(&buf[0])))

        if err != nil 
                log.Fatalln("mBoxProperty() : ", err)
        



func Ioctl(fd, op, arg uintptr) error 
        _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, fd, op, arg)
        if ep != 0 
                return syscall.Errno(ep)
        
        return nil


func IOWR(t, nr, size uintptr) uintptr 
        return IOC(IocRead|IocWrite, t, nr, size)

func IOC(dir, t, nr, size uintptr) uintptr 
        return (dir << IocDirshift) | (t << IocTypeshift) | (nr << IocNrshift) | (size << IocSizeshift)

但每当我运行此程序时,我都会收到 invalid argument 错误,我认为这可能是由于我如何调用 IOCTL() 但我不确定,我该如何解决这个问题?

【问题讨论】:

这是一个 IOCTL go 示例:github.com/stapelberg/hmgo/blob/master/internal/gpio/reset.go 【参考方案1】:

"golang.org/x/sys/unix" 中有 ioctl(2) 个包装器。 unix.IoctlSetInt 机械地可能满足您的需求。

看起来您正在将一个小内存缓冲区的控制权交给内核。你需要小心这样做:Go 垃圾收集器释放它认为未使用的内存对象,即使某些东西正在使用,它也可以移动它。内核不会知道这一点,并将继续使用旧指针。 unsafe.Pointer 文档在这个主题上有很多话要说,即使是关于不那么奇特的系统调用。我不知道有什么东西可以“锁定”内存中的 Go 对象,以防止它被移动或释放(例如,runtime 包中没有任何东西跳出)。

您可以考虑使用cgo 编写一个非常小的扩展,malloc() 编辑了一个适当的缓冲区并将其交给 ioctl。 malloc 的内存不会被垃圾收集,因此它不会移动或从您下方释放;一些低级工具可能会认为这看起来像是内存泄漏(保留指针的旧值以便以后能够释放它并避开它并不是一个坏主意)。

【讨论】:

unsafe.Pointer 正是您所描述问题的解决方案?!【参考方案2】:

您可能还与uintpr(unsafe.Pointer(...)) 需要发生的细节发生冲突在调用syscall.Syscall

这里是详细信息,来自https://golang.org/pkg/unsafe/#Pointer

(4) 在调用 syscall.Syscall 时将指针转换为 uintptr。

syscall 包中的 Syscall 函数将它们的 uintptr 参数直接传递给操作系统,然后操作系统可能会根据调用的详细信息将其中的一些重新解释为指针。也就是说,系统调用实现隐式地将某些参数从 uintptr 转换回指针。

如果必须将指针参数转换为 uintptr 以用作参数,则该转换必须出现在调用表达式本身中:

syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))

编译器通过安排引用的已分配对象(如果有)在调用完成之前保留而不移动,即使从单独的类型似乎在调用期间不再需要该对象。

为了让编译器识别这种模式,转换必须出现在参数列表中:

// INVALID: uintptr cannot be stored in variable
// before implicit conversion back to Pointer during system call.
u := uintptr(unsafe.Pointer(p))
syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))

unsafe 的这种用法是让您“锁定” Dave Maze 在上面寻找的 Go 对象的技巧。

【讨论】:

以上是关于如何从 golang 正确地进行 IOCTL的主要内容,如果未能解决你的问题,请参考以下文章

golang编译so动态库加载失败

如何让golang mysql驱动程序在2秒内超时ping?

如何在 Golang 中更快地进行 api 调用?

golang 复制对象的正确做法

如何在 Golang 中正确处理缓冲通道?

如何正确地从有效载荷中添加和删除元素? (或有条件更换)