跟着iMX28x开发套件学linux-07

七、linux应用编程之五:管道

进程间通信有多种方式,管道是其中一种。管道分为匿名管道和命名管道,匿名管道仅用于父子进程之间通信,没有实际文件。而命名管道可以实现任意进程间的通信,在系统中需要创建一个fifo文件作为管道。

管道的理解:无论是匿名管道还是命名管道,都可以把管道看做一个文件,进程A给这个文件写数据,进程B从这个文件读数据,那么进程A就可以给进程B传输数据了。而重点在于,匿名管道没有实际文件要怎么实现,还有如何保证进程A,B之间的通信同步(即进程A发送数据的时候,是不是进程B需要这个数据的时候)。

匿名管道

1) 创建管道:匿名管道没有实际的文件,想象出一个虚拟的文件,父进程给这个文件写数据,子进程从这个文件读数据,完成父子进程间的通信。既然想象成文件,那读写数据就需要文件描述符,但是这个文件又是虚拟的,无法通过open()函数获得文件描述符。实际上系统只需要知道哪个是文件描述符即可,文件描述符的内容是什么没有影响。所以可以int声明文件描述符,然后调用int pipe(int pipefd[2]);函数对这个文件描述符进行注册即可,当然要检查注册是否成功,所以有以下代码,创建管道:

    int pipefd[2];

    if(pipe(pipefd) <0 ){
        fprintf(stderr, "creat pipe error\n");
        exit(-1);
    }
    else{
        printf("creat pipe succeed\n");
    }

 

PS:代码中文件描述符用了int pipefd[2],一般文件描述符都是int fd,实际上pipefd[2]并不是两个文件的意思,而是一个文件的两个端口,读端口和写端口。linux系统定义pipefd[1]是写端口,pipefd[0]是读端口,匿名管道实际模型更加接近于下图:

 

2) 将数据写入匿名管道:父子进程都可以是数据的接收方或者数据的发送方,但是不能同时是数据的发送方和接收方,所以匿名通道实际上是一个半双工通信。 假设父进程时发送方,那父进程应当关闭pipefd[0],然后在pipefd[1]写入要发送的数据。父进程发送数据代码如下:

    else if(cpid > 0){                //parent process
        close(pipefd[0]);
        write(pipefd[1], argv[1], strlen(argv[1]));
        close(pipefd[1]);
        if(wait(NULL) != cpid){
            printf("p:exit child process error\n");
            exit(-1);
        }
        else{
            printf("p:exit child process succeed\n");
            exit(0);
        }
    }

 

代码说明:在父进程中先关闭输入端口pipefd[0],接着在pipefd[1]写入要发送的数据,这里的数据是运行程序时的第二个参数。发送完成之后关闭输出端口pipefd[1],最后给子进程发送wait()。

 

3) 从匿名管道中读取数据:上面程序中父进程已经把数据写入了匿名通道,子进程应当把管道中的数据读取出来,完成一次进程通信。子进程中先关闭匿名通道的输出端口pipe[1],然后读取匿名通道中的内容,判断是否读取完毕,然后将读取到的内容打印到屏幕上,观察与运行程序时的第二个参数是否相同。代码如下:

    else if(cpid == 0){                //child process
        close(pipefd[1]);
        while(read(pipefd[0], &read_buf[count++], 1) > 0 );
        printf("c:%s\n", read_buf);
        close(pipefd[0]);
        exit(0);
    }

 

代码说明:在读取数据的时候,应该一个字节一个字节的读,当read()函数返回-1的时候代表数据已经全部读取。

 

4) 通信同步问题:有四种特殊情况需要注意。

① 写端开启,读端开启,当读端读出数据时,若匿名管道内没有数据,读端进程将在read()处阻塞。

② 写段关闭,读端开启,当读端读出数据时,匿名管道中的数据被全部读出,读端进程的read()将会返回0

③ 写端开启,读端关闭,若写端进程试图运行write()函数时,写端进程将会收到信号SIGPIPE,并终止。

④ 写端开启,读端开启,但是读端没有进行read(),而写端一直运行write(),等到匿名管道被写满之后,写端进程将在write()处阻塞。

 

5) 匿名管道完整实验代码:

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[]){
    pid_t cpid;
    int pipefd[2];
    char read_buf[100] = {0};
    int count = 0;
    
    if(argc != 2){
        fprintf(stderr, "Usage:%s<filename>", argv[0]);
        exit(-1);
    }

    if(pipe(pipefd) <0 ){
        fprintf(stderr, "creat pipe error\n");
        exit(-1);
    }
    else{
        printf("creat pipe succeed\n");
    }
    
    
    cpid = fork();
    
    if(cpid < 0){
        fprintf(stderr, "creat child process error\n");
        return -1;
    }
    
    else if(cpid == 0){                //child process
        close(pipefd[1]);
        while(read(pipefd[0], &read_buf[count++], 1) > 0 );
        printf("c:%s\n", read_buf);
        close(pipefd[0]);
        exit(0);
    }
    
    else if(cpid > 0){                //parent process
        close(pipefd[0]);
        write(pipefd[1], argv[1], strlen(argv[1]));
        close(pipefd[1]);
        if(wait(NULL) != cpid){
            printf("p:exit child process error\n");
            exit(-1);
        }
        else{
            printf("p:exit child process succeed\n");
            exit(0);
        }
    }
    return -1;
}

 

 

程序运行结果:

 

命名管道

