Linux16-进程间的通信--共享内存

12.4共享内存

共享内存是一种高效的进程间通信(IPC)机制,允许多个进程访问同一块物理内存区域,从而实现快速的数据共享。

核心特性

  • 高性能:进程直接读写内存,无需系统调用开销
  • 零拷贝:数据不需要在内核和用户空间之间复制

需要同步机制:多个进程同时访问时需要信号量等同步工具

  • 内核持久性:共享内存段独立于进程存在,除非显式删除

12.4.1共享内存相应函数

我们使用shmget函数来创建共享内存:

cpp 复制代码
int shmget(key_t key,size_t,int shmflg);
  • key :共享内存键值,可以使用 ftok() 生成或 IPC_PRIVATE
  • size :共享内存段大小(字节)
  • shmflg :权限标志,如 IP C_CREAT | 0666

我们使用shmat函数映射到物理内存段:

cpp 复制代码
void *shmat(int shmid,const void* shmaddr,int shmflg);
  • shmid 共享内存标识符
  • shmaddr:指定映射地址通常设为NULL
  • shmflg:访问模式如SHM)RDONLY只读 可为0
  • 返回值为指向共享内存的指针

我们使用shmdt函数来分离共享内存:

cpp 复制代码
int shmdt(const void *shmaddr);
  • 返回值-1为分离失败

我们使用shmctl来控制共享内存:

cpp 复制代码
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
  • shmid 共享内存标识符

cmd:

  • IPC_RMID :删除共享内存段
  • IPC_STAT :获取状态信息
  • IPC_SET :设置参数

struct shmid_ds 是Linux内核中用于描述共享内存段状态的数据结构,定义在 <sys/shm.h> 头文件中。

cpp 复制代码
struct shmid_ds {

struct ipc_perm shm_perm; /* 操作权限结构 */

int shm_segsz; /* 段大小(字节) */

__kernel_time_t shm_atime; /* 最后附加时间 */

__kernel_time_t shm_dtime; /* 最后分离时间 */

__kernel_time_t shm_ctime; /* 最后修改时间 */

__kernel_ipc_pid_t shm_cpid; /* 创建者进程ID */

__kernel_ipc_pid_t shm_lpid; /* 最后操作进程ID */

unsigned short shm_nattch; /* 当前附加计数 */

unsigned short shm_unused; /* 兼容性填充 */

void *shm_unused2; /* 保留字段 */

void *shm_unused3; /* 保留字段 */

};
  1. 权限控制结构 - shm_perm
cpp 复制代码
struct ipc_perm {

key_t __key; /* 共享内存键值 */

uid_t uid; /* 所有者有效用户ID */

gid_t gid; /* 所有者有效组ID */

uid_t cuid; /* 创建者有效用户ID */

gid_t cgid; /* 创建者有效组ID */

unsigned short mode; /* 权限模式 */

unsigned short __seq; /* 序列号 */

};

作用:控制对共享内存段的访问权限,类似于文件权限控制。

  1. 大小信息 - shm_segsz
  • 类型: int
  • 含义:共享内存段的大小,以字节为单位
  • 示例:如果创建时指定大小为1024字节,则 shm_segsz 值为1024
  1. 时间戳信息
成员 类型 含义
shm_atime __kernel_time_t 最后一个进程附加(attach)到该共享内存的时间
shm_dtime __kernel_time_t 最后一个进程分离(detach)的时间
shm_ctime __kernel_time_t 共享内存段最后修改(创建或权限变更)的时间
  1. 进程标识信息
成员 类型 含义
shm_cpid __kernel_ipc_pid_t 创建该共享内存段的进程ID
shm_lpid __kernel_ipc_pid_t 最后对该共享内存执行操作的进程ID
shm_nattch unsigned short 当前附加到该共享内存段的进程数量

12.4.2共享内存示例

生产者a.c代码:

cpp 复制代码
#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include "sem.h"

int main()

{

int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);

if (shmid == -1)

{

exit(1);

}

char *s = shmat(shmid, NULL, 0);

if (s == (char *)-1)

{

exit(1);

}

while (1)

{

printf("input: ");

fgets(s, 127, stdin);

if (strncmp(s, "end", 3) == 0)

{

break;

}

}

shmdt(s);

}

消费者b.c代码:

cpp 复制代码
#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include "sem.h"

int main()

{

int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);

if (shmid == -1)

{

exit(1);

}

char *s = shmat(shmid, NULL, 0);

