【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;
}


目录
打赏
0
1
1
0
216
分享
相关文章
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
从零开始掌握进程间通信:管道、信号、消息队列、共享内存大揭秘
本文详细介绍了进程间通信(IPC)的六种主要方式:管道、信号、消息队列、共享内存、信号量和套接字。每种方式都有其特点和适用场景,如管道适用于父子进程间的通信,消息队列能传递结构化数据,共享内存提供高速数据交换,信号量用于同步控制,套接字支持跨网络通信。通过对比和分析,帮助读者理解并选择合适的IPC机制,以提高系统性能和可靠性。
315 14
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
30 4
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
87 34
|
27天前
|
Linux:守护进程(进程组、会话和守护进程)
守护进程在 Linux 系统中扮演着重要角色,通过后台执行关键任务和服务,确保系统的稳定运行。理解进程组和会话的概念,是正确创建和管理守护进程的基础。使用现代的 `systemd` 或传统的 `init.d` 方法,可以有效地管理守护进程,提升系统的可靠性和可维护性。希望本文能帮助读者深入理解并掌握 Linux 守护进程的相关知识。
39 7
|
26天前
|
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
45 5
Linux 进程管理基础
Linux 进程是操作系统中运行程序的实例,彼此隔离以确保安全性和稳定性。常用命令查看和管理进程:`ps` 显示当前终端会话相关进程;`ps aux` 和 `ps -ef` 显示所有进程信息;`ps -u username` 查看特定用户进程;`ps -e | grep &lt;进程名&gt;` 查找特定进程;`ps -p &lt;PID&gt;` 查看指定 PID 的进程详情。终止进程可用 `kill &lt;PID&gt;` 或 `pkill &lt;进程名&gt;`,强制终止加 `-9` 选项。
26 3
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
70 16
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
218 20
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
155 13