1) 创建通道:命名通道是有具体文件的fifo,可以时间无关进程间的通信,所以创建命名通道的过程跟创建文件是一样的。在创建命名通道之前,要用acess(fifo_name, F_OK);函数检查通道文件是否存在,若存在返回0,不存在返回-1,根据返回的结果决定是否创建命名管道。创建过程代码如下:

    if(access(fifo_name, F_OK) < 0){
        ret = mkfifo(fifo_name, 0777);
        if(ret < 0){
            perror("mkfifo error");
            exit(EXIT_FAILURE);
        }
    }

 

2) 写端将数据写进命名管道:跟写数据进文件一样,写入数据之前要先打开文件。需要注意的是,当使用只写(O_WRONLY)模式打开命名管道时,若没有进程使用只读(O_RDONLY)模式打开命名管道,则用只写模式打开命名管道的进程将会阻塞,直到有进程使用只读模式打开命名管道。当然,如果一个进程使用读写(O_RDWR)模式打开命名管道时,不会阻塞。打开命名管道以及写数据进管道的代码如下:

    pipefd = open(fifo_name, O_WRONLY);

    ret = write(pipefd, buffer, bytes);
    if(ret < 0){
        fprintf(stderr, "write data to pipe error\n");
        exit(EXIT_FAILURE);
    }

 

 

3) 读端从命名管道读取数据:与从文件读数据一样,读取数据之前要先打开文件,然后写入,代码如下:

    pipefd = open(fifoname, O_RDONLY);

    bytes = read(pipefd, buffer, BUFSIZE);
    if(bytes < 0){
        fprintf(stderr, "read data error\n");
        exit(EXIT_FAILURE);
    }

 

 

4) 完整实验代码:进程A创建命名管道,并复制一个文件的内容到管道。进程B从管道中读取数据,然后写入到另一个文件中。最后通过MD5校验查看两个代码的内容是否相同。

进程A代码:

#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>

#define BUFSIZE 1024

int main(int argc, char *argv[]){
    
    char fifo_name[] = "./fifo";
    int pipefd, datafd;
    int bytes, ret;
    char buffer[BUFSIZE] = {0};
    
    if(argc != 2){
        fprintf(stderr, "Usage:%s<filename>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    if(access(fifo_name, F_OK) < 0){
        ret = mkfifo(fifo_name, 0777);
        if(ret < 0){
            perror("mkfifo error");
            exit(EXIT_FAILURE);
        }
    }
    
    pipefd = open(fifo_name, O_WRONLY);
    datafd = open(argv[1], O_RDONLY);
    
    if(!(pipefd>0 && datafd>0)){
        fprintf(stderr, "file open error\n");
        exit(EXIT_FAILURE);
    }
    
    //printf("A:Process A sleep 5s\n");
    //sleep(5);
    
    bytes = read(datafd, buffer, BUFSIZE);
    if(bytes < 0){
        fprintf(stderr, "read data from file error\n");
        exit(EXIT_FAILURE);
    }
    
    while(bytes > 0){
        ret = write(pipefd, buffer, bytes);
        if(ret < 0){
            fprintf(stderr, "write data to pipe error\n");
            exit(EXIT_FAILURE);
        }
    
        bytes = read(datafd, buffer, BUFSIZE);
        if(bytes < 0){
            fprintf(stderr, "read data from file error\n");
            exit(EXIT_FAILURE);
        }
    }
    
    close(pipefd);
    close(datafd);
    printf("A:read from file , write to pipe succeed\n");
    
    //printf("A:Process A sleep 5s\n");
    //sleep(5);
    while(1);
    return 0;

}

 

 

进程B代码:

#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>

#define BUFSIZE 1024

int main(int argc, char* argv[])
{
    char fifoname[] = "./fifo";
    int pipefd, datafd;
    int bytes, ret;
    char buffer[BUFSIZE];
    if(argc != 2){
        fprintf(stderr, "Usage:%s<filename>", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    pipefd = open(fifoname, O_RDONLY);
    datafd = open(argv[1], O_WRONLY|O_CREAT, 0666);
    
    if(!(pipefd>0 && datafd>0)){
        fprintf(stderr, "file open error\n");
        exit(EXIT_FAILURE);
    }
    
    printf("B:i want to read\n");
    
    bytes = read(pipefd, buffer, BUFSIZE);
    if(bytes < 0){
        fprintf(stderr, "read data error\n");
        exit(EXIT_FAILURE);
    }
    
    
    while(bytes > 0){
        ret = write(datafd, buffer, bytes);
        if(ret < 0){
            fprintf(stderr, "write data error\n");    
            exit(EXIT_FAILURE);
        }
        
        bytes = read(pipefd, buffer, BUFSIZE);
        if(bytes < 0){
            fprintf(stderr, "read data error\n");
            exit(EXIT_FAILURE);
        }
    
    }
    close(pipefd);
    close(datafd);
    
    printf("ok\n");
    
    return 0;
}

 

 

 

程序运行结果,先运行进程A,再运行进程B

 

说明:运行进程A之前先创建进程A要用到的文件a.bin(在程序中文件的打开方式没写O_CREAT),然后运行进程A,注意用的指令是./processA a.bin &,表示程序后台运行,因为以只写模式打开命名管道时,进程会阻塞,如果不后台运行,这个终端将无法操作。接着运行进程B,运行进程B之后,进程A结束阻塞状态,从a.bin中读取数据,然后写到管道中,进程B从管道中读取数据,然后写到b.bin中。运行结束后,运行md5sum指令,查看a.binb.binMD5码是否一致。

 

posted on 2018-11-12 22:36  diskiii  阅读(295)  评论(0编辑  收藏  举报

导航