Linux進程間通信之管道

linux下進程間通信的幾種主要手段:
1.管道(Pipe)及有名管道(named pipe):管道可用於具有親緣關係進程間的通信,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關係進程間的通信;
2.信號(Signal):信號是比較複雜的通信方式,用於通知接受進程有某種事件生,除了用於進程間通信外,進程還可以發送信號給進程本身;linux除了支持Unix早期 信號語義函數sigal外,還支持語義符合Posix.1標準的信號函數sigaction(實際上, 該函數是基於BSD的,BSD爲了實現可靠信號機制,又能夠統一對外接口,sigaction函數重新實現了signal函數);
3. 消息隊列:消息隊列是消息的鏈接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程可以向隊列中添加消息,被賦予讀權限的進程則可以讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩衝區大小受限等缺點。
4.共享內存:使得多個進程可以訪問同一塊內存空間,是最快的可用IPC形式。是針其他通信機制運行效率較低設計的。往往與其它通信機制,如信號量結合使用, 來達到進程間的同步及互斥。
5.信號量(semaphore):主要作爲進程間以及同一進程不同線程之間的同步手段。
6.套接字(Socket):更爲一般的進程間通信機制,可用於不同機器之間的進程間通信。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix 系統上:Linux和System V的變種都支持套接字。

(以上來自網絡)

管道

管道是由內核管理的一個緩衝區(buffer)。管道的一端連接一個進程的輸出。這個進程會向管道中放入信息。管道的另一端連接一個進程的輸入,這個進程取出被放入管道的信息。一個緩衝區不需要很大,它被設計成爲環形的數據結構,以便管道可以被循環利用。當管道中沒有信息的話,從管道中讀取的進程會等待,直到另一端的進程放入信息。當管道被放滿信息的時候,嘗試放入信息的進程會等待,直到另一端的進程取出信息。當兩個進程都終結的時候,管道也自動消失。
這裏寫圖片描述

使用實例:

#include <stdio.h>
#include <unistd.h>  //fork pipe io read/write 
#include <error.h>
#include <string.h>

int main()
{   
    // fd[0] 讀, fd[1]寫
    int fd[2];
    pid_t pid;
    int ret;

    //建立管道
    ret = pipe(fd);
    if (ret < 0) {
        printf("create pipe failed.\n");
        return 1;
    }

    //創建子進程
    pid = fork();
    printf("pid is %d\n", pid);
    if (pid < 0) {
        printf("fork sub thread failed.\n");
        return 1;
    } else if (pid == 0) {
        //子進程相關操作
        //關閉讀
        printf("current is child write!\n");
        close(fd[0]);
        char *message = "hello, world!\n";
        write(fd[1], message, strlen(message));

    } else {
        //父進程讀
        printf("current is parent read!\n");
        close(fd[1]);
        char readMessage[1024];
        memset(readMessage, 0, sizeof(readMessage));
        ret = read(fd[0], readMessage, sizeof(readMessage));
        printf("%d %s\n", ret, readMessage);
    }

    return 0;
}

1.兩個進程通過一個管道只能實現單向通信。比如上面的例子,父進程寫子進程讀,如果有時候也需要子進程寫父進程讀,就必須另開一個管道。
2.管道的讀寫端通過打開的文件描述符來傳遞,因此要通信的兩個進程必須從它們的公共祖先那裏繼承管道文件描述符。上面的例子是父進程把文件描述符傳給子進程之後父子進程之間通信,也可以父進程fork兩次,把文件描述符傳給兩個子進程,然後兩個子進程之間通信, 總之需要通過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通信。 也就是說,管道通信是需要進程之間有關係。

命名管道

由於基於fork機制,所以管道只能用於父進程和子進程之間,或者擁有相同祖先的兩個子進程之間 (有親緣關係的進程之間)。爲了解決這一問題,Linux提供了FIFO方式連接進程。FIFO又叫做命名管道(named PIPE)。

