文章目录
- [一、system V 共享内存](#一、system V 共享内存)
-
- 1、理解共享内存
- 2、共享内存示意图
- 3、共享内存函数
-
- [1)ftok 函数](#1)ftok 函数)
- [2)shmget 函数](#2)shmget 函数)
- [3)shmat 函数](#3)shmat 函数)
- [4)shmdt 函数](#4)shmdt 函数)
- [5)shmctl 函数](#5)shmctl 函数)
- 4、共享内存的使用样例
- 5、共享内存的优缺点
- [二、system V 消息队列](#二、system V 消息队列)
- [三、system V 信号量](#三、system V 信号量)
一、system V 共享内存
1、理解共享内存
- 本质: 由内核分配并管理的一段物理内存区域 ,允许多个进程将其映射到各自的虚拟地址空间中,从而实现直接读写共享数据,形成高效的双向数据交互。
- 使用场景:
- 一方通过
shmget()创建或获取共享内存段。 - 各进程通过
shmat()将共享内存段附加到自己的地址空间,获得指向该内存的指针。 - 双方直接通过指针读写共享内存进行数据交互,可用于对性能要求极高的场景(如大数据量实时传输)。
- 通信结束后,通过
shmdt()分离共享内存,并可通过shmctl()删除该段。
- 一方通过
2、共享内存示意图
- 进程 A 和进程 B 通过将同一块物理内存映射到各自的虚拟地址空间,实现了数据共享。
- 数据传递不再需要通过内核缓冲区,也无需执行
read/write等系统调用,进程直接读写自己地址空间内的共享内存即可,因此是最快的 IPC 方式。 - 但是共享内存本身不提供同步机制,需要配合信号量、互斥锁等工具来避免竞态条件,确保数据一致性。

3、共享内存函数
1)ftok 函数

2)shmget 函数

3)shmat 函数

4)shmdt 函数

5)shmctl 函数

4、共享内存的使用样例
1)共享内存实现通信

a)Shm.hpp
cpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
const int gdefaultid = -1;
const int gsize = 4096;
const std::string pathname = ".";
const int projid = 0x66;
const int gmode = 0666;
#define CREATER "creater"
#define USER "user"
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
class Shm
{
private:
// 创建的一定要是一个全新的共享内存
void CreateHelper(int flg)
{
printf("key: 0x%x\n", _key);
// 共享内存的生命周期随内核,不随进程
_shmid = shmget(_key, _size, flg);
if (_shmid < 0)
{
ERR_EXIT("shmget");
}
printf("shmid: %d\n", _shmid);
}
void Create()
{
CreateHelper(IPC_CREAT | IPC_EXCL | gmode);
}
void Get()
{
CreateHelper(IPC_CREAT);
}
void Attach()
{
_start_mem = shmat(_shmid, nullptr, 0);
if ((long long)_start_mem < 0)
{
ERR_EXIT("shmat");
}
printf("attach success\n");
}
void Detach()
{
int n = shmdt(_start_mem);
if (n == 0)
{
printf("detach success\n");
}
}
void Destroy()
{
if (_shmid == gdefaultid)
return;
Detach();
if (_usertype == CREATER)
{
int n = shmctl(_shmid, IPC_RMID, nullptr);
if (n == 0)
{
printf("shmctl delete shm: %d success!\n", _shmid);
}
else
{
ERR_EXIT("shmctl");
}
}
}
public:
Shm(const std::string &pathname, int projid, const std::string &usertype)
: _shmid(gdefaultid), _size(gsize), _start_mem(nullptr), _usertype(usertype)
{
_key = ftok(pathname.c_str(), projid);
if (_key < 0)
{
ERR_EXIT("ftok");
}
if (_usertype == CREATER)
Create();
else if (_usertype == USER)
Get();
else
{
}
Attach();
}
~Shm()
{
if (_usertype == CREATER)
Destroy();
}
void *VirtualAddr()
{
printf("VirtualAddr: %p\n", _start_mem);
return _start_mem;
}
int Size()
{
return _size;
}
void Attr()
{
struct shmid_ds ds;
int n = shmctl(_shmid, IPC_STAT, &ds);
printf("shm_segsz: %ld\n", ds.shm_segsz);
printf("key: 0x%x\n", ds.shm_perm.__key);
}
private:
int _shmid;
key_t _key;
int _size;
void *_start_mem; // 指向起始虚拟地址空间的指针
std::string _usertype;
};
b)server.cc
cpp
#include "Shm.hpp"
int main()
{
Shm shm(pathname, projid, CREATER);
char *mem = (char *)shm.VirtualAddr();
while (true)
{
printf("%s\n", mem);
sleep(1);
}
return 0;
}
c)client.cc
cpp
#include "Shm.hpp"
int main()
{
Shm shm(pathname, projid, USER);
char *mem = (char *)shm.VirtualAddr();
for (char c = 'A'; c <= 'Z'; c++)
{
mem[c - 'A'] = c;
sleep(1);
}
return 0;
}
效果:


