【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:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384562.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux系统编程学习】信号、信号集以其相关函数

此为牛客Linux C和黑马Linux系统编程课程笔记。 文章目录0. 信号的概念1. Linux信号一览表2. 信号相关函数3. kill函数4. raise函数5. abort函数6. alarm函数7. setitimer函数8. signal函数9. 信号集10. 自定义信号集相关函数11. sigprocmask函数12. sigpending函数13. sigacti…

【Linux系统编程学习】父进程捕获SIGCHLD信号以处理僵尸进程

配合之前说过的sigaction函数和waitpid函数&#xff0c;我们可以解决子进程变成僵尸进程的问题。 先看如下示例程序&#xff1a; #include <sys/time.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> …

【Linux系统编程学习】Linux线程控制原语

此为牛客Linux C课程笔记。 0. 关于线程 注意&#xff1a;LWP号和线程id不同&#xff0c; LWP号是CPU分配时间片的依据&#xff0c;线程id是用于在进程内部区分线程的。 1. 线程与进程的区别 对于进程来说&#xff0c;相同的地址(同一个虚拟地址)在不同的进程中&#xff0c;反…

【Linux网络编程学习】预备知识(网络字节序、IP地址转换函数、sockaddr数据结构)

此为牛客Linux C课程和黑马Linux系统编程笔记。 1. 网络字节序 我们已经知道&#xff0c;内存中的多字节数据相对于内存地址有大端和小端之分。 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分&#xff0c;那么如何定义网络数…

【Linux网络编程学习】socket API(socket、bind、listen、accept、connect)及简单应用

此为牛客Linux C课程和黑马Linux系统编程笔记。 1. 什么是socket 所谓 socket&#xff08;套接字&#xff09;&#xff0c;就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。 一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换…

【Linux网络编程学习】使用socket实现简单服务器——多进程多线程版本

此为牛客Linux C课程和黑马Linux系统编程笔记。 1. 多进程版 1.1 思路 大体思路与上一篇的单进程版服务器–客户端类似&#xff0c;都是遵循下图&#xff1a; 多进程版本有以下几点需要注意&#xff1a; 由于TCP是点对点连接&#xff0c;服务器主进程连接了一个客户端以后…

【Linux网络编程学习】I/O多路复用——select和poll

此为牛客Linux C课程和黑马Linux系统编程笔记。 0. I/O多路复用 所谓I/O就是对socket提供的内存缓冲区的写入和读出。 多路复用就是指程序能同时监听多个文件描述符。 之前的学习中写了多进程和多线程版的简单服务器模型&#xff0c;但是有个问题&#xff1a;每次新来一个客…

【Linux网络编程学习】阻塞、非阻塞、同步、异步以及五种I/O模型

文章目录1. 基本概念1.1 阻塞与非阻塞1.2 同步与异步1.3 为什么没有“异步阻塞”2. 五种IO模型2.1 阻塞 blocking2.2 非阻塞 non-blocking2.3. IO复用&#xff08;IO multiplexing&#xff09;2.4 信号驱动&#xff08;signal-driven&#xff09;2.5 异步&#xff08;asynchron…

STM32时钟树解析

本人之前其实也用STM32做过一些小东西&#xff0c;但因为时钟的初始化一般是直接在SystemInit时钟系统初始化函数里直接配置为72MHz&#xff0c;所以对于STM32的时钟框图并没有怎么理会&#xff0c;今天刚好有空就重新看了一下并写一篇博客记录一下吧&#xff0c;以免以后又忘了…

S3C2440时钟体系

S3C2440在默认情况下&#xff0c;整个系统全靠一个12MHz的外部晶振提供频率来工作运行的&#xff0c;也就是说CPU、内存、UART、ADC等所有需要用到时钟频率的硬件都工作在12MHz下&#xff0c;但是通过查阅芯片手册我们知道CPU时钟最高可为400MHZ&#xff0c;那么怎么设置时钟让…

关于MCU、CPU扩展SDRAM的一个小知识

