【Linux系统编程学习】匿名管道pipe与有名管道fifo
此为牛客Linux C++和黑马Linux系统编程课程笔记。
0. 关于进程通信
Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:
① 管道 (使用最简单)
② 信号 (开销最小)
③ 共享映射区 (无血缘关系)
④ 本地套接字 (最稳定)
1. 匿名管道
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特点:
匿名管道采用了循环队列,可将写指针看作队列头,读指针看作队列尾:
2. pipe函数
Linux中使用pipe函数创建管道:
#include <unistd.h>
int pipe(int pipefd[2]);
功能:
创建一个匿名管道,用于进程间通信。
参数:
int pipefd[2] 这个数组是一个传出参数。
pipefd[0] 对应的是管道的读端
pipefd[1] 对应的是管道的写端
返回值:
成功 0
失败 -1
注意: 管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
当调用pipe后,当前进程的文件描述符表中就已经有两个文件描述符分别指向管道的读端和写端,pipefd[0]和pipefd[1]返回这两个文件描述符。
如下示例程序能够实现:子进程发送数据给父进程,父进程读取到数据输出到终端。
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{// 子进程发送数据给父进程,父进程读取到数据输出int pipefd[2];int ret = pipe(pipefd);if(ret == -1) {perror("pipe error");exit(0);}pid_t pid = fork();if(pid > 0) {char buffer[1024] = {0};read(pipefd[0], buffer, sizeof(buffer)); // 如果管道为空,此处阻塞printf("recieved : %s", buffer);} else if(pid == 0) {char* content = "hello, im child process";write(pipefd[1], content, strlen(content));}return 0;
}
创建管道,使用read和write分别在pipefd[0]中读数据,在pipefd[1]中写数据。
运行结果如下:
可见父进程中收到了子进程中传递的消息。
3. pipe管道的读写特点:
使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作)
1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端
读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。
2.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),而持有管道写端的进程
也没有往管道中写数据,这个时候有进程从管道中读取数据,那么管道中剩余的数据被读取后,
再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。
3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程
向管道中写数据,那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。
4.如果有指向管道读端的文件描述符没有关闭(管道的读端引用计数大于0),而持有管道读端的进程
也没有从管道中读数据,这时有进程向管道中写数据,那么在管道被写满的时候再次write会阻塞,
直到管道中有空位置才能再次写入数据并返回。
总结:
读管道:
管道中有数据,read返回实际读到的字节数。
管道中无数据:
写端被全部关闭,read返回0(相当于读到文件的末尾)
写端没有完全关闭,read阻塞等待
写管道:
管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
管道读端没有全部关闭:
管道已满,write阻塞
管道没有满,write将数据写入,并返回实际写入的字节数
4. 有名管道
5. mkfifo函数
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
- pathname: 管道名称的路径
- mode: 文件的权限 和 open 的 mode 是一样的
是一个八进制的数
返回值:成功返回0,失败返回-1,并设置错误号
6. 有名管道的注意事项:
1.一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道;
2.一个为只写而打开一个管道的进程会阻塞,直到另外一个进程为只读打开管道
读管道:
管道中有数据,read返回实际读到的字节数
管道中无数据:
管道写端被全部关闭,read返回0,(相当于读到文件末尾)
写端没有全部被关闭,read阻塞等待
写管道:
管道读端被全部关闭:进行异常终止(收到一个SIGPIPE信号)
管道读端没有全部关闭:
管道已经满了,write会阻塞
管道没有满,write将数据写入,并返回实际写入的字节数。
7. 使用FIFO实现简单的聊天功能
chatterA.c:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>int main()
{// 第一步,首先判断有名管道是否存在int ret = access("fifo1", F_OK);if(ret == -1) {// 说明管道文件不存在,则创建管道printf("管道不存在,创建管道\n");ret = mkfifo("fifo1", 0664);if(ret == -1) {// 如果创建管道失败perror("mkfifo");exit(0);}}ret = access("fifo2", F_OK);if(ret == -1) {printf("管道不存在,创建管道\n");ret = mkfifo("fifo2", 0664);if(ret == -1) {perror("mkfifo");exit(0);}}// 第二步,以只写的方式打开fifo1,以只读的方式打开fifo2// fifo1管道负责chatter1写chatter2读// fifo2管道负责chatter1读chatter2写int fd1 = open("fifo1", O_WRONLY);if(fd1 == -1) {perror("open");exit(0);}printf("打开fifo1成功,等待写入...\n");int fd2 = open("fifo2", O_RDONLY);if(fd2 == -1) {perror("open");exit(0);}printf("打开fifo2成功,等待读取...\n");// 第三步,循环地往管道fifo1里写入数据char buffer[1024] = {0};while(1) {// 把buffer置空以便重复写入memset(buffer, 0, 1024); // 获取标准输入的数据fgets(buffer, 1024, stdin);ret = write(fd1, buffer, strlen(buffer));if(ret == -1) {perror("write");exit(0);}// 第四步,循环地从管道fifo2中读出数据memset(buffer, 0, 1024);ret = read(fd2, buffer, 1024);if(ret <= 0) {perror("read");exit(0);}printf("chatterB传来消息: %s\n", buffer);}close(fd1);close(fd2);return 0;
}
chatterB:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>int main()
{// 与chatterA完全对称,往fifo2中写,从fifo1中读int ret = access("fifo1", F_OK);if(ret == -1) {printf("管道不存在,创建管道\n");ret = mkfifo("fifo1", 0664);if(ret == -1) {perror("mkfifo");exit(0);}}ret = access("fifo2", F_OK);if(ret == -1) {printf("管道不存在,创建管道\n");ret = mkfifo("fifo2", 0664);if(ret == -1) {perror("mkfifo");exit(0);}}int fd1 = open("fifo1", O_RDONLY);if(fd1 == -1) {perror("open");exit(0);}printf("打开fifo1成功,等待读取...\n");int fd2 = open("fifo2", O_WRONLY);if(fd2 == -1) {perror("open");exit(0);}printf("打开fifo2成功,等待写入...\n");char buffer[1024] = {0};while(1) {memset(buffer, 0, 1024);ret = read(fd1, buffer, 1024);if(ret <= 0) {perror("read");exit(0);}printf("chatterA传来消息: %s\n", buffer);memset(buffer, 0, 1024); fgets(buffer, 1024, stdin);ret = write(fd2, buffer, strlen(buffer));if(ret == -1) {perror("write");exit(0);}}close(fd1);close(fd2);return 0;
}
在两个终端中分别运行程序:
chatterA:
chatterB:
在运行chatterA的终端中输入:hello,im chatterA ,回车
chatterB:
输出了chatterA中传来的信息,同样,在运行chatterB的终端中输入:hello,im chatterB ,回车
chatterA:
【Linux系统编程学习】匿名管道pipe与有名管道fifo相关推荐
- 嵌入式Linux系统编程学习之二常用命令
嵌入式Linux系统编程学习之二常用命令 文章目录 嵌入式Linux系统编程学习之二常用命令 前言 一.常用命令 1.su(用户切换) 2.useradd(添加用户) 3.passwd(修改密码) 4 ...
- linux线程并不真正并行,Linux系统编程学习札记(十二)线程1
Linux系统编程学习笔记(十二)线程1 线程1: 线程和进程类似,但是线程之间能够共享更多的信息.一个进程中的所有线程可以共享进程文件描述符和内存. 有了多线程控制,我们可以把我们的程序设计成为在一 ...
- 嵌入式Linux系统编程学习之一目录结构
嵌入式Linux系统编程学习之一目录结构 文章目录 嵌入式Linux系统编程学习之一目录结构 前言 一.Linux目录结构 前言 Linux目录结构 一.Linux目录结构 /bin:存放Linux的 ...
- linux系统编程学习_(2)进程控制-- fork函数、exec函数族、回收子进程--孤儿进程僵尸进程、wait函数
linux系统编程学习_(2)进程控制-- fork函数.exec函数族.回收子进程–孤儿进程僵尸进程.wait函数 进程控制 fork()函数 创建一个子进程. pid_t fork(void); ...
- 【Linux系统编程学习】Linux进程控制原语(fork、exec函数族、wait)
此为牛客Linux C++和黑马Linux系统编程课程笔记. 1. fork函数 1.1 fork创建单个子进程 #include<unistd.h> pid_t fork(void); ...
- 嵌入式Linux系统编程学习之十八进程间通信(IPC)简介
Linux 下的进程通信手段基本上是从 UNIX 平台上的进程通信手段继承而来的.而对 UNIX 发展做出过重大贡献的两大主力 -- AT&T 的贝尔实验室和 BSD (加州大学伯克利分校 ...
- Linux系统编程学习之《编程前的准备》
在进行Linux系统编程钱,先来看看编程前的准备吧! 先说说我为什么学习Linux系统编程,因为我觉得现在Linux是IT行业的主流,学习一下Linux相关知识,对于学计算机专业的我来说肯定是有必要的 ...
- 【Linux系统编程学习】信号、信号集以其相关函数
此为牛客Linux C++和黑马Linux系统编程课程笔记. 文章目录 0. 信号的概念 1. Linux信号一览表 2. 信号相关函数 3. kill函数 4. raise函数 5. abort函数 ...
- 嵌入式Linux系统编程学习之二十一命名管道(FIFO)
文章目录 前言 一.创建.删除FIFO文件 1. 用函数创建和删除 FIFO 文件 2. 用命令创建和删除 FIFO 文件 二.打开.关闭FIFO文件 三.读写FIFO 前言 无名管道只能在有亲缘 ...
最新文章
- [转] apache2: bad user name ${APACHE_RUN_USER}
- 谈吉日嘎拉的《白话反射技术》及其他(吵架篇)
- 内卷严重?加班多?给几条程序员都适用的建议
- web.xml 中的filter
- Linux做软raid10,51CTO博客-专业IT技术博客创作平台-技术成就梦想
- android多个文件夹压缩,android文件或文件夹压缩
- 第11章[11.6] Ext JS 自行搭建远端库的包升级版本后找不到的问题解决-The following versions are available
- iOS底层探索之类的结构—cache分析(上)
- 基于PC-DIMS脱机软件 的海克斯康三坐标机脱机编程软件手册。
- 数字化定量分析_数字化定量分析
- 2022年度GitHub中文Java项目排行榜Top 10
- android 转发朋友圈,微信怎么转发朋友圈 转发朋友圈方法详细教程
- 面试官:谈谈你对geohash的理解和如何实现附近人功能呢?
- 介绍几种初学者学习电脑入门知识的方法
- 手撕HashMap数据结构(带你逐行阅读源码)
- oracle错误代码大全(超详细)
- FL Studio教程之Fruity Blood Overdrive插件
- 通过PS把月亮装进灯泡里打造创意灯泡月亮
- 全面升级!网易易盾发布设备DNA指纹系统
- 计算机应用基础0039答案,2016秋5205004计算机应用基础-0039【参考答案】
热门文章
- Mycat 安装配置
- 基于Bresenham和DDA算法画线段
- pytorch如何定义损失函数_对比PyTorch和TensorFlow的自动差异和动态模型
- linux 内存使用原理,linux中内存使用原理
- python下载显示文件丢失_Microsoft.PythonTools.resources.dll
- C语言怎么输出百分号%
- java url下载ics_使用Microsoft Graph API处理外部(Internet / .ics)日历URL
- gcc 编译器使用指南
- java 范围搜寻要怎么弄_搜索范围
- scanner close_Java Scanner close()方法与示例