注意: 进程结束了,如果没有删除共享内存,共享内存资源会一直存在。因为共享内存的资源生命周期随内核!
查看共享内存段:ipcs -m

删除共享内存有两种方式:
- 指令删除
powershell
ipcrm -m [shmid]
- 代码删除
cpp
shmctl(shmid, IPC_RMID, null);
2)借助命名管道实现访问控制版的共享内存
a)Comm.hpp
cpp
#pragma once
#include <cstdio>
#include <cstdlib>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
b)Shm.hpp
cpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "Comm.hpp"
const int gdefaultid = -1;
const int gsize = 4096;
const std::string pathname = ".";
const int projid = 0x66;
const int gmode = 0666;
#define CREATER "creater"
#define USER "user"
class Shm
{
private:
// 创建的一定要是一个全新的共享内存
void CreateHelper(int flg)
{
printf("key: 0x%x\n", _key);
// 共享内存的生命周期随内核,不随进程
_shmid = shmget(_key, _size, flg);
if (_shmid < 0)
{
ERR_EXIT("shmget");
}
printf("shmid: %d\n", _shmid);
}
void Create()
{
CreateHelper(IPC_CREAT | IPC_EXCL | gmode);
}
void Get()
{
CreateHelper(IPC_CREAT);
}
void Attach()
{
_start_mem = shmat(_shmid, nullptr, 0);
if ((long long)_start_mem < 0)
{
ERR_EXIT("shmat");
}
printf("attach success\n");
}
void Detach()
{
int n = shmdt(_start_mem);
if (n == 0)
{
printf("detach success\n");
}
}
void Destroy()
{
if (_shmid == gdefaultid)
return;
Detach();
if (_usertype == CREATER)
{
int n = shmctl(_shmid, IPC_RMID, nullptr);
if (n == 0)
{
printf("shmctl delete shm: %d success!\n", _shmid);
}
else
{
ERR_EXIT("shmctl");
}
}
}
public:
Shm(const std::string &pathname, int projid, const std::string &usertype)
: _shmid(gdefaultid), _size(gsize), _start_mem(nullptr), _usertype(usertype)
{
_key = ftok(pathname.c_str(), projid);
if (_key < 0)
{
ERR_EXIT("ftok");
}
if (_usertype == CREATER)
Create();
else if (_usertype == USER)
Get();
else
{
}
Attach();
}
~Shm()
{
if (_usertype == CREATER)
Destroy();
}
void *VirtualAddr()
{
printf("VirtualAddr: %p\n", _start_mem);
return _start_mem;
}
int Size()
{
return _size;
}
void Attr()
{
struct shmid_ds ds;
int n = shmctl(_shmid, IPC_STAT, &ds);
printf("shm_segsz: %ld\n", ds.shm_segsz);
printf("key: 0x%x\n", ds.shm_perm.__key);
}
private:
int _shmid;
key_t _key;
int _size;
void *_start_mem; // 指向起始虚拟地址空间的指针
std::string _usertype;
};
c)Fifo.hpp
cpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "Comm.hpp"
#define PATH "."
#define FILENAME "fifo"
class NameFifo
{
public:
NameFifo(const std::string &path, const std::string &name)
: _path(path), _name(name)
{
_fifoname = _path + "/" + _name;
umask(0);
// 新建管道
int n = mkfifo(_fifoname.c_str(), 0666);
if (n < 0)
{
ERR_EXIT("mkfifo");
}
else
{
std::cout << "mkfifo success" << std::endl;
}
}
~NameFifo()
{
// 删除管道文件
int n = unlink(_fifoname.c_str());
if (n == 0)
{
std::cout << "remove fifo " << _fifoname << " success" << std::endl;
}
else
{
std::cout << "remove fifo " << _fifoname << " failed" << std::endl;
}
}
private:
std::string _path;
std::string _name;
std::string _fifoname;
};
class FileOper
{
public:
FileOper(const std::string &path, const std::string &name)
: _path(path), _name(name), _fd(-1)
{
_fifoname = _path + "/" + _name;
}
~FileOper()
{
}
void OpenForRead()
{
// write方没有open的时候,read方就要在open内部进行阻塞
// 直到有人把管道文件打开了,open才会返回!
_fd = open(_fifoname.c_str(), O_RDONLY);
if (_fd < 0)
{
ERR_EXIT("open");
}
std::cout << "open fifo success" << std::endl;
}
void OpenForWrite()
{
_fd = open(_fifoname.c_str(), O_WRONLY);
if (_fd < 0)
{
ERR_EXIT("open");
}
std::cout << "open fifo success" << std::endl;
}
void Wakeup()
{
// 写入操作
int c = 'c';
int n = write(_fd, &c, 1);
printf("尝试唤醒: %d\n", n);
}
bool Wait()
{
char c;
int number = read(_fd, &c, 1);
if(number>0)
{
printf("醒来: %d\n", number);
return true;
}
return false;
}
void Close()
{
if (_fd > 0)
close(_fd);
}
private:
std::string _path;
std::string _name;
std::string _fifoname;
int _fd;
};
d)server.cc
cpp
#include "Shm.hpp"
#include "Fifo.hpp"
int main()
{
Shm shm(pathname, projid, CREATER);
shm.Attr();
NameFifo fifo(PATH, FILENAME);
FileOper readerfile(PATH, FILENAME);
readerfile.OpenForRead();
char *mem = (char *)shm.VirtualAddr();
while (true)
{
if (readerfile.Wait())
{
printf("%s\n", mem);
}
else
break;
}
readerfile.Close();
std::cout << "server and normal" << std::endl;
return 0;
}
e)client.cc
cpp
#include"Shm.hpp"
#include"Fifo.hpp"
int main()
{
FileOper writerfile(PATH, FILENAME);
writerfile.OpenForWrite();
Shm shm(pathname, projid, USER);
char *mem = (char *)shm.VirtualAddr();
// 读写共享内存,没有使用系统调用
int index = 0;
for (char c = 'A'; c <= 'Z'; c++, index += 2)
{
sleep(1);
mem[index] = c;
mem[index + 1] = c;
sleep(1);
mem[index + 2] = 0;
writerfile.Wakeup();
}
writerfile.Close();
return 0;
}
5、共享内存的优缺点

