Linux进程间通信(万字详解)—— 匿名管道 | 命名管道 | System V | 共享内存
Posted 张小姐的猫(考研停更)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux进程间通信(万字详解)—— 匿名管道 | 命名管道 | System V | 共享内存相关的知识,希望对你有一定的参考价值。
🌈欢迎来到Linux专栏~~进程通信
- (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort
- 目前状态:大三非科班啃C++中
- 🌍博客主页:张小姐的猫~江湖背景
- 快上车🚘,握好方向盘跟我有一起打天下嘞!
- 送给自己的一句鸡汤🤔:
- 🔥真正的大师永远怀着一颗学徒的心
- 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
- 🎉🎉欢迎持续关注!
文章目录
一. 进程间通信介绍
进程之间会存在特定的协同工作的场景:
- 数据传输:一个进程要把自己的数据交给另一个进程,让其继续进行处理
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
进程间通信的本质就是,让不同的进程看到同一份资源
进程是具有独立性的。虚拟地址空间+页表 保证了进程运行的独立性(进程内核数据结构+进程代码和数据)
进程通信的前提,首先需要让不同的进程看到同一份“内存”(特定的结构组织)
- 这块内存应该属于谁呢?为了维持进程独立性,它一定不属于进程A或B,它属于操作系统。
综上,进程间通信的前提就是:由OS参与,提供一份所有通信进程都能看到的公共资源。
进程间通信的发展
- 管道
- 匿名管道pipe
- 命名管道pipe
- System V标准 进程间通信
- System V 消息队列
- System V 共享内存
- System V 信号量
- POSIX标准 进程间通信(多线程详谈)
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
二. 管道
什么是管道?
- 有入口,有出口,都是单向传输资源的(数据)
所以计算机领域设计者,设计了一种单向通信的方式 —— 管道
🌍匿名管道
众所周知,父子进程是两个独立进程,父子通信也是进程间通信的一种,基于父子间进程通信就是匿名管道。我们首先要对匿名管道有一个宏观的认识
父进程创建子进程,子进程需要以父进程为模板创建自己的files_struct
,而不是与父进程共用;但是struct file这个结构体就不会拷贝,因为打开文件也与创建进程无关(文件的数据不用拷贝)
- 因为左边是进程相关数据结构,右边是文件相关结构
😎匿名管道原理
- 父进程创建管道,对同一文件分别以读&写方式打开
-
父进程
fork
创建子进程
-
因为管道是一个只能单向通信的信道,父子进程需要关闭对应读写端,至于谁关闭谁,取决于通信方向。
于是,通过子进程继承父进程资源的特性,双方进程看到了同一份资源。
😎创建匿名管道pipe
pipe
谁调用就让以读写方式打开一个文件(内存级文件)
#include <unistd.h>
int pipe(int pipefd[2]);
- 参数
pipefd
:输出型参数!通过这个参数拿到两个打开的fd - 返回值:成功返回0;失败返回-1
数组pipefd
用于返回两个指向管道读端和写端的文件描述符:
数组元素 | 含义 |
---|---|
pipefd[0]~嘴巴 | 管道读端的文件描述符 |
pipefd[1] ~ 钢笔 | 管道写端的文件描述符 |
此处提取查一下要用到的函数
man2
是获得系统(linux内核)调用的用法;man 3
是获得标准库(标准C语言库、glibc)函数的文档
//linux中用man可以查哦
#include <unistd.h>
pid_t fork(void);
#include <unistd.h>
int close(int fd);
#include <stdlib.h>
void exit(int status);
下面按照之前讲的原理进行逐一操作:①创建管道 ②父进程创建子进程 ③关闭对应的读写端,形成单向信道
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <string.h>
#include <assert.h>
using namespace std;
int main()
//1.创建管道
int pipefd[2] = 0;
int n = pipe(pipefd); //失败返回-1
assert(n != -1); //只在debug下有效
(void)n; //仅此证明n被使用过
#ifdef DEBUG
cout<< "pipefd[0]" << pipefd[0] << endl; //3
cout<< "pipefd[1]" << pipefd[1] << endl; //4
#endif
//2.创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
//子进程
//3. 构建单向通信的信道
//3.1 子进程关闭写端[1]
close(pipefd[1]);
exit(0);
//父进程
//父进程关闭读端[0]
close(pipefd[0]);
return 0;
在此基础上,我们就要进行通信了,实际上就是对某个文件进行写入,因为管道也是文件,下面提提前查看要用到的函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
返回值:
- 返回写入的字节数
- 零表示未写入任何内容,这里意味着对端进程关闭文件描述符
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
😎demo代码
简单实现了管道通信的demo版本:
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <string.h>
#include <assert.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
//1.创建管道
int pipefd[2] = 0;
int n = pipe(pipefd); //失败返回-1
assert(n != -1); //只在debug下有效
(void)n; //仅此证明n被使用过
#ifdef DEBUG
cout<< "pipefd[0]" << pipefd[0] << endl; //3
cout<< "pipefd[1]" << pipefd[1] << endl; //4
#endif
//2.创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
//子进程 - 读
//3. 构建单向通信的信道
//3.1 子进程关闭写端[1]
close(pipefd[1]);
char buffer[1024];
while(1)
size_t s = read(pipefd[0], buffer, sizeof(buffer)-1);
if(s > 0)
buffer[s] = 0;//因为read是系统调用,没有/0,此处给加上
cout<<"child get a message["<< getpid() << "] 爸爸对你说" << buffer << endl;
//close(pipefd[0]);
exit(0);
//父进程 - 写
//父进程关闭读端[0]
close(pipefd[0]);
string message = "我是父进程,我正在给你发消息";
int count = 0; //计算发送次数
char send_buffer[1024];
while(true)
//3.2构建一个变化的字符串
snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d",message.c_str(), getpid(), count);
count++;
//3.3写入
write(pipefd[1], send_buffer, strlen(send_buffer));//此处strlen不能+1
//3.4 故意sleep
sleep(1);
pid_t ret = waitpid(id, nullptr, 0);
assert(ret != -1);
(void)ret;
return 0;
此处有个问题:为什么不定义一个全局的buffer来进行通信呢?
- 因为有写时拷贝的存在,无法更改通信!
上面的方法就是把数据交给管道,让对方通过管道进行读取
😎匿名管道通信的4种情况
之前父子进程同时向显示器中写入的时候,二者会互斥 —— 缺乏访问控制
而对于管道进行读取的时候,父进程如果写的慢,子进程就会等待读取 —— 这就是说明管道具有访问控制
✨读阻塞:写快,读慢
父进程疯狂的进行写入,子进程隔10秒才读取,子进程会把这10秒内父进程写入的所有数据都一次性的打印出来!
代码如非就是在父进程添加了打印conut,子进程sleep(10),可以自行的在demo代码上添加
父进程写了1220次,子进程一次就给你读完了,读写之间没有关系,这就叫做流式的服务。
也就是管道是面向字节流的,也就是只有字节的概念,究竟读成什么样也无法保证,甚至可能读出乱码,所以父子进程通信也是需要制定协议的,但这个我们网络再细说。。
✨写阻塞:写慢,读快
管道没有数据的时候,读端必须等待:父进程每隔2秒才进行写入,子进程疯狂的读取
✨写端关闭
父进程写入10秒,后把写端fd关闭,读端会怎么样?
- 写入的一方,fd没有关闭,如果有数据就读,没有数据就等
- 写入的一方,fd关闭了,读取的一方,
read
会返回0
,表示读到了文件结尾,退出读端
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <string.h>
#include <assert.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
//1.创建管道
int pipefd[2] = 0;
int n = pipe(pipefd); //失败返回-1
assert(n != -1); //只在debug下有效
(void)n; //仅此证明n被使用过
#ifdef DEBUG
cout<< "pipefd[0]" << pipefd[0] << endl; //3
cout<< "pipefd[1]" << pipefd[1] << endl; //4
#endif
//2.创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
//子进程 - 读
//3. 构建单向通信的信道
//3.1 子进程关闭写端[1]
close(pipefd[1]);
char buffer[1024*8];
while(1)
//sleep(10);//20秒读一次
//写入的一方,fd没有关闭,如果有数据就读,没有数据就等
//写入的一方,fd关闭了,读取的一方,read会返回0,表示读到了文件结尾
size_t s = read(pipefd[0], buffer, sizeof(buffer)-1);
if(s > 0)
buffer[s] = 0;//因为read是系统调用,没有/0,此处给加上
cout<<"child get a message["<< getpid() << "] 爸爸对你说" << buffer << endl;
else if (s == 0)
cout << "write quit(father), me quit!!!" <<endl;
break;
//close(pipefd[0]);
exit(0);
//父进程 - 写
//父进程关闭读端[0]
close(pipefd[0]);
string message = "我是父进程,我正在给你发消息";
int count = 0; //计算发送次数
char send_buffer[1024*8];
while(true)
//3.2构建一个变化的字符串
snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d",message.c_str(), getpid(), count);
count++;
//3.3写入
write(pipefd[1], send_buffer, strlen(send_buffer));//此处strlen不能+1
//3.4 故意sleep
sleep(1);
cout<< count <<endl;
if(count == 5)
cout<< "父进程写端退出" << endl;
break;
close(pipefd[1]);//关闭读端
pid_t ret = waitpid(id, nullptr, 0);
assert(ret != -1);
(void)ret;
return 0;
运行结果如下:
✨读端关闭
读端关闭,写端继续写入,直到OS终止写进程
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
int fd[2] = 0 ;
if (pipe(fd) < 0) //使用pipe创建匿名管道
perror("pipe");
return 1;
pid_t id = fork(); //使用fork创建子进程
if (id == 0)
//child
close(fd[0]); //子进程关闭读端
//子进程向管道写入数据
const char* msg = "hello father, I am child...";
int count = 10;
while (count--)
write(fd[1], msg, strlen(msg));
sleep(1);
close(fd[1]); //子进程写入完毕,关闭文件
exit(0);
//father
close(fd[1]); //父进程关闭写端
close(fd[0]); //父进程直接关闭读端(导致子进程被操作系统杀掉)
int status = 0;
waitpid(id, &status, 0);
printf("child get signal:%d\\n", status & 0x7F); //打印子进程收到的信号
return 0;
运行结果显示,子进程退出时收到的是13号信号
通过kill -l命令可以查看13对应的具体信号
由此可知,当发生情况四时,操作系统向子进程发送的是SIGPIPE
信号将子进程终止的。
🐋总结上述的4中场景:
- 写快,读慢,写满了不能再写了
- 写慢,读快,管道没有数据的时候,读端必须等待
- 写关,读取的一方,
read
会返回0
,表示读到了文件结尾,退出读端 - 读关,写继续写,OS终止写进程 ——
🧐由上总结出匿名管道的5个特点 ——
- 管道是一个单向通信的通信管道,是半双工通信的一种特殊情况
- 管道是用来进行具有血缘关系的进程进行进程间通信 —— 常用于父子通信
- 管道具有通过让进程间协同,提供了访问控制!
- 管道是 面向字节流 —— 协议(后面详谈)
- 管道是基于文件的,管道的声明周期是随进程的
😎管道的大小
管道的容量是有限的,如果管道已满,那么写端将阻塞或失败,那么管道的最大容量是多少呢?
ps:原子性:要么做了,要么不做,没有中间状态
方法1 :man手册查询
然后我们可以使用uname -r
命令,查看自己使用的Linux版本
我使用的是Linux 2.6.11之后的版本,因此管道的最大容量是65536字节
方法二:自行测试
也就是如果读端一直不读取,写端又不断的写入,当管道被写满后,写端进程就会被挂起。据此,我们可以写出以下代码来测试管道的最大容量。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
int fd[2] = 0 ;
if (pipe(fd) < 0) //使用pipe创建匿名管道
perror("pipe");
return 1;
pid_t id = fork(); //使用fork创建子进程
if (id == 0)
//child
close(fd[0]); //子进程关闭读端
char c = 'a';
int count = 0;
//子进程一直进行写入,一次写入一个字节
while (1)
write(fd[1], &c, 1);
count++;
printf("%d\\n", count); //打印当前写入的字节数
close(fd[1]);
exit(0);
//father
close(fd[1]); //父进程关闭写端
//父进程不进行读取
waitpid(id, NULL, 0);
close(fd[0]前言1. 进程间通信的目的 2. 进程间通信的方式
3. 共享内存
总结
前言
每一个进程想要访问物理内存,都是通过访问地址空间中的虚拟地址来进行访问。访问的时候,通过各自的页表结构,查找对应的物理地址并且访问。造成了进程与进程之间的数据独立,虽然有进程间独立性的存在,在进程运行时不会相互干扰。但是造成了进程与进程之间相互协作的时候,没有较好的方法进行数据的共享。
所以在此引入了进程间通信的概念,进程间通信可以将不同进程中的数据通过一定的手段共享给其他进程,来实现进程间的通信。
1. 进程间通信的目的
- 数据传输:一个进程需要将它的数据发送给另一进程
- 资源共享:多个进程之间共享同样的资源
- 通知事件:一个进程需要向另一个或一组进程发送消息,同时它发生了某种事件
- 进程控制:有些进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
2. 进程间通信的方式
2.1 管道
2.1.1 什么是管道
管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
eg:
ps aux | grep xxx
命令解释:
ps a
显示现行终端机下的所有程序,包括其他用户的程序。
ps u
以用户为主的格式来显示程序状况。
ps x
显示所有程序,不以终端机来区分。
grep
用于查找文件里符合条件的字符串。
|
管道符号
所以上述命令的作用是在所有可执行程序中查找指定的程序的运行状态。
所以以上命令管道的作用为:将ps aux
的结果通过管道交给 grep
,将该结果作为grep
的输入数据
2.1.2 管道的本质
管道在内核中是一块缓冲区,供不同的进程进行读写的缓冲区。
管道相当于在内核态中建立一块缓冲区,将结果通过管道返回给用户态。
2.2 匿名管道
2.2.1 匿名管道的接口函数
int pipe(int pipefd[2]);
// 作用:创建一个匿名管道,用于数据的交换
// 参数:pipefd数组,输出型参数,参数由函数内部进行填充
// pipefd[0]: 管道的读端
// pipefd[1]: 管道的写端
// 返回值:
// 0: 创建匿名管道成功
// -1: 创建匿名管道失败
eg: 使用匿名管道进行父子进程间的通信
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main()
int fd[2];
// 创建匿名管道
int ret = pipe(fd);
if(ret < 0)
perror("pipe");
return 0;
pid_t pid = fork();
if (pid < 0)
perror("fork");
return 0;
else if (pid == 0)
// 子进程从管道中读出数据
close(fd[1]);
char buf[1024] = 0 ;
read(fd[0], buf, sizeof(buf) - 1);
printf("child: %s\\n", buf);
else
// 父进程向管道中写入数据
sleep(2);
close(fd[0]);
const char* str = "i am father";
write(fd[1], str, strlen(str));
return 0;
2.2.2 从内核的角度理解管道
2.2.3 匿名管道的特性
- 管道是半双工通信的,并且数据流只能从写端流向读端,同一只能进行写操作或者读操作,两者不能同时进行
- 匿名管道在内核中创建出来的缓冲区没有标识符,导致了其他进程无法直接找到这块缓冲区,但是通过
fork()
函数创建的进程可以通过读写两端的文件描述符进行操作。 - 匿名管道只支持具有亲缘性关系的进程进行进程间通信,在进行父子进程通信的时候,一定要父进程先创建管道,再去创建子进程,此时子进程的文件描述符表才会有匿名管道的读写两端的描述符
- 当文件描述符保持基础属性(阻塞),调用read读空管道时,则read函数会发生阻塞。
- 管道的默认大小为64k
- 当文件描述符保持基础属性时(阻塞),一直调用write将管道写满后,则write函数会发生阻塞
- 管道的生命周期是跟随进程的,进程消失后管道也会消失
- 管道提供字节流服务,描述符的前后两个数据之间是没有明显边界的
- 从
fd[1]
中读取内容的时候,是直接将数据读走了,而不是拷贝其中的数据,在下一次进行读管道内容时,就会发生阻塞 - 在对管道进行读写时,如果读的字节没有超过管道大小,则管道保证读写的原子性,即要么完成读,要么还没有开始。
2.2.4 如何将文件描述符设置为非阻塞
将文件描述符的读或者写设置为非阻塞属性,再读写受到阻塞时,程序则不会等待阻塞结束,而是继续执行下列程序。
函数接口
int fcntl(int fd, int cmd, .../*arg*/);
// 函数功能:
// 1. 查看属性
// 2. 设置为非阻塞属性 O_NONBLOCK
// 参数:
// fd: 文件描述符
// cmd: 告知fcntl函数执行的功能
// F_GETFL: 获取文件描述符属性,arg参数忽略
// F_SETFL: 设置文件描述符属性,使用第三个参数
// 返回值:
// 返回文件描述符的属性。
代码测试
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
int fd[2];
int ret = pipe(fd);
if (ret < 0)
perror("pipe");
return 0;
// 获取读端的属性
int flag = fcntl(fd[0], F_GETFL);
printf("fd[0]的属性为:%d\\n", flag);
// 将读端设置为非阻塞
fcntl(fd[0], F_SETFL, flag | O_NONBLOCK);
flag = fcntl(fd[0], F_GETFL);
printf("fd[0]的属性为:%d\\n", flag);
// 测试读端是否阻塞,此时管道中并无数据
char buf[1024];
read(fd[0], buf, 1023);
printf("buf:%s\\n", buf);
return 0;
2.2.5 匿名管道的非阻塞特性
1.读设置为非阻塞
写不关闭,一直读,读端调用read函数之后,返回值为-1,errno置为EAGAIN
写关闭,一直读,读端read函数返回0,什么都没有读到
2.写设置成非阻塞
读不关闭,一直写,把管道写满之后,再调用write就会返回-1.
读关闭,一直写,写端调用write进行写的时候,就会发生崩溃。本质上是写段收到了SIGPIPE信号,导致了写段的进程崩溃。
代码验证
此段代码只写了读设置为非阻塞的情况,写设置为非阻塞的情况可以稍微修改代码进行验证
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
// 1.创建匿名管道
int fd[2];
int ret = pipe(fd);
if (ret < 0)
perror("pipe");
return 0;
// 2.设置读或写端为非阻塞属性
int flag = fcntl(fd[0], F_GETFL);
fcntl(fd[0], F_SETFL, flag | O_NONBLOCK);
// 3.创建子进程
pid_t pid = fork();
if (pid < 0)
perror("fork");
return 0;
// 4. 父子进程验证阻塞
else if (pid == 0)
// child read fd[0]
char buf[1024] = 0 ;
// 读设置为非阻塞
size_t read_size = read(fd[0], buf, sizeof(buf) - 1);
printf("buf:%s len:%ld\\n", buf, read_size);
else
// father write fd[1]
// 写端不关闭
close(fd[0]);
sleep(10);
return 0;
程序结果
2.3 命名管道
2.3.1 命名管道与匿名管道的区别
命名管道是由标识符确定的管道,其他进程可以通过这个管道的标识符找到该管道,实现不同进程间的通信。
匿名管道则不可以使用标识符进行找到
2.3.2 命名管道的创建
1.命令创建的方式
mkfifo
创建一个命名管道文件,其他进程可以通过这个管道文件进行通信。
// readfifo.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
int fd = open("./fifo", O_RDONLY);
if(fd < 0)
perror("open");
return 0;
char buf[1024] = 0 ;
read(fd, buf, sizeof(buf) - 1);
printf("buf: %s\\n", buf);
return 0;
// writefifo.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
int fd = open("./fifo", O_WRONLY);
if(fd < 0)
perror("open");
return 0;
write(fd, "i am process A", 14);
return 0;
程序验证: 通过writefifo
程序向管道中写入内容,通过readfifo
从管道中读出数据。当从管道中读取数据时,如果管道中没有数据就会变成阻塞状态,直到管道中有了数据程序才会继续运行。
2.函数创建的方式
int mkfifo(const char* pathname, mode_t mode);
// 作用:创建一个命名管道进行通信
// 参数:pathname 要创建命名管道文件的路径
// mode_t 命名管道的权限,8进行数字
代码测试
// writefifo
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
// 创建一个管道文件
mkfifo("fifo", 7777);
int fd = open("./fifo", O_WRONLY);
if(fd < 0)
perror("open");
return 0;
write(fd, "i am write", 10);
return 0;
// readfido
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
int fd = open("./fifo", O_RDONLY);
if(fd < 0)
perror("open");
return 0;
char buf[1024] = 0 ;
read(fd, buf, 1023);
printf("readfifo: %s\\n", buf);
return 0;
2.3.3 命名管道的打开规则
- 如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功 - 如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
3. 共享内存
3.1 什么是共享内存?
- 在物理内存中开辟的一段空间
- 不同的进程通过页表将该空间映射到自己的进程虚拟空间中
- 不同的进程通过操作自己进程虚拟空间中的虚拟地址,来操作共享内存
3.2 使用步骤
1.创建或获取内存
2.附加,将进程附加至共享内存
3.分离,将虚拟地址和物理地址的映射关系,从页表中删除
下文将会根据步骤,介绍详细的接口函数
3.3 接口函数
1. 创建或获取共享内存
int shmget(key_t key, size_t size, int shmflg);
// 参数:
// key_t: 共享内存的标识符
// size: 共享内存的大小
// shmflg: 共享内存的属性信息
// IPC_CREATE: 共享内存不存在,则创建共享内存
// IPC_EXCL | IPC_CREATE: 如果共享内存存在,则报错。如果共享内存不存在,则创建
// 返回值:成功则返回共享内存的操作句柄
2. 附加
void* shmat(int shmid, const void* shamaddr, int shmflg);
// 参数:
// shamid: 共享内存的操作句柄
// shmaddr: 将共享内存附加到共享区的哪一个地址,一般传递NULL,有操作系统分配
// shmflg: SHM_RDONLY 只读属性 0 可读可写
3. 分离
int shmdt(const void* shmaddr);
// 参数:shamat的返回值
4. 控制
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
// 参数:
// shmid: 共享内存操作句柄
// cmd: 告诉shmctl函数完成的事情
// 1.获取内存信息: IPC_SET
// 2.删除共享内存: IPC_RMID
// 3.获取共享内存属性: IPC_STAT
// buf: 共享内存数据结构buf
代码测试
// readSHM.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#define IPC_F 0x1234
int main()
int shmid = shmget(IPC_F, 1024, IPC_CREAT | 0664);
if(shmid < 0)
perror("shmid");
return 0;
void *addr = shmat(shmid, NULL, SHM_RDONLY);
if(addr == NULL)
perror("shmat");
return 0;
printf("%s\\n", (char*)addr);
return 0;
/
// writeSHM.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#define IPC_F 0x1234
int main()
int shmid = shmget(IPC_F, 1024, IPC_CREAT | 0664);
if(shmid < 0)
perror("shmid");
return 0;
void *addr = shmat(shmid, NULL, 0);
if(addr == NULL)
perror("shmat");
return 0;
const char *str = "i am process A";
strncpy((char*)addr, str, strlen(str));
return 0;
结果
3.4 共享内存的特性
- 共享内存是覆盖写的方式,读的时候,访问地址中的内容
- 共享内存的生命周期跟随操作系统
- 一旦共享内存被删除后,其本质共享内存的空间已经被删除了
- 如果在删除时,共享内存附加的进程数量为0,则内核中描述共享内存的结构体也被释放了
- 如果删除时,共享内存的附加进程数量不为0,则会将共享内存的key变为0x00000000,表示当前共享内存不能被其他进程所附加。共享内存的状态被置为destory,共享内存的结构体内部的引用计数一旦为0,则该共享内存结构体被释放。
总结
以上就是所有关于进程间通信的内容,欢迎各位大佬批评指正。
以上是关于Linux进程间通信(万字详解)—— 匿名管道 | 命名管道 | System V | 共享内存的主要内容,如果未能解决你的问题,请参考以下文章
Linux进程间通信(万字详解)—— 匿名管道 | 命名管道 | System V | 共享内存
[OS-Linux]详解Linux的进程间通信1------管道