V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
Aumujun
V2EX  ›  Go 编程语言

[问题] go 调用 Linux 系统接口无法关闭 fd

  •  
  •   Aumujun ·
    None · 2021-08-05 11:48:17 +08:00 · 2345 次点击
    这是一个创建于 1206 天前的主题,其中的信息可能已经有所发展或是发生改变。

    项目中需要获取网卡的一些硬件信息,但遇到一点问题,现在用另一种方式解决了;但还是想搞懂为什么会报这个错误,基础太差,希望得到大佬的指导,非常感谢。

    package main
    
    import (
    	"fmt"
    	"golang.org/x/sys/unix"
    	"syscall"
    	"unsafe"
    )
    
    func main() {
    	for {
    		go func() {
    			fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
    			defer func() {
    				if err := unix.Close(fd); err != nil {
    					fmt.Println("fd 关闭失败:", err)
    				}
    			}()
    			if err != nil {
    				fmt.Println(err)
    				return
    			}
    			var et struct {
    				Cmd       uint32
    				Supported uint32
    			}
    
    			const GSET = 0x1
    
    			et.Cmd = GSET
    
    			var ifr struct {
    				Name [16]byte
    				Data uintptr
    			}
    
    			copy(ifr.Name[:], []byte("网卡名称"))
    			ifr.Data = uintptr(unsafe.Pointer(&et))
    
    			const SIOCETHTOOL = 0x8946
                
    			// 执行这一步后,fd 就无法关闭,close 返回 bad file descriptor
    			_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(SIOCETHTOOL), uintptr(unsafe.Pointer(&ifr)))
    			if errno != 0 {
    				fmt.Println(errno)
    			}
    		}()
    	}
    	select {}
    }
    
    第 1 条附言  ·  2021-08-05 14:19:26 +08:00

    已解决,结帖;ethtool_cmd结构体不全导致,从c源码抄一份完整的结构就解决了

    package main
    
    import (
    	"fmt"
    	"golang.org/x/sys/unix"
    	"syscall"
    	"time"
    	"unsafe"
    )
    
    type EthtoolCmd struct {
    	Cmd            uint32
    	Supported      uint32
    	Advertising    uint32
    	Speed          uint16
    	Duplex         uint8
    	Port           uint8
    	Phy_address    uint8
    	Transceiver    uint8
    	Autoneg        uint8
    	Mdio_support   uint8
    	Maxtxpkt       uint32
    	Maxrxpkt       uint32
    	Speed_hi       uint16
    	Eth_tp_mdix    uint8
    	Reserved2      uint8
    	Lp_advertising uint32
    	Reserved       [2]uint32
    }
    
    type EthtoolValue struct {
    	Cmd  uint32
    	Data uint32
    }
    
    type EthtoolDrvInfo struct {
    	cmd          uint32
    	driver       [32]byte
    	version      [32]byte
    	fw_version   [32]byte
    	bus_info     [32]byte
    	erom_version [32]byte
    	reserved2    [12]byte
    	n_priv_flags uint32
    	n_stats      uint32
    	testinfo_len uint32
    	eedump_len   uint32
    	regdump_len  uint32
    }
    
    const IFNAMSIZ = 16
    
    type Ifreq struct {
    	ifr_name [IFNAMSIZ]byte
    	ifr_data uintptr
    }
    
    func main() {
    	for {
    		func() {
    			fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
    			defer func() {
    				if err := unix.Close(fd); err != nil {
    					fmt.Println("fd关闭失败:", err)
    				}
    			}()
    			if err != nil {
    				fmt.Println(err)
    				return
    			}
    
    			var ifr Ifreq
    			//var drvinfo EthtoolDrvInfo
    			var ecmd EthtoolCmd
    			//var edata EthtoolValue
    
    			copy(ifr.ifr_name[:], []byte("eno2"))
    			ecmd.Cmd = 0x1
    			ifr.ifr_data = uintptr(unsafe.Pointer(&ecmd))
    			_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(0x8946), uintptr(unsafe.Pointer(&ifr)))
    			if errno != 0 {
    				panic("ioctl 发送失败")
    			}
    			time.Sleep(1 * time.Second)
    		}()
    	}
    	select {}
    
    16 条回复    2021-08-06 15:44:37 +08:00
    40EaE5uJO3Xt1VVa
        1
    40EaE5uJO3Xt1VVa  
       2021-08-05 12:14:08 +08:00
    另一种方法是不是直接执行个 sh 脚本获取返回值。
    Nitroethane
        2
    Nitroethane  
       2021-08-05 12:41:24 +08:00
    close 返回 EBADF 说明你要关闭的文件描述符是一个无效的文件描述符。你的代码有问题,关闭 fd 的 defer 应该放到错误处理的后面,因为如果 unix.Socket() 调用失败的话,fd 的值就是一个无效的文件描述符,你关闭它当然返回 EBADF
    Nitroethane
        3
    Nitroethane  
       2021-08-05 12:42:07 +08:00
    还有,现在都用 netlink
    wellsc
        4
    wellsc  
       2021-08-05 13:15:12 +08:00
    老铁,你把报错信息贴上来啊
    ch2
        5
    ch2  
       2021-08-05 13:25:20 +08:00
    查一下 syscall.SYS_IOCTL 的手册
    Aumujun
        6
    Aumujun  
    OP
       2021-08-05 13:28:15 +08:00
    @Nitroethane 跟 defer 放的位置无关;这个 fd 在被执行 ioctl 后才会关闭失败,不知道什么原因造成的。
    Aumujun
        7
    Aumujun  
    OP
       2021-08-05 13:29:14 +08:00
    Nitroethane
        8
    Nitroethane  
       2021-08-05 14:02:31 +08:00 via iPhone
    @Aumujun 第一,你的 defer 放在错误处理前面就是有问题。第二,我跑了一下你的代码,ioctl 调用报错,但是 close 没有问题
    qieqie
        9
    qieqie  
       2021-08-05 14:10:56 +08:00
    你这个 goroutine 为什么要放在 for{}里面,瞬间 fd 数量都爆了吧
    另外用 SIOCGIFNAME 不行吗,你这个 SIOCETHTOOL 在<bits/ioctls.h>都找不到定义
    Aumujun
        10
    Aumujun  
    OP
       2021-08-05 14:13:37 +08:00
    @qieqie 就是一个示例,顺手就写上去了;和问题无关;
    Srar
        11
    Srar  
       2021-08-05 14:15:04 +08:00
    尝试下 runtime.KeepAlive? 可能是被 gc 收走了
    Aumujun
        12
    Aumujun  
    OP
       2021-08-05 14:16:16 +08:00
    问题找到了,是我的 et 结构体不全,我在 c 源码里抄了一个完整的 ethtool_cmd 结构体就可以了。
    qieqie
        13
    qieqie  
       2021-08-05 14:29:46 +08:00
    正确做法是先 include c 的 header 然后 import "C",初始化 C 的结构体
    其实 2 楼指出的问题也是正确的,我觉得楼主 golang 应该还没入门,有些问题别急着下结论,以后水平长进了再回头看看吧。
    Aumujun
        14
    Aumujun  
    OP
       2021-08-05 15:28:04 +08:00
    @qieqie 二楼说的 defer close 我没 get 到点,fd 无效会关闭抛出 bad fd 这个我知道,但我的问题是 fd 为什么无效;这相当于没说。另外 import C 这种方式我不喜欢用;还不如纯 C 编译 so 直接给 go 用。
    www5070504
        15
    www5070504  
       2021-08-05 16:53:44 +08:00
    二楼的意思是先检查错误 如果打开错误的话 fd 就是无内容的 defer 就出错了 如果先检查错误再 defer 至少保证 defer 起到应有的关闭 fd 的作用
    codehz
        16
    codehz  
       2021-08-06 15:44:37 +08:00
    无效的原因大概是破坏了栈内容吧。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2666 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 44ms · UTC 11:43 · PVG 19:43 · LAX 03:43 · JFK 06:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.