优点(速度极致)
- IPC 中速度最快: 核心原因是映射到进程地址空间后,数据读写无需经过内核;
- 数据实时可见: 一个进程修改共享内存的数据,另一个进程可直接看到,无数据拷贝;
- 无系统调用开销: 无需通过
read/write等系统调用传递数据,直接操作内存,效率最大化。
缺点(无保护机制)
- 无同步 / 互斥机制: 共享内存本身不提供 "锁" 或 "信号量",多个进程同时读写时,会导致数据覆盖、读取脏数据等不一致问题;
- 无访问控制: 无法限制进程的读写时机 / 顺序,需手动实现保护逻辑。
解决方案
若要保护共享内存数据一致性,可结合命名管道(FIFO) 实现访问控制:
- 同步: 用命名管道的 "阻塞特性" 控制进程读写共享内存的时机(如读端等待写端通知后再读取);
- 互斥: 用命名管道实现 "锁" 的逻辑(同一时间仅允许一个进程操作共享内存)。
二、system V 消息队列
1、理解消息队列
-
本质: 由内核维护的一条消息链表 ,按消息类型(type)组织,消息按优先级或顺序排队,进程可向队列发送 / 接收特定类型的消息,形成有类型、可优先级的异步通信。
-
使用场景:
- 一方通过
msgget()创建或获取消息队列。 - 发送方通过
msgsnd()向队列发送带类型的消息。 - 接收方通过
msgrcv()按类型(或顺序)从队列接收消息,可用于异步任务分发、消息路由、多对一 / 一对多通信等场景。 - 通信结束后,通过
msgctl()删除队列,释放内核资源。
- 一方通过
2、消息队列的特性

