unsafe.Pointer 和系统调用

首发于:https://studygolang.com/articles/12400

按照 语言官方文档所说, 是关注 程序操作类型安全的包。

像包名暗示的一样,使用它要格外小心; 可以特别危险,但它也可以特别有效。例如,当处理系统调用时, 的结构体必须和 的结构体拥有相同的内存结构,这时你可能除了使用 以外,别无选择。

可以让你无视 的类型系统,完成任何类型与内建的 类型之间的转化。根据文档, 可以实现四种其他类型不能的操作:

任何类型的指针都可以转化为一个

一个 可以转化成任何类型的指针

一个 可以转化成一个

一个 可以转化成一个

这里主要关注两种只能借助 包才能完成的操作:使用 实现两种类型间转换和使用 处理系统调用。

使用 `unsafe.Pointer` 做类型转换操作方式

可以简洁适宜的转换两个在内存中结构一样的类型是使用 的一个主要原因。

文档描述:

如果与一样大,并且两者有相同的内存结构;那么就允许把一个类型的数据,重新定义成另一个类型的数据

经典的例子,是文档中的一次使用,用来实现

这似乎是一种非常简洁的完成这样转换的方法,但是这个过程中具体发生了什么?让我们一步步拆分一下:

拿到一个指向 存放 值的指针。

将 类型转化成了 类型。

将 类型转化成了 。

引用这个 类型指针,转化为一个 类型的值。

第一个例子是下面过程的一个简洁表达:

这是一个非常有用的操作,有些时候也是一个必要操作。 现在你已经理解了 是如何使用的,那么让我们再看一个真实的项目例子

现实列子:`taskstats`

我最近正在研究 的 接口,我想了一个办法在 中取到了内核的 的 结构。然后发送一个 把这个结构加到 中,我意识到这个结构实际上是如此的庞大和复杂。

为了使用这个结构,我需要从一个 类型的切片中精确的分析每一个字段。更复杂的是,每一个 类型在本地有序的存储,所以这些整数可能根据你的在内存中以不同的格式存储。

这个情况就非常适合使用简洁的 转换,下面是我的写法:

//通过这个包证实包含一个 结构的类型的切片是预计的大小,我们不能盲目的将这个类型的切片放入一个错误尺寸的结构中。

它是怎么做的?

首先,我通过参数传来的结构体实例,使用 ,确定了该结构在内存中占有的准确的大小。

接下来,我确认需要转换的类型的切片大小和 结构大小一样,这样我就可以只读取我想要的数据块,而不是随意读取内存。

最后,我使用 向 结构转换。

但是,我为什么必须指定切片索引的0位置呢?

如果你了解切片的内部结构,你将知道一个切片实际上是一个头和一个指向底层数组的指针。当使用 unsafe.Pointer 来转换切片数据时,必须指定数组第一个元素的内存地址,而不是切片本身的首地址。

使用 使得转换非常的简洁、简单。因为整型数据根据我们的CPU以相同的字节顺序存储,使用 转化意味着整型值是我们预期的。

你可以去看看我 包中的代码。

使用 `unsafe.Pointer` 处理系统调用操作方式

当处理系统调用时,有些时候需要传入一个指向某块内存的指针给内核,以允许它执行某些任务。这是 在中另一个重要的使用场景。当需要处理系统调用时,就必须使用 ,因为为了使用 家族函数,它可以被转化成 类型。

对于许多不同的操作系统,都拥有大量的系统调用。但是在这个例子中,我们将重点关注 。,在类系统中,经常被用来操作那些无法直接映射到典型的文件系统操作,例如读和写的文件描述符。事实上,由于 系统调用十分灵活,它并不在的 或者 包中。

让我看看另一个真实的例子。

现实例子:`ioctl/vsock`

在过去的几年里,增加了一个新的 家族,,它可以使管理中心和它的虚拟机之间双向,多对一的通信。

这些套接字使用一个上下文进行通信。通过发送一个带有特殊请求号的 到 驱动,可以取到这个上下文。

下面是 函数的定义:

像代码注释所写一样,在这种场景下使用 有一个很重要的说明:

在 包中的系统调用函数通过它们的 类型参数直接操作系统,然后根据调用的详细情况,将它们中的一些转化为指针。换句话说,系统调用的执行,是其中某些参数从 类型到指针类型的隐式转换。

如果一个指针参数必须转换成 才能使用,那么这种转换必须出现在表达式内部。

但是为什么会这样?这是编译器识别的特殊模式,本质上是指示垃圾收集器在函数调用完成之前,不能将被指针引用的内存再次安排。

你可以通过阅读文档来获得更多的技术细节,但是你在中处理系统调用时必须记住这个规则。事实上,在写这篇文章时,我意识到我的代码违反了这一规则,现在已经被修复了。

意识到这一点,我们可以看到这个函数是如何使用的。

在 套接字的例子里,我们想传递一个 到内核,以便它可以把我们当时的上下文赋值到这块内存地址中。

这只是在系统调用时使用 的一个例子。你可以使用这么模式发送、接收任何数据,或者是用一些特殊方式配置一个内核接口。有很多可能的情况!

你可以去看看我 包中的代码。

结尾

虽然使用 包可能存在风险,但当使用恰当时,它可以是一个非常强大、有用的工具。

既然你在读这篇文章,我建议你在你的程序使用它之前,去读一下 包的官方文档。

如果你有任何问题,请随时联系我!在 上我的名字是 。

非常感谢 对这篇文章的建议和修改!

链接

unsafe 包

字节顺序

taskstats 包

Go中Slice的使用和内部实现

ioctl

vsock 包

via: https://blog.gopheracademy.com/advent-2017/unsafe-pointer-and-system-calls/

作者:Matt Layher

译者:yiyulantian

校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出

喜欢本文的朋友们,欢迎长按下图关注订阅哦!

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180730G0W1FY00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券