【Linux 进程间通讯 管道】使用Linux管道进行linux进程间通信

简介: 【Linux 进程间通讯 管道】使用Linux管道进行linux进程间通信

管道的概念


管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。

有如下特质:

1. 其本质是一个伪文件(实为内核缓冲区)

2. 由两个文件描述符引用,一个表示读端,一个表示写端。

3. 规定数据从管道的写端流入管道,从读端流出。

管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。

管道的局限性:

① 数据自己读不能自己写。

② 数据一旦被读走,便不在管道中存在,不可反复读取。

③ 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。

④ 只能在有公共祖先的进程间使用管道。

常见的通信方式有,单工通信、半双工通信、全双工通信

管道示图


PIPE(无名管道)


/*
参数:
pipefd[0]:为读而打开
pipefd[1]:为写而打开
pipefd[1]的输出是pipefd[0]的输入.
返回值:
         若成功,返回0
         若出错,返回-1.并设置errno。
*/
#include <unistd.h>
 
int pipe(int pipefd[2]);//进程要关闭读或写端取决于数据的流向。

① 读管道:

1. 管道中有数据,read返回实际读到的字节数。

2. 管道中无数据:

(1) 管道写端被全部关闭,read返回0 (读到文件结尾)

(2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)

② 写管道:

1. 管道读端全部被关闭, 进程异常终止(收到信号SIGPIPE)(也可使用捕捉SIGPIPE信号,使进程不终止)

2. 管道读端没有全部关闭:

(1) 管道已满,write阻塞。

(2) 管道未满,write将数据写入,并返回实际写入的字节数。


FIFO(有名管道)


/*
FIFO特殊文件类似于管道,除了创建它以不同的方式。 
而不是匿名通信通道,FIFO特殊文件被输入到文件系统中调用mkfifo()。
一旦你以这种方式创建一个FIFO专用文件,任何进程可以打开它进行阅读或写作,就像普通人一样文件。 
但是,它必须在两端同时打开您可以继续执行任何输入或输出操作。
开盘用于读取正常块的FIFO,直到某个其他进程打开相同的FIFO用于写入,反之亦然。 
*/
#include <sys/types.h>
#include <sys/stat.h>
 
int mkfifo(const char *filename, mode_t mode);//创建一个名为pathname的FIFO特殊文件。
 
/*
  函数的运行方式与mkfifo()完全相同,除了这里描述的差异。
  如果路径名中给出的路径名是相对的,则会被解释相对于文件描述符dirfd引用的目录
(而不是相对于当前工作目录的调用进程,如同相对路径名的mkfifo()所做的那样)。
 如果pathname是relative,dirfd是特殊值AT_FDCWD,那么路径名相对于当前工作目录进行解释调用进程(如mkfifo())。
 如果pathname是绝对的,那么dirfd将被忽略。
*/
int mkfifoat(int dirfd, const char *pathname, mode_t mode);
 

FIFO参数说明

filname:文件名

mode:指定FIFO的权限。 它被进程修改 mask以通常的方式:创建的文件的权限是(模式&〜umask)。

注:

1、就是程序不能以O_RDWR(读写)模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程就会读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递。

2、就是传递给open调用的是FIFO的路径名,而不是正常的文件。(如:const char *fifo_name = "/tmp/my_fifo"; )

3、第二个参数中的选项O_NONBLOCK,选项O_NONBLOCK表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的。

FIFO的打开规则

  • 如果以只读方式(O_RDONLY)打开的FIFO文件时

   若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);

   如果是非阻塞的,则即使没有其他进程以写方式打开同一个FIFO文件,都将立即成功返回(当前打开操作没有设置阻塞标志)。

  • 如果以只写方式(O_WRONLY)打开的FIFO文件时

  如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);

  如果是非阻塞的,立即返回ENXIO错误(当前打开操作没有设置阻塞标志) 但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。

总结:一旦设置了阻塞标志,调用mkfifo建立好之后,那么管道的两端读写必须分别打开,有任何一方未打开,则在调用open的时候就阻塞。

FIFO读写规则

  • 当没有数据可读时

O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

  • 当管道满时