if (s == (char *)-1)

{

exit(1);

}

while (1)

{

if (strncmp(s, "end", 3) == 0)

break;

printf("read: %s", s);

sleep(1);

}

shmdt(s);

shmctl(shmid, IPC_RMID, NULL);

}

运行结果:

由运行结果可以发现当生产者不去改变映射段内容时,消费者仍在打印上次输入的内容,那么如何实现a输入一次,b打印一次呢,我们就要引入上此学习到的信号量。

a.c代码如下:

cpp 复制代码
#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include "sem.h"

int main()

{

sem_init();

int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);

if (shmid == -1)

{

exit(1);

}

char *s = shmat(shmid, NULL, 0);

if (s == (char *)-1)

{

exit(1);

}

while (1)

{

sem_p(0);

printf("input: ");

fgets(s, 127, stdin);

if (strncmp(s, "end", 3) == 0)

{

sem_v(1);

break;

}

sem_v(1);

}

shmdt(s);

}

b.c代码如下:

cpp 复制代码
#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include "sem.h"

int main()

{

sem_init();

int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);

if (shmid == -1)

{

exit(1);

}

char *s = shmat(shmid, NULL, 0);

if (s == (char *)-1)

{

exit(1);

}

while (1)

{

sem_p(1);

if (strncmp(s, "end", 3) == 0)

break;

printf("read: %s", s);

sleep(1);

sem_v(0);

}

shmdt(s);

sem_destroy();

shmctl(shmid, IPC_RMID, NULL);

}

运行结果:

可知实现a输入一次,b打印一次。

12.4.3总结

1. 同步机制必须

共享内存不提供进程间同步,需要配合其他机制:

cpp 复制代码
// 通常需要信号量来同步访问

#include <semaphore.h>

sem_t *sem = sem_open("/mysem", O_CREAT, 0644, 1);

// 写入前加锁

sem_wait(sem);

strcpy(shm_ptr, data);

sem_post(sem);

2. 内存对齐和字节序

不同架构的机器可能有不同的字节序,需要处理兼容性。

  1. 资源管理
  • 及时调用 shmdt() 分离内存
  • 使用 shmctl(IPC_RMID) 删除不再使用的共享内存
  • 避免内存泄漏
  1. 系统限制

检查系统共享内存限制:

cpp 复制代码
bash# 查看系统IPC限制

ipcs -l

# 查看当前共享内存段

ipcs -m

共享内存架构类型

UMA(统一内存访问)

所有处理器共享一个统一的物理内存,每个处理器都可以均匀地访问内存中的任何数据。

NUMA(非统一内存访问)

内存被划分为多个节点,每个节点有一个本地内存。处理器可以快速访问本地内存中的数据,但访问远程内存时会有延迟。

共享内存是Linux进程间通信的最高效机制,核心要点包括:

  1. 高性能优势:直接内存访问,零拷贝设计
  1. 使用步骤:创建( shmget ) → 连接( shmat ) → 使用 → 分离( shmdt ) → 控制( shmctl )
  1. 同步必要性:必须配合信号量等同步机制防止数据竞争
  1. 资源管理:及时分离和删除,避免资源泄漏
  1. 架构选择:根据性能需求选择UMA或NUMA架构

掌握共享内存的使用对于开发高性能的多进程应用程序至关重要,特别是在需要处理大数据量或对性能要求极高的场景中。

相关推荐
运维帮手大橙子2 小时前
Docker监控系统中添加NodeExporter
linux·运维
Lzc7743 小时前
Linux网络的应用层协议HTTP
linux·1024程序员节·应用层协议http
susu10830189113 小时前
FAT32/VFAT 文件系统不支持 Linux 文件权限,cp文件总是异常
linux·运维·服务器
絔离3 小时前
Linux下查看系统启动时间、运行时间
linux·运维·服务器
呆呆小金人3 小时前
Linux:开源时代的隐形基石
linux·1024程序员节
扶尔魔ocy3 小时前
【Linux C/C++开发】epoll模式的开源库及原生socket实现
linux·网络编程·epoll
落羽的落羽4 小时前
【Linux系统】从零掌握make与Makefile:高效自动化构建项目的工具
linux·服务器·开发语言·c++·人工智能·机器学习·1024程序员节
小小小糖果人5 小时前
Linux云计算基础篇(24)-PXE批量安装和Kickstart工具
linux·运维·php
Sylvia@8886 小时前
19.管理基本存储
linux·运维·1024程序员节