pipe相關代碼的實現

pipe

頭文件:#include<unistd.h>
功能:創建一個無名管道
函數原型:int pipe(int fd[2]);//分別以讀、寫方式打開,所以有兩個文件描述符
參數:
fd:文件描述符數組,其中fd[0]表示讀端,fd[1]表示寫端
返回值:成功返回0,失敗返回錯誤碼

管道的特點
-管道自帶互斥與同步機制
-管道只能單向通信
-只要有血緣關係的兩個進程就可以進行進程間通信
-管道也是文件
-管道的生命週期隨進程(進程退出管道隨即釋放)
-提供面向字節流的服務

這裏寫圖片描述

用fork創建一個子進程(以父進程爲模板),子進程PCB的大部分信息都是來源於父進程,子進程也有一個文件表和父進程的文件表保持一致,數組也保持一致,數組中的內容也是相同的,指向也是相同的。進程間通信的本質是讓兩個不同的進程看到一份公共的資源。file裏寫的數據肯定會被刷新到硬盤上,但在刷新到內存前,會有一個緩衝區,這個緩衝區就可以被父、子進程同時利用起來。

這裏寫圖片描述

但管道只能單向通信,若想讓父進程讀,子進程寫,就要關閉父進程的寫端,關閉子進程的讀端。那爲什麼不直接以讀的方式打開父進程,寫的方式打開子進程呢???爲什麼還要關???若父進程不以寫方式打開,子進程就拿不到寫方法,所以必須得讀寫打開。此時我們就有了一個單向數據通信的信道。

實現代碼:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        close(fd[0]);
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            sleep(1);//子進程每隔一秒寫一條
            write(fd[1],msg,strlen(msg));//往管道里寫就是往文件裏寫
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];
            while(1)
            {
                ssize_t s = read(fd[0],buf,sizeof(buf)-1);
                if(s > 0)
                {
                    buf[s] = '\0';
                    printf("f say : %s\n",buf);
                }
            }
        }
    }
}

運行結果:
這裏寫圖片描述

下面分析匿名管道的五種情況:
1.如果管道的寫端一直在寫,而讀端不關閉自己的讀文件描述符,但也不讀

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        close(fd[0]);
        int count = 0;
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            write(fd[1],msg,strlen(msg));//往管道里寫就是往文件裏寫
            printf("%d\n",count++);//寫一次count++
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];
            while(1)
            {

            }
        }
    }
}

寫滿了不能重頭覆蓋寫,一旦覆蓋寫會出現數據的二異性問題。不能再寫子進程就會被卡住,count值不再變化

這裏寫圖片描述
一瞬間寫了4千多次,寫滿了數字不再變,爲保證管道在讀端讀,寫端才寫,所以子進程卡住了。

2.讓子進程先寫滿父進程再讀

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        close(fd[0]);
        int count = 0;
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            write(fd[1],msg,strlen(msg));//往管道里寫就是往文件裏寫
            printf("%d\n",count++);//寫一次count++
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];
            while(1)
            {
                sleep(5);
                ssize_t s = read(fd[0],buf,sizeof(buf)-1);
                if(s > 0)
                {
                    buf[s] = '\0';
                    printf("f say : %s\n",buf);
                }
            }
        }
    }
}

這裏寫圖片描述
此時一瞬間子進程寫滿,父進程5秒之後讀,父進程讀完管道就空出來了,父進程繼續每隔5秒讀一條消息,當父進程在讀的時候,子進程也繼續再寫。
小結
寫方一直在寫,讀端不讀,但讀端也不關閉文件描述符,寫方在把管道寫滿之後就會停下來,等待別人取數據。

3(1).寫方寫一條消息後,10秒鐘之後再寫下一條消息,讀方立即把數據讀出來,管道里沒有數據,讀方會阻塞式等待。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        close(fd[0]);
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            write(fd[1],msg,strlen(msg));//往管道里寫就是往文件裏寫
            sleep(10);
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];
            int count = 0;
            while(1)
            {
                ssize_t s = read(fd[0],buf,sizeof(buf)-1);
                if(s > 0)
                {
                    buf[s] = '\0';
                    printf("f say : %s\n",buf);
                }
                printf("f read %d\n",count);
            }
        }
    }
}

這裏寫圖片描述

管道里沒有數據,讀方會阻塞式等待,直到有信息(10秒鐘之後)

這裏寫圖片描述

如果讀寫雙方都不關文件描述符,一個不寫或一個不讀都會導致對方要等你,這就叫同步。

3(2).如果寫端一直在寫,然後不再寫並且把寫端關閉,而讀端一直在讀,直到把管道里的數據讀完

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        close(fd[0]);
        int count = 0;
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            write(fd[1],msg,strlen(msg));//往管道里寫就是往文件裏寫
            printf("%d\n",count++);
            if(count > 10)
            {
                close(fd[1]);//關閉寫端
                break;
            }
            sleep(1);//每隔1秒寫一次
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];

            while(1)
            {
                ssize_t s = read(fd[0],buf,sizeof(buf)-1);
                if(s > 0)
                {
                    buf[s] = '\0';
                    printf("f say : %s\n",buf);
                }
                else if(s == 0)//將管道里的數據讀完後會返回一個0值,代表讀到文件結尾
                {
                    printf("pipe done,break!\n");
                    break;
                }
            }
        }
    }
}

這裏寫圖片描述

讀10次後,子進程會關閉自己的寫文件描述符,父進程就讀到0了

這裏寫圖片描述

4.寫端一直在寫,讀端不但不讀,還關閉掉了自己的讀文件描述符

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        int count = 0;
        close(fd[0]);
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            write(fd[1],msg,strlen(msg));//往管道里寫就是往文件裏寫
            printf("%d\n",count++);
            sleep(1);//每隔1秒寫一次
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];
            while(1)
            {
                ssize_t s = read(fd[0],buf,sizeof(buf)-1);
                if(s > 0)
                {
                    buf[s] = '\0';
                    printf("f say : %s\n",buf);
                    sleep(3);
                    break;
                }
                close(fd[0]);
            }
            int status = 0;
            wait(&status);
            printf("sig : %d\n",status&0x7F);//status數字當中的次7位表示退出信號
        }
    }
}

寫端的進程會立即被操作系統用信號終止

這裏寫圖片描述
子進程一直在寫,3秒鐘後父進程讀取到子進程以13號信號退出

13號信號爲SIGPIPE

這裏寫圖片描述

當寫端一直在寫,讀端不但不讀還把自己的讀文件描述符關閉,寫端就會因爲觸發異常而被操作系統直接終止,操作系統向目標進程發送13號信號

總結一下以上四種情況:

(1)寫端一直在寫,讀端不讀也不關,寫端寫滿後就會阻塞
(2)讀端一直在讀,寫端也一直在寫,可是寫端突然不寫了,但不關閉寫文件描述符,讀端一直在讀,當管道數據爲空時,就會被阻塞
(3)寫端不但不寫了,還關閉了寫文件描述符,讀端一直在讀,管道內的數據越來越少,最後讀到0(即文件結尾)
(4)寫端一直在寫,讀端不但不讀而且還關閉了讀文件描述符,一旦再寫入就會觸發操作系統用13號信號(SIGPIPE)異常終止目標進程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章