链接: linux C学习目录
linux C 共享内存机制
- 共享内存
- 物理位置shared memory
- 常用函数
- 编程模型
- 范例
- write.c
- read.c
 
- 修改参数
- 实验
共享内存
- 二个或者多个进程,共享同一块由系统内核负责维护的内部内存区域其地址空间通常被映射到堆和栈之间
- 无需复制信息,最快的一种IPC机制
- 需要考虑同步访问的问题
- 内核为每个共享内存,维护一个shmid_ds结构体形式的共享内存对象
物理位置shared memory
共享内存的物理位置位于堆和栈之间
常用函数
#include <sys/shm.h>
/**
创建一块共享内存
*  key:该函数以key参数为键值创建共享内存,获取已有的共享内存
*  size:共享内存的字节数,建议取内存页(4096)的整数倍,
        若只为获取已有的共享内存,参数写0
*  shmflag:0-获取,不存在即失败;
           IPC_CREAT:没有就创建,有就获取
           IPC_EXCL:排斥,已存在即失败
*  return:返回共享内存的标识(和地址是一个键值对),失败-1
**/
int shmget(key_t key ,size_t size,int shmflag);
/**
映射地址到共享内存
*  shmid:唯一标识符
*  shmaddr:指定映射地址,设置为NULL系统自动选择
*  shmflg:
    0-以读写方式使用内存共享
    SHM_RDONLY-只读 
    SHM_RND-只在参数shmaddr非NULL时起作用,表示对
    改参数向下取内存页的整数倍,作为映射地址
*  return:成功返回映射地址;失败-1
注意:内存将改共享内存的加载计数+1,这类共享内存的方式
必须要所有的映射都关闭之后才能释放这块内存区域,所有必须
记录映射的次数;
**/
void* shmat(int shmid,const void* shmaddr,int shmflg);
/**
卸载共享内存
*  shmaddr:从调用进程的地址空间中,
    取消由shmaddr参数所指向的,共享内存映射区域
*
*  return:成功返回0,失败返回-1
注意:卸载后共享内存计数器减1
**/
int shmdt(const void* shmaddr);
/**
销毁/控制共享内存
*  shmid:唯一识别号
*  cmd:
    IPC_STAT - 获取共享内存的属性,通过buf参数输出
    IPC_SET  - 设置共享内存的属性,通过buf参数输入,
                仅以下三个属性可设置:
                    shmid_ds::shm_perm.uid
                    shmid_ds::shm_perm.gid
                    shmid_ds::shm_perm.mode
    IPC_RMID  - 标记删除共享内存.
                并非真正删除共享内存,只是做一个删除标记
                禁止其被继续加载,但已有加载的依然保留.
                只有当该共享内存的记载计数为0时,才真正被删除
*  struct shmid_ds*:见下
*  return:成功返回0,失败返回-1
**/
int shmctl(int shmid,int cmd,struct shmid_ds* buf)
/**
*	共享内存结构体
**/
typedef struct {
    struct ipc_perm shm_perm;//所有者及权限:见下表结构体
    size_t          shm_segsz;//大小(字节)
    time_t          shm_atime;//最后加载时间
    time_t          shm_dtime;//最后卸载时间
    time_t          shm_ctime;//最后改变时间
    pid_t           shm_cpid;//创建进程PID
    pid_t           shm_lpid;//最后加载/卸载进程PID
    shmatt_t        shm_nattch;//当前加载计数
    ...
};
struct ipc_perm {
    key_t     key;//键值
    uid_t     uid;//有效属主ID
}
编程模型
| 步骤 | 进程A | 函数 | 进程B | 步骤 | 
|---|---|---|---|---|
| 1 | 创建 | shmget | 获取 | 1 | 
| 2 | 加载 | shmat | 加载 | 2 | 
| 3 | 使用 | … | 使用 | 3 | 
| 4 | 卸载 | shmdt | 卸载 | 4 | 
| 5 | 销毁 | shmctl | — | 
范例
write.c
#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>
int main(void){
    printf("创建共享内存...\n");
    
    key_t key = ftok(".",100);
    if(key == -1){
        perror("ftok");
        return -1;    
    }
    
    int shmid = shmget(key,4096,0644|IPC_CREAT | IPC_EXCL);
    if(shmid == -1){
        perror("shmget");
        return -1;    
    }
    
    printf("加载共享内存...\n");
    void* shmaddr = shmat(shmid,NULL,0);
    if(shmaddr == (void*)-1){
        perror("shmat");
        return -1;    
    }
    
    printf("写入共享内存...\n");
    sprintf(shmaddr,"我是%u进程写入的数据.",getid());
    
    printf("按<回车>卸载共享内存(0x%08x/%d)...",key,shmid);
    getchar();
    
    if(shmdt(shmaddr) == -1){
        perror("shmdt");
        return -1;    
    }
    
    printf("按<回车>销毁共享内存(0x%08x/%d)...",key,shmid);
    getchar();
    if(shmctl(shmid,IPC_RMID,NULL) == -1){
        perror("shmctl");
        return -1;    
    }
    
    printf("大功告成!\n");
    return 0;
}
read.c
#include <stdio.h>
#include <time.h>
#include <sys/shm.h>
int shmstat(int shmid){
    struct shmid_ds shm;
    if(shmctl(shmid,IPC_STAT,&shm) == -1){
        perror("shmctl");
        return -1;    
    }
    printf("键值:0x%08x\n",shm.shm_perm.__key);
    printf("有效属主ID:%u\n",shm.shm_perm.uid);
    printf("有效属组ID:%u\n",shm.shm_perm.gid);
    printf("有效创建者ID:%u\n",shm.shm_perm.cuid);
    printf("有效创建组ID:%u\n",shm.shm_perm.cgid);
    printf("权限字:%#o\n",shm.shm_perm.mode);
    printf("大小(字节):%u\n",shm.shm_segsz);
    printf("最后加载时间:%s\n",ctime(&shm.shm_atime));
    printf("最后卸载时间:%s\n",ctime(&shm.shm_dtime));
    printf("最后改变时间:%s\n",ctime(&shm.shm_ctime));
    printf("创建进程ID:%u\n",shm.shm_cpid);
    printf("最后加载/卸载进程ID:%u\n",shm.shm_lpid);
    printf("当前加载计数:%ld\n",shm.shm_nattch);
    
    return 0;
}
int main(void){
    printf("获取共享内存...\n");
    
    key_t key = ftok(".",100);
    if(key == -1){
        perror("ftok");
        return -1;    
    }
    
    int shmid = shmget(key,0,0);
    if(shmid == -1){
        perror("shmget");
        return -1; 
    }
    
    printf("加载共享内存...\n");
    void* shmaddr = shmat(shmid,NULL,0);
    if(shmaddr == (void*)-1){
        perror("shmat");
        return -1;    
    }
    shmstat(shmid);
    printf("读取共享内存...\n");
    printf("共享内存(0x%08x/%d):%s\n",key,shmid,shmaddr);
    
    printf("卸载共享内存...\n");
    if(shmdt(shmaddr) == -1){
        perror("shmdt");
        return -1;    
    }
    
    shmstat(shmid);
    printf("大功告成!\n");
    return 0;
}
修改参数
函数shmctl是可以修改一些特定参数的我们可以试一下
备注: 修改参数一定不要直接修改,这样会把原先的其他参数修改掉
- 先读取原始的共享内存结构体参数
- 然后修改参数
- 最后设置回去
//修改共享内存的一些属性
int shmset(int shmid){
    struct shmid_ds shm = {0};
    
    if(shmget(shmid,IPC_STAT,&shm) == -1){
        perror("shmctl");
        return -1;
    }
    
    shm.shm_perm.uid = 0600;
    shm.shm_segsz = 2*4096;//共享内存的大小是不能修改的
    
    if(shmctl(shmid,IPC_SET,&shm) == -1){
        perror("shmctl");
        return -1;    
    }  
}
实验
ipcs -m //查询共享内存

 可以看到shmid = 65559的就是我们创建的共享内存,字节大小也是4096,连接数是1,就是只有一个线程在使用共享内存。
看一下使用结果



![基于YOLOv5+C3CBAM+CBAM注意力的海底生物[海参、海胆、扇贝、海星]检测识别分析系统](https://img-blog.csdnimg.cn/img_convert/df21899986d75fb72e8d3291ac8b7a60.png)

















