Linux进程通信——匿名管道、命名管道、管道的特性和共享内存
Linux进程通信——匿名管道、命名管道、管道的特性和共享内存
- 一、管道
- 1.1 什么是管道?
- 1.2 匿名管道
- <1> 匿名管道参数说明
- <2> fork共享管道原理
- <3> 站在文件描述符角度-深度理解管道
- <4> 管道读写规则
- <5> 管道的特性与特点总结
- 1.3 命名管道
- <1> 创建一个命名管道
- <2> 匿名管道与命名管道的区别
- <3> 命名管道的打开规则
- <4> 用命名管道实现server&clinet通信
- 二、共享内存
- 2.1 共享内存示意图
- 2.2 共享内存函数
- <1> shmget函数(创建共享内存)
- <2> shmat函数(关联共享内存)
- <3> shmdt函数(取消关联)
- <4> shmctl函数(删除共享内存)
- 2.3 用共享内存实现server&client通信
- <1> Makefile
- <2> comm.h
- <3> server.c
- <4> client.c
- <5> 结果
一、管道
1.1 什么是管道?
- 管道是Unix中最古老的进程间通信的形式。
- 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
- 管道的本质就是一块缓冲区
1.2 匿名管道
<1> 匿名管道参数说明
#include <unistd.h>
int pipe(int fd[2]);
功能:创建一无名管道原型
参数
fd:文件描述符数组其中fd[0]表示读端, fd[1]表示写端(我们可以将0看作一张嘴代表读,1看作一支笔代表写)
返回值:成功返回0,失败返回错误代码
上图可以更好的帮助理解pipe函数的功能,当调用pipe函数时,向系统传递一个fd文件描述符数组,其中fd[1]对应写端,将数据塞入管道,fd[0]代表读端,从管道中读取数据
#include<stdio.h> #include<unistd.h>int main() {int fd[2] = {0};int ret = pipe(fd);printf("ret:%d\n",ret);printf("fd[0]:%d\n",fd[0]);printf("fd[1]:%d\n",fd[1]);return 0; }
<2> fork共享管道原理
首先我们先了解管道的两个特性:
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
- 管道是半双工的,也就是说管道只能进行单向通信,即只能一端写一端读,但我们调用fork函数时,如果父进程写入i am father,那么父进程就需要关闭读端,而子进程读取父进程写入的信息时,子进程就需要关闭写端,如下图所示
代码实例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>int main()
{int pipefd[2] = {0};int ret = pipe(pipefd);if(ret==-1){perror("pipe");return 1;}pid_t id = fork();if(id < 0){perror("fork");return 1;}//父进程读取if(id > 0){//父进程关闭写文件描述符close(pipefd[1]);char buf[64];while(1){ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);if(s > 0){buf[s] = 0;printf("father get a msg:%s\n",buf);sleep(1);} }}//子进程写入i am childif(id == 0){//子进程关闭读文件描述符close(pipefd[0]);const char *msg = "i am child";while(1){write(pipefd[1],msg,strlen(msg));sleep(1);}}return 0;
}
结果
上述程序子进程每次向管道写入信息,父进程从管道读取并打印
<3> 站在文件描述符角度-深度理解管道
父进程调用pipe()创建管道,假设系统分配文件描述符3给fd[0]用于读,文件描述符4给fd[1]用于写
父进程调用fork()函数创建子进程,子进程具有和父进程同样的数据,文件描述符的指向也是相同的
但是管道具有半双工特征,即只能一端读一端写,因此父子进程需要关闭各自不需要的文件描述符,假设父进程写子进程读,那么父进程关闭fd[0]读端,子进程关闭fd[1]写端
<4> 管道读写规则
当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h>int main() {int pipefd[2] = {0};int ret = pipe(pipefd);if(ret==-1){perror("pipe");return 1;}pid_t id = fork();if(id < 0){perror("fork");return 1;}//父进程读取if(id > 0){//父进程关闭写文件描述符close(pipefd[1]);char buf[64];while(1){ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);if(s > 0){buf[s] = 0;printf("father get a msg:%s\n",buf);} }}//子进程写入i am childif(id == 0){//子进程关闭读文件描述符close(pipefd[0]);const char *msg = "i am child";int count = 0;while(1){write(pipefd[1],msg,strlen(msg));printf("write a msg:%d\n",count++);sleep(5);}}return 0; }
当管道满的时候
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h>int main() {int pipefd[2] = {0};int ret = pipe(pipefd);if(ret==-1){perror("pipe");return 1;}pid_t id = fork();if(id < 0){perror("fork");return 1;}//父进程读取if(id > 0){//父进程关闭写文件描述符close(pipefd[1]);char buf[64];while(1){ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);if(s > 0){buf[s] = 0;printf("father get a msg:%s\n",buf);sleep(5);} }}//子进程写入i am childif(id == 0){//子进程关闭读文件描述符close(pipefd[0]);const char *msg = "i am child";int count = 0;while(1){write(pipefd[1],msg,strlen(msg));printf("write a msg:%d\n",count++);}}return 0; }
如果所有管道写端对应的文件描述符被关闭,则read返回0
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h>int main() {int pipefd[2] = {0};int ret = pipe(pipefd);if(ret==-1){perror("pipe");return 1;}pid_t id = fork();if(id < 0){perror("fork");return 1;}//父进程读取else if(id > 0){//父进程关闭写文件描述符close(pipefd[1]);char buf[64];while(1){ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);if(s > 0){buf[s] = 0;printf("father get a msg:%s\n",buf);sleep(1);} printf("father get a msg:%d\n",s);sleep(1);}}//子进程写入i am childelse{//子进程关闭读文件描述符close(pipefd[0]);const char *msg = "i am child\n";int count = 0;while(1){write(pipefd[1],msg,strlen(msg));printf("write a msg:%d\n",count++);//读10次后关闭写端if(count == 10){close(pipefd[1]);break;}}exit(2);}return 0; }
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程 退出
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
<5> 管道的特性与特点总结
管道道的四个特性:
- 如果写端不关闭文件描述符且不写入,读端可能需要长时间阻塞;读取条件不满足时,读取端就要被阻塞(管道为空);
- 当我们实际再进行写入时,如果写入条件不满足我们写入端就要进行阻塞(管道满了);
- 如果写端不但不写入还关闭文件描述符,读端读取完数据后就会读到文件结尾;
- 如果读端关闭,写端进程可能在后续会被进程杀掉。
管道特点:
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创
建,然后该进程调用fork,此后父、子进程之间就可应用该管道。管道提供流式服务
一般而言,进程退出,管道释放,所以管道的生命周期随进程,管道文件只是标识,删除后依然可以通信
一般而言,内核会对管道操作进行同步(没有数据读阻塞,缓冲区写满写阻塞)与互斥
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
1.3 命名管道
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件
<1> 创建一个命名管道
命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
mkfifo filename
命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename, mode_t mode);
<2> 匿名管道与命名管道的区别
匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用open
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完 成之后,它们具有相同的语义。
<3> 命名管道的打开规则
- 如果当前打开操作是为读而打开FIFO时
- O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
- O_NONBLOCK enable:立刻返回成功
- 如果当前打开操作是为写而打开FIFO时
- O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
- O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
<4> 用命名管道实现server&clinet通信
构建Makefile文件
.PHONY:all all:server client client:client.cgcc -o $@ $^ server:server.cgcc -o $@ $^ .PHONY:clean clean:rm client server fifo
服务器代码
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h>#define FIFO_FILE "./fifo"int main() {umask(0);if(-1 == mkfifo(FIFO_FILE,0666)){perror("mkfifo");return 1;}int fd = open(FIFO_FILE, O_RDONLY);if(fd < 0){perror("open");return 1;}else{while(1){char buf[1024];ssize_t s = read(fd,buf,sizeof(buf)-1);if(s > 0){buf[s] = 0;printf("#############################\n");printf("client#:%s\n",buf);}else{close(fd);printf("server offline!\n");break;}}}return 0; }
客户端代码
#include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h>#define FIFO_FILE "./fifo"int main() {int fd = open(FIFO_FILE,O_WRONLY);if(fd < 0){perror("open");return 1;}else{while(1){printf("Please Input Your Message:");fflush(stdout);char msg[1024];//从键盘读取信息ssize_t s = read(0,msg,sizeof(msg)-1);if(s > 0){msg[s] = 0;write(fd,msg,strlen(msg));}}}return 0; }
通过命名管道可以发现管道的本质就是一块缓存
二、共享内存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到 内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
2.1 共享内存示意图
进程间能够实现通信必然需要看到同一份资源,而共享内存就是通过让进程A和B能够同时看到同一块物理内存而实现的进程间通信。而共享内存是将同一块物理内存映射到各个进程虚拟地址空间,可以直接通过虚拟地址访问,相较于其它方式少了两步内核态与用户态之间的数据拷贝因此速度最快,对于一份数据想要通过A传递给B,只要拷贝到进程A的地址空间,共享内存再将这份资源拷贝过来,然后再拷贝给进程B,这样减少了诸多的步骤就可以做到高速高效了。
2.2 共享内存函数
<1> shmget函数(创建共享内存)
<2> shmat函数(关联共享内存)
说明
<3> shmdt函数(取消关联)
<4> shmctl函数(删除共享内存)
2.3 用共享内存实现server&client通信
<1> Makefile
.PHONY:all
all:client serverclinet:client.cgcc -o $@ $^
server:server.cgcc -o $@ $^.PHONY:clean
clean:rm client server
<2> comm.h
#pragma once
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>#define PATHNAME "."
#define PROJ_ID 0x6666#define SIZE 4096
<3> server.c
#include"comm.h"int main()
{//获取一个唯一标识内存的key值key_t key = ftok(PATHNAME, PROJ_ID);if(key < 0){perror("ftok");return 1;}//创建共享内存int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);//关联共享内存char * addr = shmat(shmid, NULL, 0);sleep(2);int count = 0;while(count++ < 26){printf("client#%s\n",addr);sleep(1);}//取消共享内存shmdt(addr);sleep(5);//删除shmctl(shmid,IPC_RMID,NULL);return 0;
}
<4> client.c
#include"comm.h"int main()
{//获取一个唯一标识内存的key值key_t key = ftok(PATHNAME, PROJ_ID);if(key < 0){perror("ftok");return 1;}//创建共享内存int shmid = shmget(key,SIZE,0);//关联共享内存char * addr = shmat(shmid, NULL, 0);int i = 0;while(i < 26){addr[i] = 'A' + i;i++;addr[i] = 0;sleep(1);}//取消共享内存shmdt(addr);sleep(5);return 0;
}
<5> 结果
Linux进程通信——匿名管道、命名管道、管道的特性和共享内存相关推荐
- Linux进程通信(一)——pipe管道
本章内容 采用pipe管道如何进行进程之间的通信 pipe管道进程通信的规则和限制 Linux中pipe管道的实现机制和管理pipe管道的结构体 什么是进程通信 进程通信就是两个进程之间进行数据交换, ...
- 【操作系统实验】Linux进程通信—共享内存通信、管道通信
Linux进程通信-共享内存通信.管道通信 一.实验目的: 二.实验题目: 1. 试设计程序利用共享内存完成如下进程通信 1.shmget函数 2.shmat函数 3.shmdt函数 4.shmctl ...
- Linux进程通信的四种方式——共享内存、信号量、无名管道、消息队列|实验、代码、分析、总结
Linux进程通信的四种方式--共享内存.信号量.无名管道.消息队列|实验.代码.分析.总结 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须 ...
- linux进程管道通信缺点,Linux进程通信(IPC)的方式详解
前言:Linux进程通信的方式 什么是进程通信?进程通信是指进程之间交换信息 进程通信方式共有6种: 管道(pipe),包括流管道(s_pipe)和有名管道(named pipe) 信号(signal ...
- Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存
Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存 参考:<linux编程从入门到精通>,<Linux C程序设计大全>,<unix环境高级编程> ...
- linux 进程通信机制,LINUX内核进程高效通信机制研究
摘要:进程间的通信是多任务.多用户操作系统必须考虑的重大问题,Linux继承了Unix的基本设计思想,其安全性和稳定性得到了人们的认可,但随着其应用领域的不断拓展,其通信机制已经不能满足用户的需求.本 ...
- linux进程通信system v,【linux高级程序设计】(第十一章)System V进程间通信 4
共享内存 共享内存主要用于实现进程间大量数据传输. 共享内存的数据结构定义: 系统对共享内存的限制: 共享内存与管道的对比: 可以看到,共享内存的优势: 1.共享内存只需复制2次,而管道需要4次 2. ...
- Linux进程通信的试验
1.实验目的 1.1了解Linux进程通信的概念. 1.2 掌握Linux进程通信的常用方法. 2.实验内容 创建admin用户,密码也是admin. 用admin登陆后,创建src目录,所有的源代码 ...
- linux进程内存结构体,Linux下网络编程(3)——进程间通信(IPC),共享内存,传递结构体数据...
1. 前言 多进程,总有一些交互要做,不可能各自独立. 这里是最简单的例子.一个写入,一个读取. 一个建立共享内存,写入数据. 一个读取共享内存的内容. 2. 简单例子代码 twriter.cpp 代 ...
最新文章
- 12 集成测试方法之大棒集成方法
- 手把手教你实现PySpark机器学习项目——回归算法
- ZooKeeper客户端ZKClient使用
- zookeeper C API
- android开发(50) Android透明状态栏。适用于 4.4 以上及 5.0以上设备
- unity 让一个数按一秒累加_万物皆数—深挖UWA Benchmark之渲染篇
- [MyBatisPlus]常用注解_@TableName_@TableId_@TableField_@TableLogic通过全局配置配置主键生成策略
- php4和php5的区别,什么是PHP 4和PHP 5之间的区别是什么-php是什么文件
- Kaggle新上比赛:地震图像的盐体分割
- 贾跃亭申请破产前收入曝光:还是厉害!
- 各大linux发行版安装宝塔桌面脚本
- python for a,b in c
- oracle like 前缀,algorithm – 在ORACLE中搜索最长前缀的最快方法
- Python 学习笔记 - RabbitMQ
- python语言程序设计教程课后答案刘卫国_Visual FoxPro程序设计教程(主编:刘卫国 第三版)1-5课后答案...
- Windows安装curl
- 新手小白学JAVA_IDEA修改主题 设置背景图片
- AbstractQueuedSynchronizer浅析
- 传奇 SF 开服架设流程详细介绍:
- java实现在线预览的功能(一)word转html