Linux 进程间通信进阶:管道与共享内存详解
Linux 进程间通信主要包含管道和共享内存。匿名管道用于父子进程单向通信,命名管道允许无关进程交互。共享内存通过内核映射实现高速数据共享,但需处理同步与权限。文中详细阐述了 shmget、shmat、shmdt、shmctl 等系统调用的使用,结合 C++ 代码演示了进程池构建与共享内存读写流程,强调了 key 值生成、内存对齐及资源清理的重要性。

Linux 进程间通信主要包含管道和共享内存。匿名管道用于父子进程单向通信,命名管道允许无关进程交互。共享内存通过内核映射实现高速数据共享,但需处理同步与权限。文中详细阐述了 shmget、shmat、shmdt、shmctl 等系统调用的使用,结合 C++ 代码演示了进程池构建与共享内存读写流程,强调了 key 值生成、内存对齐及资源清理的重要性。

进程间通信指的是两个或者多个进程进行信息相互传递的过程。
我们知道:
一个进程想把自己的数据,发送给另一个进程,至少在目前是一件比较困难的事情!
那父进程的全局变量,子进程可以看到不算嘛?不算进程间通信——
一个进程挂掉不会影响另外的进程,就算是父子进程也一样!
为什么要进行进程间通信,实际上就是要回答进程间通信的目的!
进程间通信的本质前提:先让不同的进程,看到同一份资源!
我们后续进行进程间通信的时候,大部分时候,都是想办法看到同一份资源!
要让不同的进程看到同一份资源,OS 必然要提供系统调用!
相对于网络的标准来说,系统的标准就没那么严格了。
在我们的日常生活当中,其实也是有很多标准的,这些标准也不是一开始就可以决定的,有一个发展的过程。
我们总结一下管道的五大特点:
close(wfd),读端会怎么办?&&(并且)close(rfd)。alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/2_12$ ll total 24
drwxrwxr-x 6 alice alice 4096 Feb 13 15:35 ./
drwxrwxr-x 27 alice alice 4096 Feb 12 10:42 ../
drwxrwxr-x 2 alice alice 4096 Feb 12 12:56 1.test/
drwxrwxr-x 2 alice alice 4096 Feb 12 13:23 2.test/
drwxrwxr-x 2 alice alice 4096 Feb 13 21:10 3.process_pool/
drwxrwxr-x 2 alice alice 4096 Feb 13 15:35 4.my_process_pool/
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/2_12$ cd 3.process_pool
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/2_12/3.process_pool$ make
g++ -o process_pool process_pool.cc -std=c++14
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/2_12/3.process_pool$ ll total 100
drwxrwxr-x 2 alice alice 4096 Mar 10 17:43 ./
drwxrwxr-x 6 alice alice 4096 Feb 13 15:35 ../
-rw-rw-r-- 1 alice alice 95 Feb 13 20:52 Makefile
-rwxrwxr-x 1 alice alice 80936 Mar 10 17:43 process_pool*
-rw-rw-r-- 1 alice alice 7258 Feb 14 00:26 process_pool.cc
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/2_12/3.process_pool$ ./process_pool
Usage: ./process_pool process_number alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/2_12/3.process_pool$
我们怎么体现进程间通信的协同?
下面这个函数是子进程的入口函数——
任务是什么?我们以任务码的形式体现——
由此我们形成一张任务表。
至此,有一个任务清单,一个数组,数组下标就是任务。
通道轮询式的被遍历了——
用 Close() 关闭写端文件描述符,用 Wait() 等待子进程、回收僵尸进程——
修改一下代码,运行一下,我们发现这里有一个 Bug!
我们只退出了一个子进程,并没有退出所有的子进程(这里有 10 个),我们查看一下之前写的代码,发现问题就处在这里——
我们只要加一段代码就行——
再运行一下,通道被关闭成功,子进程全部回收——
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FIFO_NAME "fifo"
int main() {
// 创建 FIFO(如果已存在则忽略错误)
if (mkfifo(FIFO_NAME, 0666) == -1) {
perror("mkfifo");
// 如果文件已存在,可以继续,但最好确认是 FIFO
}
printf("Server waiting for client...\n");
int fd = open(FIFO_NAME, O_RDONLY);
if (fd == -1) {
perror("open");
exit(1);
}
printf("Client connected.\n");
char buf[1024];
ssize_t n;
while ((n = read(fd, buf, sizeof(buf)-1)) > 0) {
buf[n] = '\0';
printf("client say# %s", buf);
// 假设消息自带换行,否则可加换行
fflush(stdout);
}
if (n == -1) perror("read");
close(fd);
unlink(FIFO_NAME); // 可选:删除 FIFO
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FIFO_NAME "fifo"
int main() {
printf("Please Enter@");
fflush(stdout); // 确保提示立即显示
char msg[1024];
if (fgets(msg, sizeof(msg), stdin) == NULL) {
perror("fgets");
exit(1);
}
// 以只写方式打开 FIFO(会阻塞直到有读者)
int fd = open(FIFO_NAME, O_WRONLY);
if (fd == -1) {
perror("open");
exit(1);
}
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
all: client server
client: client.cc
g++ -o $@ $^ -std=c++11
server: server.cc
g++ -o $@ $^ -std=c++11
.PHONY: clean
clean:
rm -f client server
像这样一来就可以了——
先打开 ./server,再打开 ./client——
也就是说先打开服务器,再打开客户端。 这样就可以完成毫不相干的进程之间的单向通信了!
#include <iostream>
#include <cstdio> // 混编
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "comm.h"
int main() {
std::cout << "open begin" << std::endl;
// int wfd = open(fifoname.c_str(),O_WRONLY);
int wfd = open("fifo", O_WRONLY);
if (wfd < 0) {
perror("open");
return 1;
}
std::cout << "open end" << std::endl;
std::string outstring;
while (true) {
std::cout << "Please Enter@ ";
std::cin >> outstring;
write(wfd, outstring.c_str(), outstring.size());
// 要不要写\0?不需要写!
}
close(wfd);
return 0;
}
#include <iostream>
#include <cstdio> // 混编
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "comm.h"
int main() {
// 1.创建管道文件
umask(0); // 关闭系统的权限,用我们指定的权限
int n = mkfifo(fifoname.c_str(), 0666); // 系统调用 mkfifo
if (n < 0) {
if (errno != EEXIST) {
perror("mkfifo");
return 1;
}
// 文件已存在,忽略错误,继续执行
}
// 2.打开管道文件
std::cout << "open begin" << std::endl;
int rfd = open(fifoname.c_str(), O_RDONLY);
if (rfd < 0) {
perror("open");
return 2;
}
std::cout << "open end" << std::endl;
char inbuffer[1024];
// 3、进行通信
while (true) {
ssize_t n = read(rfd, inbuffer, sizeof(inbuffer)-1);
if (n > 0) {
inbuffer[n] = 0;
std::cout << "client say# " << inbuffer << std::endl;
} else if (n == 0) { // 写端关闭了
break;
} else {
perror("read");
break;
}
}
// 4.关闭
close(rfd);
// 5.删除管道文件
unlink(fifoname.c_str());
return 0;
}
命名管道的本质其实就是一种符号,让不同的进程找到同一个文件,只需要存在一个 inode 就可以,不用刷新到磁盘。有缓冲区就行,做刷新时看到是以 p 为开头的就不会刷新,等另外的进程来读取。
在使用命名管道的时候,不用担心第一次读的时候读不到东西,在首次打开的时候读端会在 open 阻塞。在 open 的时候就已经进行了让读写同步。server 端变成写端也是一样的,都要保持这个同步的。
如下图所示——
我们也可以把这个例子换成跟网络和数据库关联,并且大家发现这个结构,有点像进程池的,命名管道也可以用来实现进程池。 实现类似于两个进程互传文件的逻辑——
技术上的问题解决了,下一个矛盾就是怎么让更多人用。 怎么解决这个问题呢,定标准! 朴素点的理解:函数是什么,返回值是什么,结构化的字段怎么定义等等。管你是什么系统,都遵守这个标准。用户不管用的啥都只用学一套就行了。
这三种通信技术上在使用层面已经过时了!但是共享内存还是需要讲一下的!信号量(编码工作)会交待原理。消息队列就不讲了——标准在很多地方很类似。 但是现在比较过时,现在也包装的比较好了。现在都可以网络通了,我们就讲讲共享内存就行;信号量只讲原理,线程还会再讲讲;消息队列基本就不讲了。
共享内存有一个非常重要的东西!我们后面细嗦!
让不同的进程看到同一份资源——动态库就是这样共享的!
我们在物理内存中如果也能开辟一块内存空间 整个物理内存的起始地址和大小我就知道了同时映射到进程 A 的共享区,得到一个内存块的起始虚拟地址,我们就可以直接通过虚拟地址对这块内存进行读写了所以进程 B 也可以啊,我也搞一块区域,建议共享内存和虚拟地址空间的映射,都得到了各自的虚拟地址,再加上偏移量就可以访问内存中的任何的区域。我们肯定能做到,动态库不就是共享区这样嘛。我们通过地址空间映射,让不同的进程看到了同一个内存块,这种技术就叫共享内存!
argu,environ 栈 共享内存、内存映射和 共享库位于此处 为堆扩展保留 堆 未初始化数据(bss) 初始化数据 文本(程序代码)
原图如下所示——
文本(程序代码):存放可执行代码,只读。初始化数据:已初始化的全局变量和静态变量。未初始化数据(bss):未初始化的全局变量和静态变量,在程序执行前会被清零。堆:用于动态内存分配(如 malloc),向高地址增长。为堆扩展保留:堆和共享区之间的空闲区域,供堆向上增长。共享内存、内存映射和共享库位于此处:即内存映射区,用于映射共享库、文件映射、匿名映射等,通常从高地址向低地址增长(或固定位置)。栈:存放局部变量、函数调用信息,向低地址增长。argu, environ:命令行参数和环境变量,位于栈的高地址顶部
堆和栈相对而生,动态库映射到共享区就可以被多个进程共享。
堆和栈相对生长(堆向上,栈向下),而动态库被映射到共享内存区,这样它们在物理内存中只需加载一份,多个进程的页表可以映射到同一物理页,从而节省内存并实现代码共享。
操作系统 OS,那是谁让 OS 做的呢,操作系统必然会提供系统调用,我们程序员就可以调用系统调用——所以是用户让操作系统做的!
我们访问库函数也是虚拟地址结合.GOT 表就可以直接访问使用。
需要通信时,不同的进程都想用共享内存通信啊!
肯定有链接信息,我们后面有很多的共享内存结构体对象,就可以用链表啥的组织管理起来了,思路都很类似了,我们后面会介绍的(底层用的数组管理)。
.hpp
我们就想写在一起就可以用.hpp,但是这种无法打包成库了。
编译型语言头源分离——方便把头文件打包成库——有历史原因。
.hpp 也是源文件,只不过把头文件声明、源文件可以在一个文件里面写了。
.hpp 头源混合——不能打包成库了。
联系闭源项目和开源项目。
开源软件 / 项目,把动静态库 + XXX 打包给你。header only:开源的方式,不头源分离了,减少文件量。
写代码工作量小,文件数量少,文件以后多起来可能会有 20 多个,如果头源分离的话就得有 40 多个了,很容易搞混。
header only。我们后面会很多时候选择这种做法,节省时间,文件更少点方便看。
只需要有一边负责创建并删除共享内存,另一边获取就可以了,不能两边都创建,乱套了无法进行 IPC 了。
创建成功了就会返回一个共享内存的标识符,也可以叫句柄,但是跟文件描述符可没有关系联系。这也是这种技术会被边缘化的原因之一,要是能跟文件关联上多好
shmget 怎么知道 shm 存在还是不存在呢?所以共享内存一定要有一个标识 shm 唯一性的标识符!在哪里?在它的结构体里一定有一个唯一标识符的。需要用户设置唯一值,作为 shm 在内核中的唯一值,我们叫做 key 值,这点有点违反我们的直觉
以面向对象的形式——
这两个选项参数分别是:
共享内存的结构体里面一定存在一个标识共享内存唯一性的标识符。
原则上这个键值可以随便写——保证这个键值在系统中唯一——保证不了你就改。
在系统当中有一个接口——
ftok本质上是一个算法说实话这种方式也挺离谱的,我们有时候也难保持一致还要改,所以我们建议使用系统中的一个函数 ftok,提供一个工程路径 (有效的就行),和一个人工写的随便的项目 id。严格讲这个不属于系统调用,其实是一个算法。把路径标识指定文件的 inode number 和项目 ID 结合生成一个唯一值。
这个
key_t 其实就是一个整数。
ipcrm 也是 C 语言写的——key 值是删除不了的(默认是删除不了的,有的可以,但是最普遍的是删除不了的)——要用 id 删除。
控制共享内存——
op
前面设置了
sleep(5);,共享内存自动删除——
暴露和不暴露的代码——
下面这种不是正确的做法——
我们此时发现:
shmid的值是在线性递增的——
操作系统自己也可以调用系统调用,这个挂接的函数第二个参数我们不管,暂时做不到。第三个权限问题,我们也直接设置成 0 就可以了,不用管了。 注意一下返回值,失败返回 -1(强转成 void*),成功给你一个地址,有点像 malloc 的返回值,不过一个在堆上一个在共享区。我如果不想通信我甚至能当 malloc 用,或者给库的来试试看。
shmid_ds结构体——
all:Reader Writer
Reader:Reader.cc
g++ -o$@ $^ -std=c++11
Writer:Writer.cc
g++ -o$@ $^ -std=c++11
.PHONY:clean
clean: rm-f Reader Writer
#include "Shm.hpp"
#include <iostream>
#include <string>
#include "unistd.h"
// Writer -> shm -> Reader
int main(){
// 1.在内核中创建共享内存
Shm shm;
shm.Create(); // 这一步才是创建
sleep(3);
shm.Attach();
shm.Debug();
shm.GetShmAttr();
sleep(5);
shm.Delete();
return 0;
}
#ifndef __SHM_HPP
#define __SHM_HPP
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/shm.h>
#include <string>
const std::string proj_name = "/home"; // 工作路径,可以随机,最好是系统存在的路径
const int proj_id = 0x6666; // 项目 ID
const int g_size = 4096; // g_size:global size
// 转成十六进制
static std::string ToHex(int data){
char hex[64];
snprintf(hex,sizeof(hex),"0x%x",data);
return hex;
}
class Shm{
public:
// 构造函数和析构函数
Shm(int size = g_size):_shmid(-1),_size(size),_key(0){}
~Shm(){}
private:
key_t GetKey(){
_key = ftok(proj_name.c_str(),proj_id);
if(_key <0){perror("ftok");}
return _key;
}
bool CreateCoreHelper(int flags){
// 1.获取 key 值
key_t k = GetKey();
// 2.创建共享内存
_shmid = shmget(k,_size,flags);
if(_shmid <0){perror("shmget");return false;}
return true;
}
public:
// 1.创建
bool Create(){return CreateCoreHelper(IPC_CREAT | IPC_EXCL |0666);}
// 2.获取共享内存
bool Get(){return CreateCoreHelper(IPC_CREAT);// return CreateCoreHelper(0);}
// 3.删除共享内存
bool Delete(){
int n = shmctl(_shmid,IPC_RMID,NULL);
return n <0?false:true;
}
// 4.保存共享内存属性
void GetShmAttr(){
struct shmid_ds ds;
int n = shmctl(_shmid,IPC_STAT,&ds);
if(n <0){perror("shmctl");return;}
std::cout <<"pid: "<<getpid()<< std::endl;// 当前进程的 PID - 显示调用此函数的进程 ID
// 创建共享内存的进程 PID - shm_cpid 是创建该共享内存段的进程 ID
std::cout << ds.shm_cpid << std::endl;
// 共享内存段的大小(字节) - shm_segsz 表示共享内存的大小,单位是字节
std::cout << ds.shm_segsz << std::endl;
// 共享内存的 key 值(十六进制)
// - shm_perm.__key 是共享内存的键值,通过 ToHex 函数转换为十六进制显示
std::cout <<ToHex(ds.shm_perm.__key)<< std::endl;
}
// 挂接
void* Attach(){return shmat(_shmid,nullptr,0);}
// Debug
void Debug(){
std::cout <<"key: "<<ToHex(_key)<< std::endl;
std::cout <<"shmid: "<< _shmid << std::endl;
}
private:
key_t _key;
int _shmid;
int _size;
};
#endif
#include "Shm.hpp"
#include <iostream>
#include "string"
int main(){
Shm shm;
// 这里不能创建共享内存,创建了就不能建立进程间通信的信道了
shm.Get();
shm.Debug();
return 0;
}
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ll total 64
drwxrwxr-x 2 alice alice 4096 Mar 13 19:00 ./
drwxrwxr-x 29 alice alice 4096 Mar 12 22:20 ../
-rw-rw-r-- 1 alice alice 143 Mar 12 23:04 Makefile
-rwxrwxr-x 1 alice alice 17712 Mar 13 19:00 Reader*
-rw-rw-r-- 1 alice alice 350 Mar 12 22:28 Reader.cc
-rw-rw-r-- 1 alice alice 2233 Mar 13 18:58 Shm.hpp
-rwxrwxr-x 1 alice alice 18008 Mar 13 19:00 Writer*
-rw-rw-r-- 1 alice alice 225 Mar 12 22:30 Writer.cc
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Reader
shmget: File exists
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcrm -m7
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcrm -m
ipcrm: option requires an argument -- 'm'
Try 'ipcrm --help'for more information.
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipc -m ^[[A
Command 'ipc' not found, but there are 25 similar ones.
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcrm -m^C
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Writer
key: 0x66020002
shmid: 8
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x66020002 8 alice 040960 alice
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -l
------ Messages Limits --------
max queues system wide =32000
max size of message (bytes)=8192
default max size of queue (bytes)=16384
------ Shared Memory Limits --------
max number of segments =4096
max seg size (kbytes)=18014398509465599
max total shared memory (kbytes)=18446744073709551612
min seg size (bytes)=1
------ Semaphore Limits --------
max number of arrays =32000
max semaphores per array =32000
max semaphores system wide =1024000000
max ops per semop call =500
semaphore max value =32767
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x66020002 8 alice 040960 alice
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Writer
key: 0x66020002
shmid: 8
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x66020002 8 alice 040960 alice
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x66020002 8 alice 040960 alice
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x66020002 8 alice 040960 alice
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcrm -M 0x66020002
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ make clean
rm-f Reader Writer
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ make
g++ -o Reader Reader.cc -std=c++11
g++ -o Writer Writer.cc -std=c++11
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Writer
key: 0x66020002
shmid: 9
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Reader
^C
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Writer
key: 0x66020002
shmid: 10
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Writer
key: 0x66020002
shmid: 10
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Reader
shmget: File exists
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Reader
shmget: File exists
^[[A^C
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x66020002 10 alice 66640960 alice
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ make clean
rm-f Reader Writer
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ make
g++ -o Reader Reader.cc -std=c++11
g++ -o Writer Writer.cc -std=c++11
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Reader
shmget: File exists
key: 0x66020002
shmid: -1
shmctl: Invalid argument
^C
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcrm -M 0x66020002
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Reader
key: 0x66020002
shmid: 11
pid: 180681018068104096
0x66020002
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Writer
key: 0x66020002
shmid: 12
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ipcrm -M 0x66020002
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Writer
key: 0x66020002
shmid: 13
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ ./Reader
shmget: File exists
key: 0x66020002
shmid: -1
shmctl: Invalid argument
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ man3 shmget
No manual entry for shmget in section 3
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ man2 shmget
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ man2 ftok
No manual entry for ftok in section 2
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$ man3 ftok
alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/3_12_shm$
all:Reader Writer
Reader:Reader.cc
g++ -o$@ $^ -std=c++11
Writer:Writer.cc
g++ -o$@ $^ -std=c++11
.PHONY:clean
clean: rm-f Reader Writer
#include "Shm.hpp"
#include <iostream>
#include <string>
#include "unistd.h"
// 信号
#include <signal.h>
// Writer -> shm -> Reader
int main(){
// 1.在内核中创建共享内存
Shm shm;
shm.Create(); // 这一步才是创建
sleep(100);
char* addr =(char*)shm.Attach();
buffer_t *shm_addr =(buffer_t*)addr;
int old = shm_addr->count;
while(true){
if(old != shm_addr->count){
std::cout <<"count : "<< shm_addr->count << std::endl;
std::cout <<"data : "<< shm_addr->buffer << std::endl;
old = shm_addr->count;
}
usleep(50000);
if(shm_addr->count >=26)break;
}
// sleep(3);
// shm.Attach();
// shm.Debug();
// shm.GetShmAttr();
// sleep(5);
shm.Detach();
shm.Delete();
return 0;
}
#ifndef __SHM_HPP
#define __SHM_HPP
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/shm.h>
#include <string>
const std::string proj_name = "/home"; // 工作路径,可以随机,最好是系统存在的路径
const int proj_id = 0x6666; // 项目 ID
const int g_size = 4096; // g_size:global size
// 转成十六进制
static std::string ToHex(int data){
char hex[64];
snprintf(hex,sizeof(hex),"0x%x",data);
return hex;
}
class Shm{
public:
// 构造函数和析构函数
Shm(int size = g_size):_shmid(-1),_size(size),_key(0){}
~Shm(){}
private:
key_t GetKey(){
_key = ftok(proj_name.c_str(),proj_id);
if(_key <0){perror("ftok");}
return _key;
}
bool CreateCoreHelper(int flags){
// 1.获取 key 值
key_t k = GetKey();
// 2.创建共享内存
_shmid = shmget(k,_size,flags);
if(_shmid <0){perror("shmget");return false;}
return true;
}
public:
// 1.创建
bool Create(){return CreateCoreHelper(IPC_CREAT | IPC_EXCL |0666);}
// 2.获取共享内存
bool Get(){return CreateCoreHelper(IPC_CREAT);// return CreateCoreHelper(0);}
// 3.删除共享内存
bool Delete(){
int n = shmctl(_shmid,IPC_RMID,NULL);
return n <0?false:true;
}
// 4.保存共享内存属性
void GetShmAttr(){
struct shmid_ds ds;
int n = shmctl(_shmid,IPC_STAT,&ds);
if(n <0){perror("shmctl");return;}
std::cout <<"pid: "<<getpid()<< std::endl;// 当前进程的 PID - 显示调用此函数的进程 ID
// 创建共享内存的进程 PID - shm_cpid 是创建该共享内存段的进程 ID
std::cout << ds.shm_cpid << std::endl;
// 共享内存段的大小(字节) - shm_segsz 表示共享内存的大小,单位是字节
std::cout << ds.shm_segsz << std::endl;
// 共享内存的 key 值(十六进制)
// - shm_perm.__key 是共享内存的键值,通过 ToHex 函数转换为十六进制显示
std::cout <<ToHex(ds.shm_perm.__key)<< std::endl;
}
// 挂接
void* Attach(){return shmat(_shmid,nullptr,0);}
// Detach:去关联
void Detach(){
int n = shmdt(_start); // 返回值自己设置
(void)n;
}
// Debug
void Debug(){
std::cout <<"key: "<<ToHex(_key)<< std::endl;
std::cout <<"shmid: "<< _shmid << std::endl;
}
private:
key_t _key;
int _shmid;
int _size;
void*_start;
};
// 重定义
typedef struct data{
int count;
char buffer[26*2];
}buffer_t;
#endif
#include "Shm.hpp"
#include <iostream>
#include <string>
#include <string.h>
Shm shm;
class Init// 定义结构体
{
public:
Init(){
shm.Get();
addr =(char*)shm.Attach();
std::cout <<"addr: "<<ToHex((long long)addr)<< std::endl;
}
// 析构函数
~Init(){
shm.Detach();
}
char* Addr(){return addr;}
public:
char* addr;
};
Init init;
int main(){
std::cout <<"test Begin..."<< std::endl;
buffer_t *shm =(buffer_t*)init.Addr();
shm->count =0;
memset(shm->buffer,0,4096);
char ch ='A';
for(int i =0;i <26*2;i +=2,ch++){
// ? shm->buffer[i]= ch;
// ?usleep(2000000);
shm->buffer[i +1]= ch;
usleep(7000000);
// ? shm->count++;
usleep(7000000);
sleep(1);
}
return 0;
}
共享内存映射到虚拟地址空间的时候,由于虚拟地址空间使用情况不同,两个进程的这个地址一样或者不一样都很正常,反正都在堆栈之间。
因为创建共享内存的过程是 OS 创建的,所以 OS 必然要提供系统调用。 系统设置 key 的无法和目标进程进行进程间通信,变成了'鸡生蛋'问题,所以得由程序员约定 key 值。 key 值只在内核中使用。 共享内存可以挂接到虚拟地址空间里。 共享内存的生命周期随内核,不是随进程!
共享内存是可以同时存在多份的!需要先描述再组织,OS 要管理共享内存。
上文中已经获取了共享内存属性、把共享内存挂接到虚拟地址空间,挂接到虚拟地址空间之后会返回一个虚拟地址空间的起始地址——我们可以打印查看一下 addr(起始地址),看看地址被映射到了什么地方。
创建共享内存成功,attach——
这里 addr 是有精度损失的!addr 的类型是 int,我们转成 long long:
我们运行一下,发现:
我们也可以去验证一下,看看这个 addr 的地址是不是在堆栈之间。
这样我们就把共享内存创建、去关联、删除的系统封装做好了。
作为使用共享内存的一方,只需要获取、关联、去关联(从虚拟地址空间剥离下去):
而读取的一方:
监控开起来,我们可以观察挂接从无到有、从有到无的变化,至此我们就可以完成对共享内存的管理。
我们使用共享内存的过程中,通信了吗?
我们之前大量的时间都花在让不同的进程看到同一份资源! 为什么这么麻烦呢?因为进程具有独立性!所以要做很多准备工作!
我们可以把共享内存做得更好玩一点,写端向往里写'AA''BB'CC'这样的内容,读端可以让它 while(true) 一直读取共享内存,或者也可以做得更好玩一点 比如头四个字节作为约定,从 4 字节的位置往后写(前 4 字节写对数):
共享内存,共享结构体变量,用指针强转成我们想要的数据类型,buffer_t(typedef 出来的数据类型)——对共享内存可以全部进行定义,设置成 4096:
一方直接写入的数据,另一方是直接看到了!!!
共享内存直接属于映射方!双方都会看到同一份资源! 以前 A 和 B 通信要进行两次拷贝,先拷贝给内核,再拷贝给 B,这里只要进行一次拷贝!
结论 2:共享内存是进程间通信中速度最快的,没有之一! 不需要使用系统调用,而且还可以减少拷贝次数!速度是进程间通信中速度最快的! 把内存块映射到地址空间,对方立马就看到了。
模拟一下(今天还做不到)让这三个结构体变量同时
我们修改一下代码,效果不够明显,我们多复制几台机器,搞两个(多个)写端——
上下对应,明显感觉到有多个写端的时候,另一个写端写的数据被覆盖或者清空:
出现了覆盖和清空问题,我们可由此得出结论 3.
大小设置建议设置为 4096 的整数倍。
底层操作系统是按 4096 对齐向上申请的,换言之设置 4097,有 4095 的空间被浪费了。 万一访问越界了,但是因为申请了 4096*2,出错了也不知道,你会不会怪操作系统?

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online