匿名管道pipe是单向的,或者说是半双工的。
提供了一个和它有共同先祖的进程的通信方式。
注意:如果需要全双工通信,应该转而考虑套接字API
另一种类型的管道被称为命名管道。一个命名管道像正常管道一样工作,但是它存在于文件系统中,因而任意进程都可以找到它。这使得不同先祖的进程之间也可以进行通信。
注意:管道只不过是一对文件描述符,因此所有能够操作文件描述符的函数都可以用于管道。这些函数包括但不限于 select,read,write,fcntl,freopen。
pipe 函数
|
|
pipe 函数成功时返回0,失败时返回-1,并设置相应的errno。
成功返回时,fds数组(作为pipe函数的参数传递)会被赋予两个活动的文件描述符。
数组中第一个元素是应用程序可读取的文件描述符,第二个元素是应用程序可写入的文件描述符。
例子:
创建一个管道,然后把进程分支为一个父进程和一个子进程。(16行)
在子进程中,试图从管道的输入文件描述符进行读取(第18行),这会将该进程挂起,直到有什么东西可以读取为止。
父进程就是使用写文件描述符(pipe数组的第二个元素)向管道中写入一个测试字符串,然后用wait函数等待子进程退出。
|
|
|
|
|
|
当进程结束后,为管道分配的资源会被自动释放。
尽管如此,在编程中最好还是调用close关闭管道使用的描述符。
|
|
dup和dup2
|
|
这两个函数是用来复制文件描述符的。
举个栗子,用 dup(2) 实现输出的重定向。
|
|
puts(3) 函数是将参数字符串写入到标准输出 stdout(文件描述符是 1) 中,而标准输出默认目标是我们的 shell。
如果想要让 puts(3) 的参数输出到一个文件中,实现思路是:首先打开一个文件获得一个新的文件描述符,然后关闭标准输出文件描述符(1),然后使用 dup(2) 函数族复制产生一个新的文件描述符,此时的 1 号文件描述符就不是标准输出的文件描述符了,而是我们自己创建的文件的描述符了。
还记得我们之前提到过吗,文件描述符优先使用可用范围内最小的。进程中当前打开的文件描述符有标准输入(0)、标准输出(1)、标准错误(2)和我们自己打开的文件(3),当我们关闭了 1 号文件描述符后,当前可用的最小文件描述符是 1,所以新复制的文件描述符就是 1。而标准库函数 puts(3) 在调用系统调用 write(2) 函数向 1 号文件描述符打印时,正好是打印到了我们所指定的文件中。
|
|
由于题目的要求是 puts(3) 上面注释以下的内容都不能修改,原则上 1 号文件描述符在这里使用完毕也需要 close(2),所以这里造成了一个内存泄漏,但并不影响对 dum(2) 函数族的解释和测试。
例二
模拟 ls -l | wc -l
的指令,即将ls的结果通过管道作为输入给wc
,wc
指令:我们可以计算文件的Byte数、字数、或是列数,若不指定文件名称、或是所给予的文件名为”-“,则wc指令会从标准输入设备读取数据。
参数:
- -c或–bytes或–chars 只显示Bytes数。
- -l或–lines 只显示行数。
- -w或–words 只显示字数。
- –help 在线帮助。
- –version 显示版本信息。
使用dup实现:
|
|
execlp
从PATH 环境变量中查找文件并执行
定义:
int execlp(const char * file,const char * arg,……);
头文件:
#include<unistd.h>
说明:
execlp()
会从PATH 环境变量所指的目录中查找符合参数file的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,
最后一个参数必须用空指针(NULL)作结束。
函数mkfifo
mkfifo用于在文件系统中创建一个文件,提供FIFO功能(也称命名管道)。
之前的匿名管道只能用于进程与其子进程之间,
命名管道在文件系统中可见,因此可以用于任何进程之间。
函数mkfifo原型如下:
|
|
第一个参数(pathname)在文件系统中指明要创建的文件。
第二个参数表示对该FIFO的读写权限设置。
函数成功时返回0,错误返回-1,并设置相应errno
示例:
|
|