O_NONBLOCK disable: write调用阻塞,直到有进程读走数据

O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

注:

  1. 如果所有管道写端对应的文件描述符被关闭,则read返回0
  2. 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE
  3. 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  4. 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

O_NONBLOCK对管道的影响图示

常见的阻塞问题

  • 打开方式:写进程阻塞,读进程阻塞。

先运行写进程(被阻塞),再运行读进程,一切正常。

先运行读进程(被阻塞),再运行写进程,一切正常。

  • 打开方式:写进程阻塞,读进程非阻塞。

就改一句代码 fd=open(FIFO_NAME,O_RDONLY | O_NONBLOCK),下面类似。

先运行写进程(被阻塞),再运行读进程,一切正常。

先运行读进程,程序直接崩掉(Segmentation fault (core dumped)),想想也挺自然的,没东西你还要读,而且不愿等。。。

  • 打开方式:写进程非阻塞,读进程阻塞。

先运行写进程,open调用将返回-1,打开失败。

先运行读进程(被阻塞),再运行写进程,一切正常。

  • 写进程非阻塞,读进程非阻塞。

其实就是上面2,3类各取一半不正常的情况。。

管道和FIFO的限制

  • OPEN_MAX

一个进程在任意时刻打开的最大描述符数.

可通过sysconf函数获取.

  • PIPE_BUF

可原子地写往一个管道或FIFO的最大数据量.

定义在<limlits.h>中,可在运行的时候调用pathconf或fpathconf取得.

fork,exec和exit对管道的影响

fork:子进程取得父进程的所有打开着的描述符副本

exec:所有打开着的描述符继续打开着,除非已设置描述符 FD_CLOEXEC 位

_exit:关闭所有打开着的描述符,最后一个关闭时删除管道或FIFO中残留的所有数据.


管道的其他应用方式


创建一个链接到另一个进程的管道,常用于获取shell命令的结果

popen函数原型

FILE *popen(const char *command, const char *type);
//本身会调用fork()产生子进程,然后从子进程中调用   /bin/sh -c   来执行参数command的指令。
//根据参数type的值,popen函数会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。
//之后此进程可以通过此文件指针来读取子进程的输出或写入子进程的标准输入设备中。
int pclose(FILE *stream);
//关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。

功能

创建一个管道,调用fork()产生一个子进程,接着关闭管道的不使用端,子进程执行cmd指向的应用程序或者命令。

执行完该函数后父进程和子进程之间生成一条管道,函数返回值为FILE结构指针,该指针作为管道的一端,为父进程所拥有。

子进程则拥有管道的另一端,该端口为子进程的stdin或者stdout。

这个流是单向的(只能用于读或写)。

向这个流写内容相当于写入该命令的标准输入,命令的标准输出和调用popen()的进程相同;

与之相反的,从流中读数据相当于读取命令的标准输出,命令的标准输入和调用popen()的进程相同。

参数

type参数:

只能是读或者写中的一种,得到的返回值(标准I/O流)也具有和type相应的只读或只写类型。

如果type=r,那么该管道的方向为:子进程的stdout到父进程的FILE指针,连接到cmd的标准输出;

如果type=w,那么管道的方向为:父进程的FILE指针到子进程的stdin,连接到cmd的标准输入。

cmd参数:

是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令。

可以通过这个管道执行标准输入输出操作。这个管道必须由pclose()函数关闭,

而不是fclose()函数(若使用fclose则会产生僵尸进程)。

pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。

如果shell不能被执行,则pclose()返回的终止状态与shell已执行exit一样。

popen示例

#include<stdio.h>
int main(void)
{
    FILE * fp;
    char buffer[80];
    fp=popen(“cat /etc/passwd”,”r”);
    fgets(buffer,sizeof(buffer),fp);
    printf(“%s”,buffer);
    pclose(fp);
    return 0;
}


目录
相关文章
|
9月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
317 67
|
8月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
235 16
|
8月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
153 20
|
7月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
155 0
|
7月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
221 0
|
7月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
140 0
|
7月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
194 0
|
4月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
503 1
二、Linux文本处理与文件操作核心命令
|
4月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
346 137
|
4月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
792 57