System V共享内存
共享内存概述
共享内存是所有IPC中最快的一种。它之所以快是因为共享内存一旦映射到进程的地址空间,进程之间数据的传递就不需要涉及内核了。
管道、FIFO和消息队列,任意两个进程之间想要交换信息,都必须通过内核,内核在其中发挥了中转站的作用:
- 发送信息的一方,通过系统调用(write或msgsnd)将信息从用户层拷贝到内核层,由内核暂存这部分信息。
- 提取信息的一方,通过系统调用(read或msgrcv)将信息从内核层提取到应用层。
建立共享内存之后,内核完全不参与进程间的通信,这种说法严格来讲并不是正确的。因为当进程使用共享内存时,可能会发生缺页,引发缺页中断,这种情况下,内核还是会参与进来的。
允许多个进程同时操作共享内存,就不得不防范竞争条件的出现,比如有两个进程同时执行更新操作,或者一个进程在执行读取操作时,另外一个进程正在执行更新操作。因此,共享内存这种进程间通信的手段通常不会单独出现,总是和信号量、文件锁等同步的手段配合使用。
创建或打开共享内存
shmget函数负责创建或打开共享内存段,其接口定义如下:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
第一个参数是共享内存标识,可以随机选择一个整数值,也可以使用ftok函数创建。
可以参考链接:https://blog.csdn.net/m0_51415606/article/details/138001330?spm=1001.2014.3001.5502
第二个参数size必须是正整数,表示要创建的共享内存的大小。内核以页面大小的整数倍来分配共享内存,因此,实际size会被向上取整为页面大小的整数倍。
第三个参数支持IPC_CREAT和IPC_EXCL标志位。如果没有设置IPC_CREAT标志位,那么第二个参数size对共享内存段并无实际意义,但是必须小于或等于共享内存的大小,否则会有EINVAL错误。
返回值:
创建或打开的共享内存标识。
使用共享内存
shmget函数,不过是在茫茫内存中创建了或找到了一块共享内存区域,但是这块内存和进程尚没有任何关系。要想使用该共享内存,必须先把共享内存引入进程的地址空间,这就是attach操作。
attach操作的接口定义如下:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
第一个参数是调用shmget函数的返回值。
第二个参数是用来指定将共享内存放到虚拟地址空间的什么位置的。大部分的普通青年都会将第二个参数设置为NULL,表示用户并不在意,一切交由内核做主。
第三个参数是标志参数,用于指定连接共享内存的选项。
- 如果指定了 SHM_RDONLY 标志,则进程只能以只读方式访问共享内存,否则进程可以以读写方式访问共享内存。
- 通常可以使用 0 作为该参数,表示默认行为。
返回值:
调用成功,则返回进程虚拟地址空间内的一个地址。如果失败,就会返回(void*)-1,并且设置errno。
分离共享内存
分离操作的接口定义如下:
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
shmdt函数仅仅是使进程和共享内存脱离关系,并未删除共享内存。shmdt函数的作用是将共享内存的引用计数减1。如前所述,只有共享内存的引用计数为0时,调用shmctl函数的IPC_RMID命令才会真正地删除共享内存。
进程执行exec之后,所有attach的共享内存都会被分离。当进程终止之后,共享内存也会自动被分离。
控制共享内存
shmctl函数用来控制共享内存,函数接口定义如下:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
当cmd为IPC_STAT和IPC_SET时,需要用到第三个参数。其中shmid_ds结构体的定义如下:
struct shmid_ds {
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_t shm_lpid;
shmatt_t shm_nattch;
...
};
1、IPC_STAT
用于获取shmid对应的共享内存的信息。所谓信息,就是上面结构体的内容。
2、IPC_SET
IPC_SET也只能修改shm_perm中的uid、gid及mode。
代码示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#define SHM_SIZE 1024
int main() {
int shmid;
key_t key;
char* shmaddr;
// 生成一个唯一的key
key = ftok(".", 's');
if (key == -1) {
perror("ftok");
exit(1);
}
// 创建共享内存段
shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1)
{
perror("shmget");
exit(1);
}
// 将共享内存段映射到进程的地址空间
shmaddr = (char*)shmat(shmid, NULL, 0);
if (shmaddr == (char*)-1)
{
perror("shmat");
exit(1);
}
// 创建五个子进程
for (int i = 0; i < 5; ++i)
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
exit(1);
}
else if (pid == 0) { // 子进程
while (1)
{
printf("Child %d: PID=%d\n", i + 1, getpid());
char message[50];
sprintf(message, "Message from Child %d", i + 1);
sprintf(shmaddr, "%s", message);
// exit(0);
sleep(1);
}
}
}
while (1)
{
// 打印从共享内存中读取的消息
printf("Parent process reads: %s\n", shmaddr);
sleep(1);
}
// 等待所有子进程退出
for (int i = 0; i < 5; ++i)
wait(NULL);
// 分离共享内存
if (shmdt(shmaddr) == -1)
{
perror("shmdt");
exit(1);
}
// 删除共享内存段
if (shmctl(shmid, IPC_RMID, NULL) == -1)
{
perror("shmctl");
exit(1);
}
return 0;
}