1概述
管道是最初的Unix IPC形式,可追溯到1973年的Unix第3版。但其最根本的局限在于没有名字,故而只能由又亲缘关系的进程使用。这一点随FIFO的加入在System III Unix中得以改正。FIFO有时称为命名管道(named pipe)。管道和FIFO都是使用通常的read和write函数访问的。
2管道特点
1)管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。
2)只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)
3)单独构成一种独立的文件系统:管道对于管道两端的进程而言就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中。
4)数据的读出和写入:一个进程向管道中写的数据被管道另一端的进程读出。写入的数据每次都添加到管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
3管道创建
头文件 | #include <unistd.h> |
函数 | int pipe(int fd[2]); |
参数[out] | 参数fd用于返回两个描述符,fd[0]和fd[1]。 其中前者打开来读,后者打开来写。 |
返回值 | 成功返回0;出错返回-1. |
4管道读写规则
管道的两端分别用描述符fd[0]和fd[1]来描述,其中fd[0]端用于读,称为管道读端;fd[1]端用于写,称为管道写端。
(1)从管道读端读数据的规则:
1.1)管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的字节数为0
1.2)管道的写端存在,如果读请求的字节数大于PIPE_BUF,则返回管道中现有的数据字节数;如果读请求的字节数不大于
PIPE_BUF,则返回管道中与请求字节数等同的数据或仅有且小于请求的数据。
注:PIPE_BUF在include/linux/limits.h中定义,不同的内核版本可能会有不同。Posix.1要求PIPE_BUF不少于512字节。Linux 2.18内核则为#define PIPE_BUF 4096 该宏定义了内核中管道数据缓冲区的大小。(对FIFO也一样)
(2)从管道写端写入数据的规则:
2.1)想管道中写入数据时,不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不
读出管道缓冲区中的数据,那么写操作将一直阻塞。
5管道的典型应用
5.1 Shell应用
管道的最常见用途是用在各种shell中。
管道可用于输入输出重定向,它将一个命令的输出直接定向到另一个命令的输入。用这种方式连接起来的一系列程序被称为管线。管线中所有的进程是并发运行的,每一个进程等待前一个进程的输出作为输入。
注:只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号,应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止)。
lincoln@ubuntu:~$ ps aux | grep out | sort
lincoln 832 0.0 0.0 3320 800 pts/1 S+ 13:53 0:00 grep --color=auto out
root 282 0.0 0.1 14436 3748 ? S 10:42 0:00 /sbin/plymouthd --mode=boot --attach-to-session
lincoln@ubuntu:~$
重定向
当你需要将一个命令的输出保存在一个文件而非显示到显示器中时,可用重定向实现。
重定向输出 | 说明 (文件描述符0表示标准输入、1表示标准输出、2表示标准出错输出) |
ps > psoutput.txt | 将ps的标准输出重定向到文件psoutput.txt。默认情况下,若文件已经存在,则其内容将被覆盖。等价于:ps 1>psoutput.txt |
ps >> psoutput.txt | 将ps的标准输出重定向追加到文件psoutput.txt的尾部。 |
kill -9 1234 2>killerr.txt | lincoln@ubuntu:~$ kill -9 1234 -bash: kill: (1234) - No such process /*标准错误输出信息*/ lincoln@ubuntu:~$ kill -9 1234 1> err.txt /*重定向标准输出*/ -bash: kill: (1234) - No such process lincoln@ubuntu:~$ kill -9 1234 2> err.txt /*重定向标准错误输出*/ lincoln@ubuntu:~$ cat err.txt -bash: kill: (1234) - No such process lincoln@ubuntu:~$ |
kill -9 1234 >killerr.txt 2>&1 | 将标准输出重定向到文件killerr.txt,然后将标准错误输出重定向到与标准输出相同的地方 |
重定向输入 | 说明 |
more < killerr.txt | 将killerr.txt文件重定向到标准输出上。 |
示例:命令./a.out > outfile 2>&1与./a.out 2&1 >outfile的区别:
Shell是从左到右处理命令行的,所以:
./a.out >outfile 2&1首先设置标准输出到outfile,然后执行dup将标准输出复制到描述符2(标准错误输出)上,结果是将标准输出和标准错误设置为同一文件,即描述符1和描述符2指向同一个文件表项。
./a.out 2&1> outfile首先执行dup,所以使描述符2成为终端,标准输出重定向到outfile。结果是描述符1执行outfile的文件表项,描述符2执行终端的文件表项。
重定向与管道的区别
1)管线中,|左边的命令有标准输出,右边的命令能接收标准输入
重定向输出中,>左边有标准输出,右边只能是文件
重定向输入中,<左边的命令有标准输入,右边只能是文件
2)管道触发两个子进程执行|两边的程序,而重定向是在一个进程内执行的。
5.2父子进程间通信
管道的典型用途是为父子进程提供进程间的通信。一般首先,由一个进程(它将成为父进程)创建一个管道后调用fork派生一个自身的副本。接着,父进程关闭这个管道的读端,子进程关闭同一管道的写端。
利用管道来实现一个客户-服务器的例子。Main函数创建两个管道并用fork生成一个子进程。客户作为父进程运行,服务器作为子进程运行。第一个管道用于从客户向服务器发送路径名,第二个管道用于从服务器向客户发送该文件的内容(或者一个出错消息)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#define MAXLINE 4096
void server(int, int);
void client(int, int);
int main(int argc, char *argv[])
{
int ret;
pid_t pid;
int fd1[2];
int fd2[2];
/*创建第一个管道*/
if (pipe(fd1) == -1)
{
printf("pipe 1 error\n");
exit(1);
}
/*创建第二个管道*/
if (pipe(fd2) == -1)
{
printf("pipe 2 error\n");
exit(1);
}
pid = fork();
if (pid < 0)
{
printf("fork error\n");
exit(1);
}
else if (pid == 0) /*子进程,即服务器*/
{
close(fd1[1]);
close(fd2[0]);
server(fd1[0], fd2[1]);
exit(0);
}
/*父进程,即客户*/
close(fd1[0]);
close(fd2[1]);
client(fd2[0], fd1[1]);
waitpid(pid, NULL, 0);
exit(0);
}
void client(int readfd, int writefd)
{
size_t len;
ssize_t n;
char buff[MAXLINE];
fgets(buff, MAXLINE, stdin);
len = strlen(buff);
if (buff[len-1] == '\n')
len--;
if ( (n = write(writefd, buff, len)) == -1)
{
printf("client write error\n");
exit(1);
}
while ((n = read(readfd, buff, MAXLINE)) > 0)
write(STDOUT_FILENO, buff, n);
}
void server(int readfd, int writefd)
{
int fd;
ssize_t n;
char buff[MAXLINE];
if ((n = read(readfd, buff, MAXLINE)) == 0)
{
printf("end of file while read pathname\n");
exit(0);
}
buff[n] = '\0';
if ((fd = open(buff, O_RDONLY)) < 0)
{
snprintf(buff+n, sizeof(buff)-n, ": can't open %s\n", strerror(errno));
n = strlen(buff);
write(writefd, buff, n);
}
else
{
while ((n = read(fd, buff, MAXLINE)) > 0)
write(writefd, buff, n);
close(fd);
}
}
lincoln@ubuntu:~$ ./a.out
/etc/inet/ntp.conf
/etc/inet/ntp.conf: can't open No such file or directory
<a target=_blank href="mailto:lincoln@ubuntu:~$">lincoln@ubuntu:~$</a> ./a.out
/etc/shadow
root:$1$pZWe8074$X/f42c6E5.PobPBvT6.eY1:14924:0:99999:7:::
bin:*:14924:0:99999:7:::
daemon:*:14924:0:99999:7:::
adm:*:14924:0:99999:7:::
lp:*:14924:0:99999:7:::
<a target=_blank href="mailto:lincoln@ubuntu:~$">lincoln@ubuntu:~$</a>
5.3 popen和pclose
标准I/O库函数提供了popen函数,它创建一个管道并启动另外一个进程,该进程要么从该管道读出标准输入,要么往改管道写入标准输出。
头文件 | #include <stdio.h> |
函数 | FILE *popen(const char *command, const char *type); |
参数 | command:是一个shell命令 type: 如果type为r,那么调用进程读进command的标准输出 如果type为w,那么调用进程写到command的标准输入
|
返回 | 成功返回文件指针,出错返回NULL |
函数 | int pclose(FILE *stream); |
返回 | 成功则返回shell的终止状态,出错则返回-1. |
6 FIFO概述
管道没有名字,故而只能用于有一个共同祖先进程的各个进程之间。而FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
7 FIFO创建
头文件 | #include <sys/types.h> #include <sys/stat.h> |
函数 | int mkfifo(const char *pathname, mode_t mode); |
参数 | pathname:路径名,是该FIFO的名字 mode:文件权限位,用户、组、其他的读、写、执行等 |
返回 | 成功返回0,出错返回-1 |
说明 | mkfifo函数已隐含执行O_CREAT|O_EXCL,即要么创建一个新的FIFO,要么返回一个EEXIST错误(所指定名字的FIFO已经存在)。如果不希望创建一个新的FIFO,那就改为调用open而不是mkfifo。 因为FIFO是半双工的,所以在创建一个FIFO后,或者打开来读,或者打开来写。 对管道或FIFO的write总是往末尾添加数据,对它们的read则总是从开头返回数据。如果对管道或FIFO调用lseek,则返回ESPIPE错误。 |
8 FIFO规则
FIFO打开规则:
1)如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。
2)如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。
从FIFO读数据规则:
1)如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。
2)对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。
3)读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。
4)如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。
从FIFO写数据规则:
对于设置了阻塞标志的写操作:
1)当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。
2)当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
对于没有设置阻塞标志的写操作:
1)当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
2)当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;
9 FIFO应用
示例1:无亲缘关系的客户与服务器
//头文件fifo.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define MAXLINE 4096
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
//服务器 server.c
#include "fifo.h"
void server(int, int);
int main(int argc, char *argv[])
{
int readfd, writefd;
if ((mkfifo(FIFO1, FILE_MODE) < 0) && (errno != EEXIST))
{
printf("can't create %s\n", FIFO1);
exit(1);
}
if ((mkfifo(FIFO2, FILE_MODE) < 0) && (errno != EEXIST))
{
unlink(FIFO1);
printf("can't create %s\n", FIFO1);
exit(1);
}
writefd = open(FIFO2, O_WRONLY, 0);
if (writefd == -1)
{
printf("open FIFO2 error\n");
exit(1);
}
readfd = open(FIFO1, O_RDONLY, 0);
if (readfd == -1)
{
printf("open FIFO1 error\n");
exit(1);
}
server(readfd, writefd);
exit(0);
}
void server(int readfd, int writefd)
{
int fd;
ssize_t n;
char buff[MAXLINE];
printf("start read...\n");
if ((n = read(readfd, buff, MAXLINE)) == 0)
{
printf("end of file while read pathname\n");
exit(0);
}
buff[n] = '\0';
printf("end read..\n");
if ((fd = open(buff, O_RDONLY)) < 0)
{
snprintf(buff+n, sizeof(buff)-n, ": can't open %s\n", strerror(errno));
n = strlen(buff);
write(writefd, buff, n);
}
else
{
while ((n = read(fd, buff, MAXLINE)) > 0)
write(writefd, buff, n);
close(fd);
}
}
//客户端 client.c
#include "fifo.h"
void client(int, int);
int main(int argc, char *argv[])
{
int readfd, writefd;
readfd = open(FIFO2, O_RDONLY,0);
if (readfd == -1)
{
printf("open FIFO2 error\n");
exit(1);
}
writefd = open(FIFO1, O_WRONLY,0);
if (writefd == -1)
{
printf("open FIFO1 error\n");
exit(1);
}
client(readfd,writefd);
unlink(FIFO1);
unlink(FIFO2);
exit(0);
}
void client(int readfd, int writefd)
{
size_t len;
ssize_t n;
char buff[MAXLINE];
fgets(buff, MAXLINE, stdin);
len = strlen(buff);
if (buff[len-1] == '\n')
len--;
if ( (n = write(writefd, buff, len)) == -1)
{
printf("client write error, %s\n",strerror(errno));
exit(1);
}
示例2:单个服务器(迭代服务器),多个客户
//头文件fifo.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#define MAXLINE 4096
#define SERV_FIFO "/tmp/fifo.serv"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
typedef struct client_info
{
pid_t pid; /*客户端PID*/
char pathname[100];/*客户端路径名*/
}cli_info_t;
//迭代服务器server.c
#include "fifo.h"
int main(int argc, char *argv[])
{
int fd;
int ret;
pid_t pid;
ssize_t n;
int readfd;
int writefd;
int dummyfd;
char buff[MAXLINE];
char pathname[MAXLINE];
char fifoname[MAXLINE];
cli_info_t cli_buf;
struct pollfd poll_array;
umask(0);
pid = fork();
if (pid < 0)
{
printf("fork error");
exit(1);
}
else if (pid > 0)
{
exit(0);/*父进程退出*/
}
setsid();
/*继续调用fork,使父进程退出*/
pid = fork();
if (pid < 0)
{
printf("fork error");
exit(1);
}
else if (pid > 0)
{
exit(0);
}
if (chdir("/") < 0)
{
printf("chdir error");
exit(1);
}
/*关闭不需要的标准输出、标准输入、错误输出等文件描述符*/
close(0);
close(1);
close(2);
/*打开/dev/null,使其具有文件描述符0,1和2*/
open("/dev/null", O_RDWR);
dup(0);
dup(0);
if ((mkfifo(SERV_FIFO, FILE_MODE) < 0) && (errno != EEXIST))
{
syslog(LOG_INFO,"can't create %s\n", SERV_FIFO);
exit(1);
}
/*打开服务器FIFO文件来读*/
readfd = open(SERV_FIFO, O_RDONLY, 0);
if (readfd == -1)
{
syslog(LOG_INFO, "open FIFO1 error\n");
exit(1);
}
dummyfd = open(SERV_FIFO, O_WRONLY, 0);
if (dummyfd == -1)
{
printf("open FIFO2 error\n");
exit(1);
}
poll_array.fd = readfd;
poll_array.events = POLLIN;
/*迭代处理每个客户的请求*/
while (1)
{
ret = poll(&poll_array, 1, 2);
if (ret < 0)
{
syslog(LOG_INFO, "poll error..\n");
break;
}
else if (ret == 0)
{
continue;
}
if (poll_array.revents)
{
n = read(readfd, (char *)&cli_buf, sizeof(cli_buf));
if (n <= 0)
{
syslog(LOG_INFO, "server read SERV_FIFO error...\n");
continue;
}
/*客户端FIFO名字*/
snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%ld", (long)cli_buf.pid);
/*客户端路径名*/
snprintf(pathname, sizeof(pathname), "%s", cli_buf.pathname);
n = strlen(pathname);
if (pathname[n-1] == '\n') /*去掉路径名中的换行符*/
n--;
pathname[n] = '\0';
/*打开客户端FIFO*/
if ((writefd = open(fifoname, O_WRONLY)) < 0)
{
syslog(LOG_INFO, "Failed to open client fifoname: %s\n", fifoname);
continue;
}
/*打开客户端路径名*/
if ((fd = open(pathname, O_RDONLY)) < 0)
{
snprintf(buff, sizeof(buff), "server can't open %s : %s\n", pathname, strerror(errno));
n = strlen(buff);
write(writefd, buff, n);
}
else
{
/*读取客户端路径名内容并写给客户端*/
while ((n = read(fd, buff, MAXLINE)) > 0)
write(writefd, buff, n);
close(fd);
close(writefd);
}
}
}
exit(0);
}
//客户端client.c
#include "fifo.h"
int main(int argc, char *argv[])
{
int readfd, writefd;
pid_t pid;
ssize_t n;
cli_info_t cli_info;
char fifoname[MAXLINE];
char buff[MAXLINE];
if (argc < 2)
{
printf("usage: ./a.out pathname\n");
exit(1);
}
pid = getpid();
snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%ld", (long)pid);
if ((mkfifo(fifoname, FILE_MODE) < 0) && (errno != EEXIST))
{
printf("can't create %s\n", fifoname);
exit(1);
}
/*填充路径名和PID*/
sprintf(cli_info.pathname, argv[1]);
cli_info.pid = pid;
/*发送PID和路径名*/
writefd = open(SERV_FIFO, O_WRONLY,0);
if (writefd == -1)
{
printf("open SERV_FIFO error\n");
exit(1);
}
write(writefd, (char *)&cli_info, sizeof(cli_info));
/*打开客户端FIFO*/
readfd = open(fifoname, O_RDONLY,0);
if (readfd == -1)
{
printf("open CLI_FIFO error\n");
exit(1);
}
while ((n = read(readfd, buff, MAXLINE)) > 0)
write(STDOUT_FILENO, buff, n);
close(readfd);
unlink(fifoname);
exit(0);
}
//输出
lincoln@ubuntu:./server
lincoln@ubuntu:./client /etc/pam.conf
# ---------------------------------------------------------------------------#
# /etc/pam.conf #
# ---------------------------------------------------------------------------#
#
# NOTE
# ----
#
# NOTE: Most program use a file under the /etc/pam.d/ directory to setup their
# PAM service modules. This file is used only if that directory does not exist.
# ---------------------------------------------------------------------------#
# Format:
# serv. module ctrl module [path] ...[args..] #
# name type flag #
<a target=_blank href="mailto:lincoln@ubuntu">lincoln@ubuntu</a><: