1.简单使用共享内存
如果还不清楚什么是共享内存,可以先看上一篇:
共享内存使用
2.共享内存写入结构体封装
2.1实现思路
这里采用洋葱模型,将结构体一层一层的写到共享内存,
遇到指针(或引用),只保留指针(或引用)所占内存地址
写完这一层数据后,在写入指针指向的具体数据,并修改之前留下的内存插槽
2.2具体代码
import (
"reflect"
"unsafe"
)
var v = &struct{}{}
var sysPointerSize = reflect.TypeOf(v).Size() // 系统指针长度,32位系统为4,64位系统为8
type pointerTask struct {
slot unsafe.Pointer // 插槽位置地址,后续在这个插槽写入数据体的内存地址
value reflect.Value
copy func(manager *ShareMemoryManager, task *pointerTask)
}
// ShareMemoryManager
type ShareMemoryManager struct {
startAddress unsafe.Pointer // 内存地址起始位置
currentAddress unsafe.Pointer // 当前内存地址位置
queue []pointerTask
}
func NewShareMemoryManager(pointer unsafe.Pointer) *ShareMemoryManager {
// fmt.Printf("pointer size: %d \n", sysPointerSize)
o := &ShareMemoryManager{
startAddress: pointer,
currentAddress: pointer,
queue: make([]pointerTask, 0, 1024),
}
return o
}
func (this *ShareMemoryManager) GetStartPointer() unsafe.Pointer {
return this.startAddress
}
func (this *ShareMemoryManager) startPointer() uintptr {
return uintptr(this.startAddress)
}
func (this *ShareMemoryManager) GetCurrentPointer() unsafe.Pointer {
return this.currentAddress
}
// Size 总大小
func (this *ShareMemoryManager) Size() uintptr {
return uintptr(this.currentAddress) - uintptr(this.startAddress)
}
// WriteObject 写入数据进入指定内存中
func (this *ShareMemoryManager) WriteObject(data any) {
this.writeObject(reflect.ValueOf(data))
for {
task := this.pop()
if task == nil {
break
}
task.copy(this, task)
}
}
// 追加内存写入任务队列
func (this *ShareMemoryManager) append(wait pointerTask) {
this.queue = append(this.queue, wait)
}
func (this *ShareMemoryManager) pop() *pointerTask {
if len(this.queue) == 0 {
return nil
}
task := &this.queue[0]
this.queue = this.queue[1:len(this.queue)]
return task
}
func (this *ShareMemoryManager) writeObject(data reflect.Value) {
size := data.Type().Size()
switch data.Type().Kind() {
case reflect.Pointer:
if data.IsNil() {
*(*uintptr)(this.currentAddress) = uintptr(0)
this.currentAddress = unsafe.Add(this.currentAddress, size)
return
}
this.append(pointerTask{
slot: this.currentAddress,
value: data.Elem(),
copy: copyObjectValue,
})
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Slice: // 切片
if data.IsNil() {
*(*uintptr)(this.currentAddress) = uintptr(0)
*(*int)(unsafe.Add(this.currentAddress, 8)) = 0
*(*int)(unsafe.Add(this.currentAddress, 16)) = 0
this.currentAddress = unsafe.Add(this.currentAddress, size)
return
}
lenInt := data.Len()
capInt := data.Len()
this.append(pointerTask{
slot: this.currentAddress,
value: data,
copy: copySliceValue,
})
// 数组结构 {pointer: uintptr, len: int, cap: int }
*(*int)(unsafe.Add(this.currentAddress, 8)) = lenInt
*(*int)(unsafe.Add(this.currentAddress, 16)) = capInt // 这里将 cap设置和 len一样即可
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Array: // 数组
start := uintptr(this.currentAddress)
for i := 0; i < data.Len(); i++ {
this.writeObject(data.Index(i))
}
realy := uintptr(this.currentAddress) - start // 数据真实长度
if realy < size { // 如果小于对齐长度 则补上对齐长度
this.currentAddress = unsafe.Add(this.currentAddress, size-realy)
}
case reflect.String: // 字符串
lenInt := data.Len()
if lenInt == 0 {
*(*uintptr)(this.currentAddress) = uintptr(0) //
*(*int)(unsafe.Add(this.currentAddress, 8)) = lenInt
this.currentAddress = unsafe.Add(this.currentAddress, size)
return
}
this.append(pointerTask{
slot: this.currentAddress,
value: data,
copy: copyStringValue,
})
// 数组结构 {pointer: uintptr, len: int}
*(*int)(unsafe.Add(this.currentAddress, 8)) = lenInt
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Struct:
dataType := data.Type()
var p unsafe.Pointer
for i := 0; i < dataType.NumField(); i++ {
//name := dataType.Name()
field := dataType.Field(i)
fieldSize := field.Type.Size() // 这个字段所占内存空间大小
alignmentOffset := uintptr(0)
if i == dataType.NumField()-1 { // 如果最后一个字段,使用struct的size来计算内存对齐偏移
alignmentOffset = dataType.Size() - field.Offset - fieldSize
} else { // 用下一个字段的的偏移-本字段偏移-本字段真实大小, 算得内存对齐偏移
fieldSize = dataType.Field(i+1).Offset - field.Offset - fieldSize
}
if field.IsExported() { // 公有变量
this.writeObject(data.Field(i))
} else { // 私有变量,通过指针偏移读取到私有变量的值,但是最好拷贝的结构体全是公有变量
if p == nil {
v := data.Interface()
p = unsafe.Pointer(&v)
p = unsafe.Add(p, 8)
p = *(*unsafe.Pointer)(p) // 取出对象指针
}
fv := reflect.NewAt(field.Type, unsafe.Add(p, field.Offset))
this.writeObject(fv.Elem())
}
if alignmentOffset > 0 { // 补充 内存对齐偏移
this.currentAddress = unsafe.Add(this.currentAddress, alignmentOffset)
}
}
case reflect.Map:
panic("MAP_NOT_SUPPORTED")
case reflect.Int:
value := data.Interface().(int)
*(*int)(this.currentAddress) = value
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Uintptr:
value := data.Interface().(uintptr)
*(*uintptr)(this.currentAddress) = value
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Int64:
value := data.Interface().(int64)
*(*int64)(this.currentAddress) = value
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Int32:
value := data.Interface().(int32)
*(*int32)(this.currentAddress) = value
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Int8:
value := data.Interface().(int8)
*(*int8)(this.currentAddress) = value
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Uint8:
value := data.Interface().(uint8)
*(*uint8)(this.currentAddress) = value
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Uint32:
value := data.Interface().(uint32)
*(*uint32)(this.currentAddress) = value
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Uint64:
value := data.Interface().(uint64)
*(*uint64)(this.currentAddress) = value
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Float32:
value := data.Interface().(float32)
*(*float32)(this.currentAddress) = value
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Float64:
value := data.Interface().(float64)
*(*float64)(this.currentAddress) = value
this.currentAddress = unsafe.Add(this.currentAddress, size)
case reflect.Bool:
value := data.Interface().(bool)
*(*bool)(this.currentAddress) = value
this.currentAddress = unsafe.Add(this.currentAddress, size)
default:
panic("UNKNOWN_TYPE:" + data.Type().String())
}
}
func copyStringValue(manager *ShareMemoryManager, task *pointerTask) {
*(*unsafe.Pointer)(task.slot) = manager.currentAddress // 先将之前预留的插槽位置,写入当前写数据的内存地址
// 循环将数据一个字节一个字节的拷贝到内存中
for i := 0; i < task.value.Len(); i++ {
b := task.value.Index(i).Interface().(byte)
*(*byte)(manager.currentAddress) = b
manager.currentAddress = unsafe.Add(manager.currentAddress, 1)
}
// 内存对齐
alignmentOffset := task.value.Len() % int(sysPointerSize)
if alignmentOffset > 0 {
manager.currentAddress = unsafe.Add(manager.currentAddress, int(sysPointerSize)-alignmentOffset)
}
}
// 拷贝切片
func copySliceValue(manager *ShareMemoryManager, task *pointerTask) {
*(*unsafe.Pointer)(task.slot) = manager.currentAddress // 先将之前预留的插槽位置,写入当前写数据的内存地址
start := uintptr(manager.currentAddress)
// 循环将数据一个字节一个字节的拷贝到内存中
for i := 0; i < task.value.Len(); i++ {
manager.writeObject(task.value.Index(i))
}
size := uintptr(manager.currentAddress) - start
// 内存对齐
alignmentOffset := size % sysPointerSize
if alignmentOffset > 0 {
manager.currentAddress = unsafe.Add(manager.currentAddress, sysPointerSize-alignmentOffset)
}
}
// 指针值拷贝
func copyObjectValue(manager *ShareMemoryManager, task *pointerTask) {
*(*unsafe.Pointer)(task.slot) = manager.currentAddress // 先将之前预留的插槽位置,写入当前写数据的内存地址
manager.writeObject(task.value)
}
3.封装挂载共享内存的函数
import (
"unsafe"
)
// bindShareMemory 挂载共享内存(写入时 address传入0,由系统分配,加载时需要指定上一次系统分配的内存地址)
func bindShareMemory(address uintptr) unsafe.Pointer {
// 声明需要创建共享内存的大小
const memory_size = 512 // 这里 512b
var shareKey uintptr = 8 // 自己定义共享内存的key
var readOnly = false // 是否只读方式挂载
// 通过KEY, 获取一个共享内存,如果不存在会创建
shmId, _, err := syscall.Syscall(syscall.SYS_SHMGET, shareKey, memory_size, 00001000|0600) // shmId 为共享内存的ID(操作系统构建返回
if err != 0 {
panic(err)
}
flags := uintptr(0x0)
if readOnly {
flags = uintptr(0x00001000) // 只读挂载
}
// 开始挂载共享内存, shmAddress为共享内存地址
shmAddress, _, err2 := syscall.Syscall(syscall.SYS_SHMAT, shmId, address, flags)
if err2 != 0 {
panic(err2)
}
return unsafe.Pointer(shmAddress)
}
4.写一个程序对共享内存写入结构体
// 随意定义2个结构体,包含有指针 切片等数据结构
type FiledObject struct {
Detail string
BoolValue bool
}
type DataObject struct {
Name string
IntValue int
List []FiledObject
PointerValue *FiledObject
Obj struct {
SV int64
Url string
}
}
func main() {
pointer := bindShareMemory(0) // 指定共享内存地址0由系统自行分配
value := DataObject{
Name: "test1",
IntValue: 123,
List: []FiledObject{{Detail: "detail1", BoolValue: true}, {Detail: "detail2", BoolValue: false}},
PointerValue: &FiledObject{
Detail: "pointer value",
BoolValue: true,
},
}
value.Obj.SV = 8989
value.Obj.Url = "url1"
mananger := NewShareMemoryManager(pointer)
mananger.WriteObject(value)
fmt.Printf("结构体写入共享成功:[%p]\n", pointer)
// 结构体写入共享成功:[0x7fc2bc0e2000]
}
PS: 执行上面可以拿到系统分配的内存地址(0x7fc2bc0e2000)
5.额外写个示例程序来挂载之前分配的共享内存并输出数据
type FiledObject struct {
Detail string
BoolValue bool
}
type DataObject struct {
Name string
IntValue int
List []FiledObject
PointerValue *FiledObject
Obj struct {
SV int64
Url string
}
}
func main() {
pointer := bindShareMemory(0x7fc2bc0e2000) // 0x7fc2bc0e2000 是之前写入时返回的内存地址,需替换自己刷入数据时输出的地址值
fmt.Printf("加载共享内存: %p \n", pointer)
data := (*DataObject)(pointer)
fmt.Printf("Name: %v , IntValue: %v, OBJ.SV: %v, OBJ.Url:%v \n", data.Name, data.IntValue, data.Obj.SV, data.Obj.Url)
for i, v := range data.List {
fmt.Printf("[%d] Detail: %v , Bool: %v \n", i, v.Detail, v.BoolValue)
}
if data.PointerValue != nil {
fmt.Printf("[pointer] Detail: %v , Bool: %v \n", data.PointerValue.Detail, data.PointerValue.BoolValue)
}
}
6.总结
// 这里需要充分了解 golang 的 string, array,slice 在内存里面的存储结构。
// 例如: string 可以参考源码:runtime/string.go
// 本质是一个结构体
type stringStruct struct {
str unsafe.Pointer
len int
}
// slice 和 string 几乎一样的结构,只是多了一个 cap
type slice struct {
array unsafe.Pointer
len int
cap int
}
// 所以在拷贝这些数据的时候,有点类似 Pointer的拷贝,先预留指针插槽位,在后面写入len数据即可。
PS:
1.这里没有实现对 map 数据结构的拷贝,因为golang 每次运行hash因子都不一致,
这将导致,拷贝的map 第二次挂载时数据无法正常读取,
2.拷贝过程中还需要考虑内存对齐的情况,否则在挂载读取的时候将会panic.
特别是在写入 string过后,通常string的len 并非指针长度的整数倍。