更新:
BUG 已经被修复。
今天和聂工在研究怎么用libvirt-go 时,发现GetFreepages 有bug,demo 一跑就报错virError(Code=9, Domain=0, Message='operation failed: page size 679968 is not available on node 0')
.
其中这个page size 679968
每次跑还会变化。
1 | package main |
用qemu+tcp 的方式去测试,抓包发现发送给libvirtd 的指令里这个参数就已经是错的,也就是问题出现在客户端这边。同时聂工也测试过python,python 的libvirt 库没这个毛病。
之后开始debug,因为CentOS 8 没有libvirt-devel 的debuginfo,去CentOS 7 重新装了下环境,还是能复现问题,用gdb 看到这部分进到cgo 里了,源码一顿乱翻,在GetFreePages 的代码里可以看到这就是个对C 的封装,里面用了go 的unsafe 想把go 的slice 转换成c 的数组。
但代码里有一个bug,这玩意是用virNodeGetFreePagesWrapper 调用C 库的,在传递之前,先弄了cpageSizes 和ccounts 俩slice,然后用unsafe 直接把slice 的地址转成int 型指针了了:
1 | func (c *Connect) GetFreePages(pageSizes []uint64, startCell int, maxCells uint, flags uint32) ([]uint64, error) { |
这玩意的函数签名如下:
1 | int |
这里有一个问题,go 的slice 实现大概是这样的(取自CSDN深度解密Go语言之unsafe):
1 | // runtime/slice.go |
一个slice 里有三个成员,其中第一个成员array 是个指针,它指向的是一个数组,也就是说直接把slice 转成uint * 是有问题的,这样转换完得到的其实是slice 指针,而我们需要的unsigned int *pages
其实是最终数组的首地址,也就是array 的值,所以需要把它的第一个成员也就是array 的值拿出来才对,不然的话我们拿到的是slice 的地址。
之后用gdb 跟了一下,果然page size 679968
中的数字是array 的低32 位,因为传参错误把uint * 理解成uint用了,实际上需要把它和紧挨着的高32 位拼在一起再解引用才是libvirtd 需要的值。
从CSDN Golang切片和数组底层详解上,我们找到一个获取数组首地址的方法:
1 | s := make([]byte, 200) |
所以修改libvirt-go 的代码connect.go 问题解决:
1 | ret := C.virNodeGetFreePagesWrapper(c.ptr, C.uint(len(pageSizes)), (*C.uint)(unsafe.Pointer(&cpageSizes[0])), C.int(startCell), |
因为刚开始学golang,感觉这个改法可能不是很科学,感觉哪里有点毛病,回头还得再测测。