首发于: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语言中文网 荣誉推出
喜欢本文的朋友们,欢迎长按下图关注订阅哦!
领取专属 10元无门槛券
私享最新 技术干货