共享内存是一种进程间通信的机制,它允许多个进程在它们的地址空间中映射相同的一块物理内存,从而实现进程之间的数据共享。通过共享内存,不同进程可以直接读取和写入这块内存,而无需经过内核空间,因此通常比其他进程间通信机制更为高效。在所有进程间通信的方式中共享内存的效率是最高的。
共享内存的主要特点包括:
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
):指定共享内存段的权限。返回值:
errno
表示错误原因。函数使用举例:
shmget(100, 4096, IPC_CREAT|0664);
// 如果共享内存已经存在, 共享内存创建失败, 返回-1, 可以perror() 打印错误信息
shmget(100, 4096, IPC_CREAT|0664|IPC_EXCL);
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,以产生最终的键值。返回值:
errno
表示错误原因。使用举例:
// 根据路径生成一个key_t
key_t key = ftok("/home/robin", 'a');
// 创建或打开共享内存
shmget(key, 4096, IPC_CREATE|0664);
创建**/打开共享内存之后还必须和共享内存进行关联,这样才能得到共享内存的起始地址**,通过得到的内存地址进行数据的读写操作,关联函数的原型如下:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
返回值:关联成功,返回值共享内存的起始地址,关联失败返回 (void *) -1
当进程不需要再操作共享内存,可以让进程和共享内存解除关联,另外如果没有执行该操作,进程退出之后,结束的进程和共享内存的关联也就自动解除了。
int shmdt(const void *shmaddr);
shmctl
函数用于控制共享内存段,包括获取共享内存段信息、设置权限和删除共享内存段等操作。以下是 shmctl
函数的一般形式:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数说明:
shmid
:共享内存段的标识符,通常由 shmget
返回。IPC_STAT
:获取共享内存段的信息,并将结果存储在 buf
结构体中。IPC_SET
:设置共享内存段的信息,使用 buf
结构体中的数据。IPC_RMID
:删除共享内存段。buf
:用于存储或传递信息的结构体指针,通常是 struct shmid_ds
类型的结构体。
结构体:
// 参数 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; /* 记录了有多少个进程和当前共享内存进行了管联 */
...
};
返回值:
errno
表示错误原因。使用ipcs 添加参数-m可以查看系统中共享内存的详细信息
使用 ipcrm 命令可以标记删除某块共享内存
struct shmid_ds的结构体中,其中有一个非常重要的成员叫做shm_nattch,在这个成员变量里边记录着当前共享内存关联的进程的个数,一般将其称之为引用计数。当共享内存被标记为删除状态,并且这个引用计数变为0之后共享内存才会被真正的被删除掉。
当共享内存被标记为删除状态之后,共享内存的状态也会发生变化,共享内存内部维护的key从一个正整数变为0,其属性从公共的变为私有的。这里的私有是指只有已经关联成功的进程才允许继续访问共享内存,不再允许新的进程和这块共享内存进行关联了。
下面是一个简单的 C 语言示例,演示了如何使用共享内存实现两个相关进程之间的通信。一个进程写入数据到共享内存,另一个进程从共享内存读取数据:
#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;
}
#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;
}
记住要删除共享内存(代码中实现也可以)