像上图这种硬件电路图上的16个数据位和我们在初始化SDRAM的时候设置的16位数据位宽是指我们读写SDRAM的时候可以同时读写16个数据位&#xff0c;数据线越多肯定越快&#xff0c;但是数据线也不可能无限增加&#xff0c;我们在程序里是可以读写8位&#xff0c;16位&#xff0c;3…

S3C2440扩展SDRAM

本文主要目的是记录一下S3C2440扩展SDRAM的一些知识&#xff0c;方便以后查阅。 通过查阅手册我们知道&#xff0c;2440有8个可以用来扩展内存的BANK&#xff0c;其中第6和第7还可用来扩展SDRAM 下面我们来看一下2440扩展SDRAM需要设置哪些寄存器。 一、BWSCON寄存器 该寄存器…

汇编语言的相对跳转和绝对跳转以及反汇编代码解析

上图第一行的b1 main为相对跳转&#xff0c;即跳转到pcoffset,其中pc为当前pc值&#xff0c;offset可以理解为偏移地址&#xff0c;也就是根据当前所在地址加上偏移地址实现跳转&#xff0c;为相对跳转。 我们来看看它的反汇编代码 上图清除完bss区后使用b1指令跳转到30000668…

韦东山嵌入式第一期14课第004节_und异常模示程序示例_P笔记

本节课的第一个程序韦老师是想让大家见识一下未定义异常&#xff0c;而第二个程序是对第一个程序进行改进&#xff0c;防止在某些条件下执行不了&#xff0c;下面就来讲一下第2个程序改进了哪些地方并且有什么用。 程序在此路径中&#xff1a;源码文档图片\源码\源码_20180321…

关于NOR FLASH地址左右移的问题

问题引入&#xff1a;不知道你会不会有这样的疑问&#xff1a;为什么在发送解锁命令时&#xff0c;我们不用右移一位&#xff0c;而发送扇区地址时却要右移一位&#xff08;nor_cmd函数内部已经左移一位&#xff09;&#xff0c;这里先补充说明一下说明是cpu角度和nor角度&…

在linux下利用ls命令进行模糊查找

如上图&#xff0c;我们当前路径下有三个文件&#xff0c;分别为helloworld.c以及helloworld和1.c&#xff0c;直接输入命令ls则显示所有文件&#xff0c;我们可以利用ls 加*的方向进行模糊查找。 输入ls 目录名 形式的命令行&#xff0c;则是对该目录名下的文件全部进行显示&a…

Linux下没有包含头文件(不知是哪个)导致编译无法通过的解决心得

最近写程序的时候编译出错了&#xff0c;提示信息为&#xff1a;invalid use of undefined type fb_var_screeninfo。显示根据英文知道是没有定义 fb_var_screeninfo这个类型&#xff0c;明显是缺少了某个头文件&#xff0c;但是缺少哪个头文件以及有什么又快又好的解决方法呢&…

Linux编译程序时加-I指定头文件位置

Linux下编译出现以下错误&#xff0c;错误的原因是在/usr/local/arm/arm-2009q3/bin/../arm-none-linux-gnueabi/libc/usr/include/freetype/config/下找不到ftheader.h&#xff0c;而我到该目录下看&#xff0c;发现路径是这样的rootubuntu:/usr/local/arm/arm-2009q3/arm-non…

关于源文件用不同的编码方式编写,会导致执行结果不一样的现象及解决方法

如果我们编写以下程序&#xff0c;并分别另存为ANSI和UTF-8两种不同的编码方式保存&#xff0c;放到Linux下编译并运行如下图&#xff0c;两端相同的程序以不同的编码方式保存编译后的运行结果不一样&#xff0c;./ansi采用ANSI编码方式&#xff0c;会自动采用GBK方式来保存中文…

arm-linux-gcc静态编译和动态编译的区别

很多教程会提到加上-static是静态编译&#xff0c;但对于新手来说没有用例子来说明可能不太好理解&#xff0c;今天我就介绍一下关于这方面知识的一个例子&#xff1a; 最近在做一个关于freetype字体的东西&#xff0c;需要依赖freetype官方提供的库&#xff0c;我已经把电脑这…