UNPv2:进程间通信(二) 管道和FIFO

1概述

        管道是最初的Unix IPC形式,可追溯到1973年的Unix3版。但其最根本的局限在于没有名字,故而只能由又亲缘关系的进程使用。这一点随FIFO的加入在System III Unix中得以改正。FIFO有时称为命名管道(named pipe)。管道和FIFO都是使用通常的readwrite函数访问的。

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_BUFinclude/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 popenpclose

标准I/O库函数提供了popen函数,它创建一个管道并启动另外一个进程,该进程要么从该管道读出标准输入,要么往改管道写入标准输出。

头文件

#include <stdio.h>

函数

FILE *popen(const char *command, const char *type);

参数

command:是一个shell命令

type:

如果typer,那么调用进程读进command的标准输出

如果typew,那么调用进程写到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后,或者打开来读,或者打开来写。

对管道或FIFOwrite总是往末尾添加数据,对它们的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><:



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值