Lichen Liu

与其感慨路难行,不如马上出发

  • 主页
所有文章 友链 关于我

Lichen Liu

与其感慨路难行,不如马上出发

  • 主页

libvirt-go 的一个坑

2020-01-07

更新:
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"

libvirt "github.com/libvirt/libvirt-go"
)

func main() {
conn, err := libvirt.NewConnect("qemu:///system")
if err != nil {
return
}
defer conn.Close()

doms, err := conn.ListAllDomains(libvirt.CONNECT_LIST_DOMAINS_ACTIVE)
if err != nil {
return
}
fmt.Println(conn.GetFreePages([]uint64{4, 2048, 1048576}, 0, 1, uint32(0)))
}

用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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (c *Connect) GetFreePages(pageSizes []uint64, startCell int, maxCells uint, flags uint32) ([]uint64, error) {
if C.LIBVIR_VERSION_NUMBER < 1002006 {
return []uint64{}, makeNotImplementedError("virNodeGetFreePages")
}
cpageSizes := make([]C.uint, len(pageSizes)) //这俩slice
ccounts := make([]C.ulonglong, len(pageSizes)*int(maxCells)) //这俩slice

for i := 0; i < len(pageSizes); i++ {
cpageSizes[i] = C.uint(pageSizes[i])
}

var err C.virError
ret := C.virNodeGetFreePagesWrapper(c.ptr, C.uint(len(pageSizes)), (*C.uint)(unsafe.Pointer(&cpageSizes)), C.int(startCell),
C.uint(maxCells), (*C.ulonglong)(unsafe.Pointer(&ccounts)), C.uint(flags), &err) // 就这做的unsafe
if ret == -1 {
return []uint64{}, makeError(&err)
}
...
}

这玩意的函数签名如下:

1
2
3
4
5
6
7
8
9
int
virNodeGetFreePagesWrapper(virConnectPtr conn,
unsigned int npages,
unsigned int *pages,
int startCell,
unsigned int cellCount,
unsigned long long *counts,
unsigned int flags,
virErrorPtr err);

这里有一个问题,go 的slice 实现大概是这样的(取自CSDN深度解密Go语言之unsafe):

1
2
3
4
5
6
// runtime/slice.go	
type slice struct {
array unsafe.Pointer // 元素指针
len int // 长度
cap int // 容量
}

一个slice 里有三个成员,其中第一个成员array 是个指针,它指向的是一个数组,也就是说直接把slice 转成uint * 是有问题的,这样转换完得到的其实是slice 指针,而我们需要的unsigned int *pages其实是最终数组的首地址,也就是array 的值,所以需要把它的第一个成员也就是array 的值拿出来才对,不然的话我们拿到的是slice 的地址。
之后用gdb 跟了一下,果然page size 679968 中的数字是array 的低32 位,因为传参错误把uint * 理解成uint用了,实际上需要把它和紧挨着的高32 位拼在一起再解引用才是libvirtd 需要的值。
从CSDN Golang切片和数组底层详解上,我们找到一个获取数组首地址的方法:

1
2
s := make([]byte, 200)
ptr := unsafe.Pointer(&s[0])

所以修改libvirt-go 的代码connect.go 问题解决:

1
2
ret := C.virNodeGetFreePagesWrapper(c.ptr, C.uint(len(pageSizes)), (*C.uint)(unsafe.Pointer(&cpageSizes[0])), C.int(startCell),
C.uint(maxCells), (*C.ulonglong)(unsafe.Pointer(&ccounts[0])), C.uint(flags), &err)

因为刚开始学golang,感觉这个改法可能不是很科学,感觉哪里有点毛病,回头还得再测测。

  • golang
  • libvirt
  • libvirt-go

扫一扫,分享到微信

微信分享二维码
golang xml 如何忽略空结构体
在AWS 的Amazon Linux 上下载源码
© 2023 Lichen Liu
Hexo Theme Yilia by Litten
  • 所有文章
  • 友链
  • 关于我

tag:

  • Linux
  • golang
  • xml
  • omitempty
  • test
  • art
  • AWS
  • Kernel
  • bonding
  • sk_buff
  • kernel
  • libvirt
  • libvirt-go
  • numa
  • ping
  • arp
  • 输入法
  • ovs
  • font
  • manjaro
  • 坑
  • 北京
  • 旅游

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • 肥叉烧
  • 安全大手子
  • 物理学博士
  • 一个愣头青
普通上班族,会一点Linux, 想学SDN