Go读取共享内存

前言

业务需要从给定的共享内存中读取数据,但写入以及更新方都是C代码,本文以Go为例,读取共享内存具体数据。

阅读完本文将获得以下技能:

  • unsafe包基本使用
  • 理解变量在内存中布局
  • 能够通过 syscall 使用 shmget/at/dt 系统调用
  • 类型转换与内存地址运算

数据准备

使用C代码开辟一块System V共享内存,并写入数据

#include <sys/mman.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

typedef struct
{
    char name[32];
    int age;
} people;

int main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    people* p_map;
    char temp = 'a';
    
    const char* name = "/dev/shm/my_systemv_shm1";
    key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    shm_id=shmget(key, 4096, IPC_CREAT);
    if(shm_id == -1)
    {
        perror("shmget error");
        return -1;
    }
    p_map=(people*)shmat(shm_id,NULL,0);
    
    for(int i = 0; i < 10; i++)
    {
        memcpy( ( *(p_map+i) ).name, &temp, 1);
        ( *(p_map+i) ).name[1] = 0;
        ( *(p_map+i) ).age = 20+i;
        temp += 1;
    }
    printf("initialize over\n");
    
    if(shmdt(p_map) == -1)
    {
        perror(" detach error ");
        return -1;
    }
    
    return 0;
}

共享内存布局

  1. 得到System V共享内存的key
[root@localhost xcxing1]# ipcs -m

------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     nattch     状态      
0x00130d5e 4          root       0          4096       0                       

[root@localhost xcxing1]# 
  1. 了解内存中的数据布局,要具体到字节。
 示例中是往共享内存写入了10个People结构体,共享内存总大小为4096字节,内存布局如下:

        [name1 age1 name2 age2 name3 age3 ...] 
        |                                    |
        首地址                                末地址
  1. 使用GO建立对应结构模型:
type pn [32]byte

func (p pn) String() string {
    return string(p[:])
}
//name 本质还是一个[32]byte数组,为了方便转换,重定义了pn类型
type People struct {
    name pn
    age  int32
}
  1. 通过系统调用读取
shmId, _, err := syscall.Syscall(syscall.SYS_SHMGET, 0x00130d5e, 4096, 0)
    if err < 0 {
        log.Fatal(err)
    }

    shmaddr, _, err := syscall.Syscall(syscall.SYS_SHMAT, shmId, 0, 0)
    if err != 0 {
        fmt.Printf("syscall error, err: %v\n", err)
        os.Exit(-2)
    }
    fmt.Printf("shmaddr: %v\n", shmaddr)

    defer syscall.Syscall(syscall.SYS_SHMDT, shmaddr, 0, 0)

    sizeOfPeople := unsafe.Sizeof(People{})

    for i := 0; i < 10; i++ {
        p := shmaddr + sizeOfPeople*uintptr(i)
        data := (*People)(unsafe.Pointer(p))
        fmt.Println(data.name.String())
        fmt.Println(data.age)
    }

代码讲解

  1. shmget 返回值是什么?
    通过shmget系统调用,返回的是共享内存段的首地址


    image.png
  2. unsafe包的魔力
    官网地址:https://golang.google.cn/pkg/unsafe/
    正如包名所说,使用它必须要格外小心,可以很危险,也可以很高效,特别是处理系统调用的时候,往往返回的只是一个首地址,剩下的则需要通过控制结构体与C结构对应,必须要是相同的内存结构才能正确读取,这种时候unsafe包是唯一选择。
  • 无视go的类型系统,完成任何类型与内建uintptr类型之间的转换
  • 主角:unsafe.Pointer
    Pointer represents a pointer to an arbitrary type. There are four special operations available for type Pointer that are not available for other types:
  • A pointer value of any type can be converted to a Pointer.//任何类型的指针都可以转化为一个 unsafe.Pointer
  • A Pointer can be converted to a pointer value of any type.//一个 unsafe.Pointer 可以转化成任何类型的指针
  • A uintptr can be converted to a Pointer.//一个 uintptr 可以转化成一个 unsafe.Pointer
  • A Pointer can be converted to a uintptr.//一个 unsafe.Pointer 可以转化成一个 uintptr
  1. 类型转换
/*
>> 一个People结构体占内存空间是40bytes
>> syscall(SYS.SHMGET,...) 返回的第一个参数是 uintptr (即内存地址)
>> (People)(unsafe.Pointer(shmAddr)) 意思是一次从shmAddr指向的内存读取大小为40字节的内存,所以类型转换后,可以直接通过 people1.age 方式访问内存中的age
*/
people1 := (*People)(unsafe.Pointer(shmAddr))
fmt.Println(people1.name.String())
fmt.Println(people1.age)

完整go代码

package main
/*
//C 代码定义的People结构体
typedef struct
{
    char name[32];
    int age;
} people;
*/
/*
**内存数据布局**

        [name1 age1 name2 age2 name3 age3 ...]  (示例中是往共享内存写入了10个People,共享内存总大小为4096字节)
        |                                    |
        首地址                                末地址

**主要踩坑点**
    没有将Go的People结构体内存大小与C的People对齐,导致每次通过unsafe包转换成People的时候都失败
    如果不清楚C和GO类型内存大小对应关系,参考:https://www.cntofu.com/book/73/ch2-cgo/ch2-03-cgo-types.md
*/

import (
    "fmt"
    "log"
    "os"
    "syscall"
    "unsafe"
)

type pn [32]byte

func (p pn) String() string {
    return string(p[:])
}

type People struct {
    name pn
    age  int32
}

func main() {
    shmId, _, err := syscall.Syscall(syscall.SYS_SHMGET, 0x00130d5e, 4096, 0)
    if err < 0 {
        log.Fatal(err)
    }

    shmaddr, _, err := syscall.Syscall(syscall.SYS_SHMAT, shmId, 0, 0)
    if err != 0 {
        fmt.Printf("syscall error, err: %v\n", err)
        os.Exit(-2)
    }
    fmt.Printf("shmaddr: %v\n", shmaddr)

    defer syscall.Syscall(syscall.SYS_SHMDT, shmaddr, 0, 0)

    sizeOfPeople := unsafe.Sizeof(People{})

    for i := 0; i < 10; i++ {
        p := shmaddr + sizeOfPeople*uintptr(i)
        data := (*People)(unsafe.Pointer(p))
        fmt.Println(data.name.String())
        fmt.Println(data.age)
    }
}

运行效果:


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容