- 提供了一种从一个进程向另一个进程发送有类型数据块的方式。
- 每个数据块都被认为有一个类型,接收进程可以根据不同的类型值来选择性地接收数据块。
- 消息队列的生命周期随内核,IPC 资源不会自动清除,必须显式调用
msgctl()或使用命令行工具删除。 - 常用管理命令:
- 查看消息队列资源:
ipcs -q - 删除消息队列资源:
ipcrm -q [msqid]
- 查看消息队列资源:
3、消息队列函数
1)msgget 函数

2)msgsnd 函数

3)msgrcv 函数

4)msgctl 函数

三、system V 信号量
1、理解信号量
- 本质: 信号量本质是一个计数器 ,用来表明临界资源中可用资源的数量,是一种对共享资源的预订机制。
- 使用场景:
- 一方通过
semget()创建或获取信号量集。 - 初始化信号量值(通过
semctl()的SETVAL或SETALL命令)。 - 进程通过
semop()执行P/V操作,申请或释放资源,可用于共享内存保护、临界区互斥、生产者 - 消费者模型等场景。 - 通信结束后,通过
semctl()删除信号量集,释放内核资源。
- 一方通过
2、信号量的操作
- 核心操作(PV 操作):
- P 操作(申请资源):计数器减 1。若结果 > 0,进程继续;若结果≤0,进程阻塞挂起,等待资源释放。
- V 操作(释放资源):计数器加 1,唤醒等待该资源的阻塞进程。
- 二元信号量: 信号量值只有 0 或 1 两种状态,用于实现互斥锁,保证同一时间只有一个进程进入临界区。
3、信号量的特性
- 生命周期: 信号量的生命周期随内核,IPC 资源不会自动清除,必须显式删除。
- 常用管理命令:
- 查看信号量资源:
ipcs -s - 删除信号量资源:
ipcrm -s [semid]
- 查看信号量资源:
4、并发编程

1、核心定义
- 共享资源: 多个执行流(进程 / 线程)能够同时看到和访问的同一份公共资源。
- 临界资源(互斥资源): 系统中一次只允许一个执行流使用的资源。被保护起来的共享资源就是临界资源。
- 临界区: 进程中涉及访问临界资源的代码段。程序代码 = 临界区(访问临界资源) + 非临界区(不访问临界资源)。
2、保护本质
- 对共享资源进行保护,本质是对访问共享资源的代码(临界区)进行保护。
3、保护方式
- 互斥: 任何时刻,只允许一个执行流进入临界区访问资源,防止竞争冲突。
- 同步: 多个执行流访问临界资源时,需要按照预定的顺序进行,保证时序正确性。
4、原子性要求
- 锁本身也是共享资源,在申请锁(加锁)时,操作必须是原子的(要么完全执行,要么完全不执行),才能保证锁的安全。
5、信号量函数
1)semget 函数

2)semop 函数

3)semctl 函数
