https://blog.csdn.net/qscftqwe/article/details/156148196
上节课链接,大家可以点进去看一下!
一.共享内存
共享内存共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

那释放和关联共享内存是由进程操作还是操作系统?
只有靠操作系统来做才是共享的,不然就是进程私有的
5.1 共享内存函数
1.shmget
cpp
// 获取一个 System V 共享内存段的标识符(shmid),用于进程间共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
// 参数:
// key - 共享内存的唯一标识键(通常由 ftok 生成,或 IPC_PRIVATE 表示私有)
// size - 要创建的共享内存段大小(以字节为单位,建议大小4096倍);
// 若访问已存在的段,可设为 0
// shmflg - 权限和控制标志,如:
// IPC_CREAT:若不存在则创建
// IPC_EXCL :与 IPC_CREAT 同用,确保不打开已有段
// 权限位(如 0666)指定新段的访问权限
// 返回值:
// 成功:返回非负整数(共享内存标识符 shmid)
// 失败:返回 -1,并设置 errno
// 例子:
// 创建一个 4096 字节的私有共享内存段
int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
补充:

- 为什么ftok是用户提供不让系统提供,因为如果让系统提供就变成了鸡生蛋,蛋生鸡的问题了即要先开辟通信才能发送但是要先发送才能开辟
- 共享内存标识符的体系结构是独立的不太符合一切皆文件思想!
- 为什么推荐size为4096整数倍,因为页的大小是4KB=4096字节,然后内核是以页为单位进行分配物理内存的
理解key和shmid的区别:
key:操作系统内,标定唯一性
shmid:在进程内,标定唯一性
共享内存的生命周期:
共享内存的生命周期由内核管理,不会因某个进程退出而自动销毁。
用户不主动关闭,共享内存会一直存在。直到显示删除或系统重启
bash
# 1. 查看所有共享内存
ipcs -m
# 2. 删除某个 shmid(例如 131072)
ipcrm -m 131072
2.shmat函数
cpp
// 将共享内存段挂接到当前进程的地址空间,返回可直接读写的指针
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
// 参数:
// shmid - 共享内存标识符(由 shmget 返回)
// shmaddr - 指定挂接地址;通常设为 NULL,表示由系统自动选择合适地址
// shmflg - 控制挂接方式,常用:
// 0 :可读可写
// SHM_RDONLY:只读挂接
// 返回值:
// 成功:返回指向共享内存的指针(可像普通内存一样读写)
// 失败:返回 (void *) -1,并设置 errno
// 案例:
// 将 shmid 对应的共享内存挂接到当前进程
char *shm_ptr = shmat(shmid, NULL, 0);
3.smhdt
cpp
// 从当前进程的地址空间分离(卸载)已挂接的共享内存段
#include <sys/shm.h>
int shmdt(const void *shmaddr);
// 参数:
// shmaddr - 指向之前通过 shmat() 挂接的共享内存地址
// 返回值:
// 成功:返回 0
// 失败:返回 -1,并设置 errno
// 案例:
// 分离之前挂接的共享内存
shmdt(ptr);
补充:当进程结束后会关闭进程地址空间,所以关闭进程也能断开共享内存连接,不过断开并不代表删除共享内存段本身,只是表示不能在访问了!
4.shmctl
cpp
// 对共享内存段执行控制操作,如获取信息、修改权限或删除
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
// 参数:
// shmid - 共享内存标识符(由 shmget 返回)
// cmd - 控制命令,常用:
// IPC_STAT:获取共享内存信息到 buf
// IPC_SET :设置权限和属主(从 buf 读取)
// IPC_RMID:立即标记共享内存段为删除(释放资源)
// buf - 指向 shmid_ds 结构的指针,用于传入或接收元数据
// 返回值:
// 成功:返回 0(IPC_STAT/IPC_SET)或 0(IPC_RMID)
// 失败:返回 -1,并设置 errno
// 案例:
// 删除指定的共享内存段
shmctl(shmid, IPC_RMID, NULL);
1.2 共享内存性质
1.共享内存特性
-
一旦有了共享内存,将其链接到自己的地址空间中,就可以直接把它当成自己的内存空间来使用,不需要调用系统调用
-
一旦有人把数据写入到共享内存,我们立马就能看到;不需要经过系统调用,直接就能看到数据
2.共享内存特点
-
共享内存没有同步互斥之类的保护机制
-
共享内存是所有的进程间通信中,速度最快的!(因为共享内存的特性)
-
共享内存内部的数据,由用户自己维护!
二.共享内存代码实现
2.1 函数介绍
ftok
cpp
// 生成一个唯一的 System V IPC 键(key_t),用于 shmget、msgget 等函数
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
// 参数:
// pathname - 必须是一个已存在的、可访问的文件路径
// proj_id - 项目 ID(低 8 位有效,通常用 'a'、'b' 或 1、2 等简单值)
// 返回值:
// 成功:返回生成的 key_t 值(非负)
// 失败:返回 -1,并设置 errno(如文件不存在)
// 案例:
// 使用当前目录和项目 ID 'A' 生成 IPC 键
key_t key = ftok(".", 'A');
2.2 代码实现
cpp
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/shm.h>
#include <cstdlib>
using namespace std;
// 共享内存总大小:留出空间给消息 + 1 字节作为 stop 标志
// (简单起见,用最后 1 字节表示 stop)
const int BUFFER_SIZE = 1024;
const int MSG_SIZE = BUFFER_SIZE - 1; // 消息最多占前 1023 字节
void Writer(int shmid)
{
char *shm_ptr = (char *)shmat(shmid, nullptr, 0);
if (shm_ptr == (void *)-1)
{
perror("shmat in child");
exit(1);
}
pid_t self = getpid();
int number = 0;
// 【关键修改】循环条件:检查 stop 标志(位于 shm_ptr[BUFFER_SIZE - 1])
while (shm_ptr[BUFFER_SIZE - 1] == 0) // 0 表示继续,1 表示停止
{
// 写入消息到前 MSG_SIZE 字节
snprintf(shm_ptr, MSG_SIZE, "hello,I am child-%d-%d", self, number);
number++;
sleep(1);
}
shmdt(shm_ptr);
exit(0); // 子进程正常退出
}
void Reader(int shmid)
{
char *shm_ptr = (char *)shmat(shmid, nullptr, 0);
if (shm_ptr == (void *)-1)
{
perror("shmat in parent");
return;
}
int cnt = 0;
while (cnt < 5)
{
cout << "father get a message[" << getpid() << "]# " << shm_ptr << endl;
cnt++;
sleep(1);
}
// 【关键】读完 5 条后,设置 stop 标志(写入最后一字节)
shm_ptr[BUFFER_SIZE - 1] = 1; // 通知子进程退出
shmdt(shm_ptr);
}
int main()
{
key_t key = ftok(".", 'A');
if (key == -1)
{
perror("ftok");
return 1;
}
// 创建共享内存(整块 1024 字节)
int shmid = shmget(key, BUFFER_SIZE, IPC_CREAT | 0666);
if (shmid < 0)
{
perror("shmget");
return 1;
}
pid_t id = fork();
if (id < 0)
{
perror("fork");
shmctl(shmid, IPC_RMID, nullptr);
return 2;
}
if (id == 0)
{
Writer(shmid);
// 不会执行到这里(exit 在 Writer 中)
}
sleep(1);
Reader(shmid);
waitpid(id, nullptr, 0); // 进程阻塞式等待
// 清理共享内存
shmctl(shmid, IPC_RMID, nullptr);
return 0;
}
补充:这个为什么子进程不能写成死循环,因为共享内存没有同步(无阻塞)所以不行,管道它是有的所以子进程可以写成死循环,因为父进程不读了,你子进程也写不了!
关于进程间的通信就到这里,总共和大家介绍了两种:管道和共享内存,其中管道又分为匿名管道和命名管道,这三部分我都给大家写了简易的代码,大家可以跟着敲一遍多思考,这样就能获得提升,不管怎么说还是要加油加油!