進程間通訊之匿名管道闡述

前言

每一個進程各自有不一樣的用戶地址空間,任何一個進程的全局變量在另外一個進程中都看不到,因此進程之間要交換數據必須經過內核,在內核中開闢一塊緩衝區,進程1把數據從用戶空間拷到內核緩衝區,進程2再從內核緩衝區把數據讀走,內核提供的這種機制稱爲進程間通訊(IPC,InterProcess Communication)。以下圖所示。
圖片描述linux

管道

什麼是管道

若是用過Linux命令,那麼咱們對這個名詞不會陌生,咱們常常經過「|」來使用管道,好比ls -l|grep string.
管道是一個進程鏈接數據流到另外一個進程的通道,一般一個進程的輸出經過管道鏈接到另外一個進程的輸入。shell

高層管道

語法

#include <stdio.h>
FILE* popen (const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);

用法

popen會啓動另外一個新進程,而後傳遞數據給它或從它接收數據。
command是要運行的程序名和相應的參數, open_mode只能是"r"或"w"
popen返回一個FILE類型指針, 而後就可使用I/O文件函數對其操做。
當open_mode爲「r」時, 表示主程序能夠從被調用程序讀取數據
當open_mode爲「w」時, 表示主程序能夠寫數據到被調用程序
pclose用於關閉由popen建立出的關聯文件流。pclose只在popen啓動的進程結束後才返回,若是調用pclose時被調用進程仍在運行,pclose會等其結束。它返回關閉的文件流所在進程的退出碼。編程

示例

代碼

//
// Created by         : Harris Zhu
// Filename           : test.c
// Author             : Harris Zhu
// Created On         : 2017-08-20 01:18
// Last Modified      :
// Update Count       : 2017-08-20 01:18
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char** argv)
{
    FILE *rdFP = NULL;
    FILE *writeFP = NULL;
    char buf[BUFSIZ + 1];
    int num_of_chars = 0;

    memset(buf, '\0', sizeof(buf));

    //打開ls做爲讀接口
    rdFP = popen("ls -l", "r");
    if(!rdFP)
    {
        ERR_EXIT("failed to open read pipe");
    }
    //打開grep做爲寫接口
  wrFP = popen("grep \\\\-rw-rw-r--", "w");
    if(!wrFP)
    {
        ERR_EXIT("failed to open write pipe");
    }
    if(rdFP && wrFP)
    {
        //從ls讀取BUFSIZ字符
        num_of_chars = fread(buf, sizeof(char), BUFSIZ, rdFP);
        while(num_of_chars > 0)
        {
            buf[num_of_chars] = '\0';
            //把數據寫入grep
            fwrite(buf, sizeof(char), num_of_chars, wrFP);
            //循環讀取數據直到讀完全部數據
            num_of_chars = fread(buf, sizeof(char), BUFSIZ, rdFP);
        }
        //關閉文件流
        pclose(rdFP);
        pclose(wrFP);
    }
    exit(EXIT_SUCCESS);
}

輸出

-rw-rw-r--. 1 harriszh harriszh   126 Aug 20 01:28 makefile
-rw-rw-r--. 1 harriszh harriszh  1560 Aug 20 01:32 test.c

由於grep緣由,在例子中須要使用4個 來escape -, 不然grep 被把它當成選項
另外一種寫法是函數

wrFP = popen("grep \'\\-rw-rw-r--\'", "w");

優缺點

當popen運行一個程序時,它先啓動shell, 而後當command字符串做爲參數傳遞給它。
優勢是參數擴展是由shell完成,因此可使用各類通配符
缺點是每次打開一個程序,還要啓動一個shell, 也就是說一次popen調用,啓動了兩個進程,從效率和資源看,popen都不太理想。spa

底層管道

popen是高級函數,pipe則是底層調用,它不須要啓動shell來解釋命令,並且它能提供更多對數據的控制.net

語法

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

用法

調用pipe函數時在內核中開闢一塊緩衝區(稱爲管道)用於通訊,它有一個讀端一個寫端,而後經過filedes參數傳出給用戶程序兩個文件描述符,filedes[0]指向管道的讀端,filedes[1]指向管道的寫端(很好記,就像0是標準輸入1是標準輸出同樣)。因此管道在用戶程序看起來就像一個打開的文件,經過read(filedes[0]);或者write(filedes[1]);向這個文件讀寫數據實際上是在讀寫內核緩衝區。pipe函數調用成功返回0,調用失敗返回-1。3d

通訊機制

圖片描述

示例

代碼

//
// Created by         : Harris Zhu
// Filename           : test.c
// Author             : Harris Zhu
// Created On         : 2017-08-19 13:28
// Last Modified      :
// Update Count       : 2017-08-19 13:28
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <string.h>
#include <time.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


int main(int argc, char** argv)
{
    int pipefd[2];
    if (pipe(pipefd) == -1)
    {
        ERR_EXIT("open pipe error");
    }
    pid_t pid;
    pid = fork();
#ifdef SWITCH
    char r_buf[100]={0};
    char w_buf[100]="hello\0";
    switch(pid)
    {
    case -1:
        ERR_EXIT("fork error");
        break;
    case 0:
        close(pipefd[0]);
        printf("son: sending %s, size = %d\n", w_buf, sizeof(w_buf));
        write(pipefd[1], w_buf, sizeof(w_buf));
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
        break;
    default:
        close(pipefd[1]);
        read(pipefd[0], r_buf, 100);
        printf("paraent: receive %s, size=%d\n", r_buf, sizeof(r_buf));
        close(pipefd[0]);
//        exit(EXIT_SUCCESS);
        break;
    }
#else
    if(pid < 0)
    {
        ERR_EXIT("fork error");
    } else {
        if(pid == 0)
        {
            close(pipefd[0]);
            char *w_buf="hello";
            printf("son: sending %s, size = %d\n", w_buf, strlen(w_buf));
            write(pipefd[1], "hello", 5);
            close(pipefd[1]);
            exit(EXIT_SUCCESS);
        } else {
            char r_buf[100]={0};
            close(pipefd[1]);
            read(pipefd[0], r_buf, 5);
            printf("paraent: receive %s, size=%d\n", r_buf, strlen(r_buf));
            close(pipefd[0]);
            exit(EXIT_SUCCESS);
        }
    }
#endif
    return 0;
}

解釋

  1. 父進程調用pipe開闢管道,獲得兩個文件描述符指向管道的兩端。指針

  2. 父進程調用fork建立子進程,那麼子進程也有兩個文件描述符指向同一管道。code

  3. 父進程關閉管道寫端,子進程關閉管道讀端。子進程能夠往管道里寫,父進程能夠從管道里讀,管道是用環形隊列實現的,數據從寫端流入從讀端流出,這樣就實現了進程間通訊。blog

注意點

兩個進程經過一個管道只能實現單向通訊,好比最上面的例子,父進程讀子進程寫,若是有時候也須要子進程讀父進程寫,就必須另開一個管道。
管道的讀寫端經過打開的文件描述符來傳遞,所以要通訊的兩個進程必須從它們的公共祖先那裏繼承管道文件描述符。上面的例子是父進程把文件描述符傳給子進程以後父子進程之間通訊,也能夠父進程fork兩次,把文件描述符傳給兩個子進程,而後兩個子進程之間通訊,總之須要經過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通訊。
通訊進程須要是父子進程關係,這使得它的應用受到了很大限制,這時咱們能夠用命名管道來解決

後言

pipe的使用和原理很是簡單,因此本文不過多解釋展開,若有問題講寫郵件本人

參考:
linux系統編程之管道

相關文章
相關標籤/搜索