共享内存
1. 概述
共享内存是一种进程间通信的机制,它允许多个进程在它们的地址空间中映射相同的一块物理内存,从而实现进程之间的数据共享。通过共享内存,不同进程可以直接读取和写入这块内存,而无需经过内核空间,因此通常比其他进程间通信机制更为高效。在所有进程间通信的方式中共享内存的效率是最高的。
共享内存的主要特点包括:
- 直接访问: 共享内存允许多个进程直接访问相同的内存区域,无需通过中间缓冲区或消息传递的方式,提高了数据传输的效率。
- 高效: 由于不涉及内核空间的数据传输,共享内存通常比其他进程间通信方式(如管道或消息队列)更为高效。
- 持久性: 共享内存通常是持久性的,即在进程结束后,共享内存仍然存在,其他进程仍然可以访问其中的数据。
- 不同步机制: 共享内存本身并不提供同步机制,因此在多进程访问共享内存时,需要使用其他同步手段,如信号量或互斥锁,以避免数据一致性问题。
2. 创建/打开共享内存
2.1 shmget函数
shmget
是一个用于创建或获取共享内存段的系统调用。在 POSIX 系统中,它通常用于进程间通信,允许多个进程共享同一块内存。以下是 shmget
函数的一般形式:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数说明:
key
:共享内存段的键值,用于唯一标识一个共享内存段。可以使用ftok
函数生成键值,或者使用 IPC_PRIVATE 表示创建一个新的共享内存段。size
:共享内存段的大小,以字节为单位。shmflg
:标志参数,用于指定操作的方式。可以是以下标志的组合:IPC_CREAT
:如果共享内存段不存在,则创建一个新的。IPC_EXCL
:与IPC_CREAT
一起使用,如果共享内存段已存在,则报错。- 权限标志(例如
0666
):指定共享内存段的权限。
返回值:
- 成功时,返回共享内存段的标识符(非负整数),可以用于后续的操作。
- 失败时,返回 -1,并设置全局变量
errno
表示错误原因。
函数使用举例:
- 创建一块大小为4k的共享内存
shmget(100, 4096, IPC_CREAT|0664);
- 创建一块大小为4k的共享内存, 并且检测是否存在
// 如果共享内存已经存在, 共享内存创建失败, 返回-1, 可以perror() 打印错误信息
shmget(100, 4096, IPC_CREAT|0664|IPC_EXCL);
2.2 ftok函数
ftok
函数是一个用于生成键值(key)的辅助函数,通常用于创建或获取共享内存、消息队列等 IPC(Inter-Process Communication)机制的标识符。ftok
的声明如下:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数说明:
pathname
:一个已经存在的文件的路径名。ftok
通过使用指定文件的 inode 和 proj_id 来生成唯一的键值。proj_id
:是一个整数值,通常是 0 到 255 之间的一个数字。它被用于加密 inode,以产生最终的键值。
返回值:
- 如果成功,返回生成的键值。
- 如果失败,返回 -1,并设置全局变量
errno
表示错误原因。
使用举例:
// 根据路径生成一个key_t
key_t key = ftok("/home/robin", 'a');
// 创建或打开共享内存
shmget(key, 4096, IPC_CREATE|0664);
3. 关联和解除关联
3.1 shmat
创建**/打开共享内存之后还必须和共享内存进行关联,这样才能得到共享内存的起始地址**,通过得到的内存地址进行数据的读写操作,关联函数的原型如下:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
- shmid: 要操作的共享内存的ID, 是 shmget() 函数的返回值
- shmaddr: 共享内存的起始地址, 用户不知道, 需要让内核指定, 写NULL
- shmflg: 和共享内存关联的对共享内存的操作权限
- SHM_RDONLY: 读权限, 只能读共享内存中的数据
- 0: 读写权限,可以读写共享内存数据
返回值:关联成功,返回值共享内存的起始地址,关联失败返回 (void *) -1
3.2 shmdt
当进程不需要再操作共享内存,可以让进程和共享内存解除关联,另外如果没有执行该操作,进程退出之后,结束的进程和共享内存的关联也就自动解除了。
int shmdt(const void *shmaddr);
- 参数:shmat() 函数的返回值, 共享内存的起始地址
- 返回值:关联解除成功返回0,失败返回-1
4. 删除共享内存
4.1 shmctl
shmctl
函数用于控制共享内存段,包括获取共享内存段信息、设置权限和删除共享内存段等操作。以下是 shmctl
函数的一般形式:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数说明:
shmid
:共享内存段的标识符,通常由shmget
返回。- cmd:控制命令,用于指定 shmctl的操作方式。可以是以下常量之一:
IPC_STAT
:获取共享内存段的信息,并将结果存储在buf
结构体中。IPC_SET
:设置共享内存段的信息,使用buf
结构体中的数据。IPC_RMID
:删除共享内存段。
buf
:用于存储或传递信息的结构体指针,通常是struct shmid_ds
类型的结构体。- cmd==IPC_STAT, 作为传出参数, 会得到共享内存的相关属性信息
- cmd==IPC_SET, 作为传入参数, 将用户的自定义属性设置到共享内存中
- cmd==IPC_RMID, buf就没意义了, 这时候buf指定为NULL即可
结构体:
// 参数 struct shmid_ds 结构体原型
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
// 引用计数, 多少个进程和共享内存进行了关联
shmatt_t shm_nattch; /* 记录了有多少个进程和当前共享内存进行了管联 */
...
};
返回值:
- 成功时,根据命令不同,返回相应的信息或执行成功的状态。通常返回 0。
- 失败时,返回 -1,并设置全局变量
errno
表示错误原因。
4.2 相关shell命令
使用ipcs 添加参数-m可以查看系统中共享内存的详细信息
使用 ipcrm 命令可以标记删除某块共享内存
4.3 共享内存状态
struct shmid_ds的结构体中,其中有一个非常重要的成员叫做shm_nattch,在这个成员变量里边记录着当前共享内存关联的进程的个数,一般将其称之为引用计数。当共享内存被标记为删除状态,并且这个引用计数变为0之后共享内存才会被真正的被删除掉。
当共享内存被标记为删除状态之后,共享内存的状态也会发生变化,共享内存内部维护的key从一个正整数变为0,其属性从公共的变为私有的。这里的私有是指只有已经关联成功的进程才允许继续访问共享内存,不再允许新的进程和这块共享内存进行关联了。
5. 进程间通信
下面是一个简单的 C 语言示例,演示了如何使用共享内存实现两个相关进程之间的通信。一个进程写入数据到共享内存,另一个进程从共享内存读取数据:
进程1 - 写入数据到共享内存:
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <string.h>
#define SHARED_MEMORY_KEY 1234
#define SHARED_MEMORY_SIZE 1024
int main() {
// 创建或获取共享内存段
int shmid = shmget(SHARED_MEMORY_KEY, SHARED_MEMORY_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
// 连接到共享内存段
void *shared_memory = shmat(shmid, NULL, 0);
if (shared_memory == (void*)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
// 写入数据到共享内存
char* char_shared_memory = (char*)shared_memory;
strcpy(char_shared_memory, "Hello, shared memory!");
// 等待一段时间,模拟进程间同步
sleep(5);
// 解除连接
shmdt(shared_memory);
return 0;
}
进程2 - 从共享内存读取数据:
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#define SHARED_MEMORY_KEY 1234
#define SHARED_MEMORY_SIZE 1024
int main() {
// 获取共享内存段
int shmid = shmget(SHARED_MEMORY_KEY, SHARED_MEMORY_SIZE, 0666);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
// 连接到共享内存段
void *shared_memory = shmat(shmid, NULL, 0);
if (shared_memory == (void*)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
// 读取数据从共享内存
printf("Received message: %s\n", (char*)shared_memory);
// 解除连接
shmdt(shared_memory);
return 0;
}
记住要删除共享内存(代码中实现也可以)