進程間通訊

原文連接:http://www.orlion.ga/1072/web

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

   

1、管道函數

    管道是一種最基本的IPC機制,由pipe函數建立:spa

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

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

    開啓了管道以後能夠按以下的步驟通訊:繼承

   

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

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

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

 

    例:內存

#include <stdlib.h>
#include <unistd.h>
#define MAXLINE 80
int main(void)
{
        int n;
        int fd[2];
        pid_t pid;
        char line[MAXLINE];
        if (pipe(fd) < 0) {
                perror("pipe");
                exit(1);
        }
        if ((pid = fork()) < 0) {
                perror("fork");
                exit(1);
        }
        if (pid > 0) { /* parent */
                close(fd[0]);
                write(fd[1], "hello world\n", 12);
                wait(NULL);
        } else {       /* child */
                close(fd[1]);
                n = read(fd[0], line, MAXLINE);
                write(STDOUT_FILENO, line, n);
        }
        return 0;
}

    使用管道有一些限制:

  • 兩個進程經過一個管道只能實現單向通訊,好比上面的例子,父進程寫子進程讀,若是有時候也須要子進程寫父進程讀,牛必須另開一個管道。

  • 管道的讀寫端經過打開的文件描述符來傳遞。所以要通訊的兩個進程必須從它們的公共祖先那裏繼承管道文件描述符。上面的例子是父進程把文件描述符傳給子進程以後父子進程之間通訊,也能夠父進程fork兩次,把文件描述符傳給兩個子進程,而後兩個子進程之間通訊,總之須要經過fork傳遞文件描述符使兩個進程都能訪問同一管道,他們才能通訊。

 

    使用管道須要注意一下4點(假設都是阻塞I/O操做,沒有設置O_NONBLOCK標誌):

    1. 若是全部指向管道寫端的文件描述符都關閉了(管道寫端的引用計數爲0),而仍然有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會返回0,就像讀到文件末尾同樣。

    2. 若是有指向管道寫端的文件描述符沒關閉(管道寫端的引用計數大於0),而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀數據,那麼管道中剩餘的數據都被讀取後,再次read會阻塞,直到管道中有數據可讀了纔讀取數據並返回。

    3. 若是全部指向管道讀端的文件描述符都關閉了(關閉讀端的引用計數等於0),這時有進程向管道的寫端write,那麼該進程會收到信號SIGPIPE,一般會致使進程異常終止。

    4. 若是有指向管道讀端的文件描述符沒關閉(管道讀端的引用計數大於0),而持有管道讀端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那麼在管道被寫滿時再次write會阻塞,直到管道中有了空位置才寫入數據並返回。

 

2、其餘IPC機制

    進程間通訊必須經過內核提供了通道,並且必須有一種方法在進程中標示內核提供的某個通道,上一節講的管道是用打開的文件描述符來標示的。若是要互相通訊的幾個進程沒有從公共祖先那裏繼承文件描述符就不能通訊了。內核提供一條通道不是問題,問題是如何標識這條通道才能使各進程均可以訪問它?文件系統中的路徑名是全局的,各進程均可以訪問,所以能夠用文件系統中的路徑名來標識一個IPC通道。

    FIFO和Unix Domain Socket這兩種IPC機制都是利用文件系統中的特殊文件來標識的,能夠用mkfifo命令建立一個FIFO文件:

  

     FIFO文件在磁盤上沒有數據塊,僅用來標識內核中的一條通道,各進程能夠打開這個文件進行read/write,其實是在讀寫內核通道(根本緣由在於這個file結構體所指向的read、write函數和常規文件不同),這樣就實現了進程間通訊。

    Socket和FIFO的原理相似,也須要一個特殊的socket文件來標識內核中的通道,例如/var/run目錄下有不少系統服務的socket文件,文件類型s表示socket,這些文件在磁盤上沒有數據塊。Unix Domail Socket是目前最普遍使用的IPC機制。

 

    總結一下進程間通訊的機制:

    1.父進程經過fork能夠將打開文件的描述符傳遞給子進程

    2.子進程結束時,父進程調用wait能夠獲得子進程的終止信息

    3.幾個進程能夠在文件系統中讀寫某個共享文件,也能夠經過給文件加鎖來實現進程間同步

    4.進程之間互發信號,通常使用SIGUSR1和SIGUSR2實現用戶自定義功能

    5.管道

    6.FIFO

    7.mmap函數,幾個進程能夠映射同一內存區

    8.SYS V IPC,之前的SYS V UNIX系統實現的IPC機制,包括消息隊列、信號量和共享內存,如今已經基本廢棄

    9.UNIX Domain Socket,目前最普遍使用的IPC機制

相關文章
相關標籤/搜索