FIFO (First in, First out)爲一種特殊的文件類型,它在文件系統中有對應的路徑。當一個進程以讀(r)的方式打開該文件,而另一個進程以寫(w)的方式打開該文件,那麼內核就會在這兩個進程之間建立管道,所以FIFO實際上也由內核管理,不與硬盤打交道。之所以叫FIFO,是因爲管道本質上是一個先進先出的隊列數據結構,最早放入的數據被最先讀出來(好像是傳送帶,一頭放貨,一頭取貨),從而保證信息交流的順序。FIFO只是借用了文件系統(file system, 參考Linux文件管理背景知識)來爲管道命名。寫模式的進程向FIFO文件中寫入,而讀模式的進程從FIFO文件中讀出。當刪除FIFO文件時,管道連接也隨之消失。FIFO的好處在於我們可以通過文件的路徑來識別管道,從而讓沒有親緣關係的進程之間建立連接。

創建命名管道的系統函數有兩個:mknod和mkfifo。兩個函數均定義在頭⽂文件sys/stat.h。

#include <sys/types.h>  
#include <sys/stat.h>  
int mkfifo(const char *filename, mode_t mode);  
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0); 

這兩個函數都能創建一個FIFO文件,注意是創建一個真實存在於文件系統中的文件,filename指定了文件名,而mode則指定了文件的讀寫權限。

mknod是比較老的函數,而使用mkfifo函數更加簡單和規範,所以建議在可能的情況下,儘量使用mkfifo而不是mknod。

與打開其他文件一樣,FIFO文件也可以使用open調用來打開。

open(const char *path, O_RDONLY);//1  
open(const char *path, O_RDONLY | O_NONBLOCK);//2  
open(const char *path, O_WRONLY);//3  
open(const char *path, O_WRONLY | O_NONBLOCK);//4 

使用實例:
1.寫

#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  
#include <fcntl.h>  
#include <limits.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#define MSG_SIZE 1024
// WRITE
int main()
{

    int ret;
    int pipe_fd;
    int send_msg = 0;
    const char *fifo_name = "/tmp/my_fifo";
    char buffer[PIPE_BUF] = "hello, name pipe!";
    //檢測文件是否存在
    if (access(fifo_name, F_OK) == -1) {
    //創建fifo管道
        ret = mkfifo(fifo_name, 0777);
        if (ret <  0) {

            printf("create fifo failed.\n");
            return 1;
        }
    }   

    //打開fifo管道(只寫阻塞方式打開)
    pipe_fd = open(fifo_name, O_WRONLY);
    printf("now pipe id is %d.\n", pipe_fd);

    if (pipe_fd != -1) {
        while (send_msg < MSG_SIZE) {
            ret = write(pipe_fd, buffer, sizeof(buffer));
            if (ret < 0) {
                printf("write pipe failed.\n");
                return 1;
            }
            send_msg += ret;
        }
        close(pipe_fd);
    } else {
        printf("open pipe failed.\n");
        return 1;
    }


    return 0;
}

2.讀


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


//read 
int main()  
{  
    char *fifo_name = "/tmp/my_fifo";
    int pipe_fd;  
    int ret;  
    char buffer[PIPE_BUF + 1];  
    int bytes = 0;  

    memset(buffer,'\0', sizeof(buffer));  
    pipe_fd = open(fifo_name, O_RDONLY);  
    printf("pipe fd is %d\n", pipe_fd);  

    if (pipe_fd != -1) {  
        do {  
            ret = read(pipe_fd, buffer, PIPE_BUF);  
            bytes += ret;
            printf("read from fifo is %s\n", buffer);
        } while(ret > 0);  
        close(pipe_fd);  
    } else {  
        printf("open pipe fd failed!\n");
        return 1; 
    }  

    printf("Process %d finished, %d bytes read\n", getpid(), bytes);  
    return 0; 
}  

[root@localhost workspace]# ./namepipe &
[1] 5128
[root@localhost workspace]# ./namepipe2
now pipe id is 3.
pipe fd is 3
read from fifo is hello, name pipe!
read from fifo is hello, name pipe!
Process 5129 finished, 4096 bytes read
[1]+ Done ./namepipe

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