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

 

 

 

 

 

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