Golang 往共享内存写入结构体

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.总结

string struct

//  这里需要充分了解 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 并非指针长度的整数倍。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值