Linux进程间通信(IPC)编程实践(一) 匿名管道

1 管道概念

管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”, 管道的本质是固定大小的内核缓冲区;它包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。 


2 管道限制

   1)管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;

   2)匿名管道只能用于具有共同祖先的进程(如父进程与fork出的子进程)之间进行通信, 原因是pipe创建的是两个文件描述符, 不同进程直接无法直接获得;(通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程共享该管道)

3 匿名管道创建

#include <unistd.h>  
int pipe(int pipefd[2]); 

参数

pipefd:文件描述符数组,其中pipefd[0]表示读端,pipefd[1]表示写端,示意图如下:

(1) 接下来,我们利用匿名管道来进行父子进程之间的通信,子进程向父进程发送信息。

int main()
{
    int pipefd[2];
    if(pipe(pipefd)==-1)
        ERR_EXIT("pipe error!");
    pid_t pid;
    pid=fork();
    if(pid==-1)
        ERR_EXIT("fork error");
    if(pid==0)
    {
        close(pipefd[0]);
        write(pipefd[1],"hello",5);
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
    }
    
    close(pipefd[1]);
    char buf[10]={0};
    read(pipefd[0],buf,10);
    printf("buf=%s\n",buf);
 
    return 0;
}

结果:父进程接收到子进程发送的hello

(2) 我们来模拟实现管道命令 ls | wc -w   关键点就是:

(a) 子进程运行ls,dup2(pipefd[1],STDOUT_FILENO)重定向标准输出,定位到管道写端,ls写入到管道写端而不是标准输出设备;

(b) 父进程运行wc -w ,wc获取数据的时候从管道读端获取,不再从标准输入设备。

(c) 通过管道, 将子进程的输出发送到wc的输入 。

int main()
{
    int pipefd[2];
    if(pipe(pipefd)==-1)
        ERR_EXIT("pipe error!");
    pid_t pid;
    pid=fork();
    if(pid==-1)
        ERR_EXIT("fork error");
    if(pid==0)
    {
        dup2(pipefd[1],STDOUT_FILENO);//重定向输出
        close(pipefd[0]);
        close(pipefd[1]);
        execlp("ls","ls",NULL);//若出错才执行下面的代码
        fprintf(stderr,"error execute ls\n");
        exit(EXIT_FAILURE);
                 
    }
    
    dup2(pipefd[0],STDIN_FILENO);
    close(pipefd[0]);
    close(pipefd[1]);
    execlp("wc","wc","-w",NULL);
    fprintf(stderr,"error execute wc\n");
    exit(EXIT_FAILURE);
    
    return 0;
}

(3) 实现cp操作

不带任何参数的cat命令是从标准输入读入命令,写到标准输出。0->Makefile; 1->Makefile2;

int main()
{
    close(0);
    open("Makefile",O_RDONLY);
    close(1);
    open("Makefile2",O_WRONLY | O_CREAT | O_TRUNC,0644);
    execlp("cat","cat",NULL);
 
    return 0;
}

4 管道的读写规则

我们对以上的规则一一进行验证。

(1) 如果管道为空,那么read会阻塞(模式),如果使用非阻塞模式的话,也就是使用fcntl函数,对模式进行修改后

int flags=fcntl(pipefd[0],F_SETFL,flags | O_NONBLOCK);
read(pipefd[0],buf,10);

此时,读操作会失败,显示资源暂且不可用的错误。

(2) 管道的写端关闭,read打印输出0,但是并不报错误,显示读到了文件的末尾。

int main()  
{  
    int pipefd[2];  
    if (pipe(pipefd) != 0)  
        err_exit("pipe error");  
  
    pid_t pid = fork();  
    if (pid == -1)  
        err_exit("fork error");  
    else if (pid == 0)  
    {  
        close(pipefd[1]);  
        exit(EXIT_SUCCESS);  
    }  
  
    close(pipefd[1]);  
    sleep(2);  
    char buf[2];  
    if (read(pipefd[0], buf, sizeof(buf)) == 0)  
        cout << "sure" << endl;
    
    return 0;
} 

(3) 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进程终止。如果我们自定义SIGPIPE的处理函数的话会生效。

int main()  
{  
    if (signal(SIGPIPE, handler) == SIG_ERR)  
        err_exit("signal error");  
  
    int pipefd[2];  
    if (pipe(pipefd) != 0)  
        err_exit("pipe error");  
  
    pid_t pid = fork();  
    if (pid == -1)  
        err_exit("fork error");  
    else if (pid == 0)  
    {  
        close(pipefd[0]);  
        exit(EXIT_SUCCESS);  
    }  
  
    close(pipefd[0]);  
    sleep(2);  
    char test;  
    if (write(pipefd[1], &test, sizeof(test)) < 0)  
        err_exit("write error");  
    
    return 0;
}  

会打印出  singal error错误。

(4) 关于PIPE_BUF和原子性操作之间的关系,

已知管道的PIPE_BUF为4K, 我们启动两个进程A, B向管道中各自写入68K的内容, 然后我们以4K为一组, 为了方便我们查看管道最后一个字节的内容, 多运行该程序几次, 就会发现这68K的数据会有交叉写入的情况 。
 

int main()  
{  
    const int TEST_BUF = 68 * 1024; //设置写入的数据量为68K  
    char bufA[TEST_BUF];  
    char bufB[TEST_BUF];  
    memset(bufA, 'A', sizeof(bufA));  
    memset(bufB, 'B', sizeof(bufB));  
  
    int pipefd[2];  
    if (pipe(pipefd) != 0)  
        err_exit("pipe error");  
  
    pid_t pid;  
    if ((pid = fork()) == -1)  
        err_exit("first fork error");  
    else if (pid == 0)  //第一个子进程A, 向管道写入bufA  
    {  
        close(pipefd[0]);  
        int writeBytes = write(pipefd[1], bufA, sizeof(bufA));  
        cout << "A Process " << getpid() << ", write "  
             << writeBytes << " bytes to pipe" << endl;  
        exit(EXIT_SUCCESS);  
    }  
  
    if ((pid = fork()) == -1)  
        err_exit("second fork error");  
    else if (pid == 0)  //第二个子进程B, 向管道写入bufB  
    {  
        close(pipefd[0]);  
        int writeBytes = write(pipefd[1], bufB, sizeof(bufB));  
        cout << "B Process " << getpid() << ", write "  
             << writeBytes << " bytes to pipe" << endl;  
        exit(EXIT_SUCCESS);  
    }  
  
    // 父进程  
    close(pipefd[1]);  
    sleep(2);   //等待两个子进程写完  
    char buf[4 * 1024]; //申请一个4K的buf  
    int fd = open("save.txt", O_WRONLY|O_TRUNC|O_CREAT, 0666);  
    if (fd == -1)  
        err_exit("file open error");  
  
    while (true)  
    {  
        int readBytes = read(pipefd[0], buf, sizeof(buf));  
        if (readBytes == 0)  
            break;  
        if (write(fd, buf, readBytes) == -1)  
            err_exit("write file error");  
        cout << "Parent Process " << getpid() << " read " << readBytes  
             << " bytes from pipe, buf[4095] = " << buf[4095] << endl;  
    }
    
    return 0;
}  

注:我们可以使用man 7 pipe查询有关管道容量的信息;另外,管道的容量不一定就等于PIPE_BUF, 如在Ubuntu中, 管道容量为64K, 而PIPE_BUF为4K.

本文转自:

https://blog.csdn.net/NK_test/article/details/48662897

猜你喜欢

转载自blog.csdn.net/u011857683/article/details/82320875
今日推荐