linux 進程間通信(一、總論及管道)

1、背景

1.1 參考資料

https://blog.csdn.net/maopig/article/details/77800124

 

2、進程間通信

每個進程有獨立的地址空間,任何一個進程的全局變量在另一個進程中都是看不見的。因此進程間要交換數據必須通過內核在內核中開闢一塊緩衝區,進程1把數據從用戶空間拷貝到內存緩衝區,進程2再從內核緩衝區把數據讀走。內核提供的這種機制稱爲進程間通信(IPC, inter process communication)。

對於32位Linux內核地址空間劃分0~3G爲用戶空間,3~4G爲內核空間。邏輯地址。

3、管道

3.1 匿名管道緩衝區的實現機制

管道是最基本的IPC機制。在內核中開闢一塊緩衝區(稱爲管道 pipe),提供一個讀端文件描述符和一個寫端文件描述符,供用戶通信。

在linux中,管道的實現並沒有專門的數據結構,而是藉助了文件系統的file結構和VFS的索引節點inode。通過將兩個file結構指向同一個臨時的VFS索引節點,而這個索引節點又指向一個物理頁面而實現的。

所上圖所示,兩個file數據結構定義文件操作例程地址(f_op)是不同的,其中一個是向管道寫入數據的例程地址,而另一個是從管道讀取數據的例程地址。就這樣,用戶程序就可以對管道的兩個文件描述符分別進行read、write系統調用(表現的依舊是文件操作),而內核卻利用這種抽象機制實現了管道這一特殊操作。普通管道僅供具有共同祖先的兩個進程之間共享,並且這個祖先已經爲他們建立了供他們使用的管道。因爲文件描述符是針對進程的。

3.2 匿名管道這個緩衝區有多大

內核給我們創建的管道的buffer有多大,是由pipe buf 和緩衝條目的數量共同決定的。pipe buf *條數 == pipe capactiy;

pipe buf定義了內核管道緩存區的容量,這個值由內核設定,可以通過ulimit-a命令查看

從查看到的資源看pipe size 512bytes * 8 = 4096bytes。即一次原子寫入爲4096字節。

當然另一個與之對應的緩衝條目的個數與linux的內核版本是有關聯的,16條;4096*16 = 65536字節。

當管道滿時,

對於O_NONBLOCK disable的:write調用阻塞,知道有程序讀走數據;

對於O_NONBLOCK enable的:write調用返回-1,errno值爲EAGAIN。

把管道寫滿需要多少字符。經驗是65536個。

3.3 匿名管道的實際應用

頭文件:#include <unistd.h>

原型: int pipe(int fd[2]);

函數說明: pipe()會建立管道,並將文件描述符由參數fl數組返回。fd[0]爲管道的讀取端,fd[1]是管道的寫入端。

返回值: 成功 0  失敗 -1, 錯誤原因存於errno中

錯誤代碼:

EMFILE-----進程已用完文件描述符最大量;

ENFILE-----系統已無文件描述符可用;

EFAULT-----參數fd數組地址非法 

 開闢管道之後,父子進程是如何實現通信的,如下圖所示步驟通信

1、父進程調用pipe開闢管道,得到連個文件描述符指向管道兩端;

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

3、父進程關閉管道讀端,子進程關閉寫端,這樣就可以實現父進程寫,子進程讀的進程間通信了。

即讀的進程,要關閉fd[1];   寫的進程要關閉fd[0];

 

3.4匿名管道的限制和優點

限制:

1、匿名管道只能單向通信,要實現雙向通信,要再創建一個管道;

2、管道通信依賴於文件系統,即管道的生命週期隨進程。

3、匿名管腳只能進行在有親緣關係的進程間通信,通常用於父子進程、兄弟進程;

4、管道容量有限

優點:

某些限制,從其他角度看也是優點。

1、自帶同步機制,保證讀寫順序一致------FIFO先進先出

2、管道的通信被稱爲面向字節流,與通信格式無關。

3、單向通信

3.5 Linux 下標準C庫的匿名管道的函數和應用

在應用匿名管道進行編程時,基本的流程是一樣的:先創建一管道,然後併發一個進程,父子進程間通過管道進行數據交換。

頭文件:<stdio.h>

函數原型: FILE *popen(const char *command, const char *type);

參數: command----要執行的命令串;

type---指定要返回的FILE 類型指針是用於讀,還是寫的

返回值: NULL---執行失敗,可用errno查錯誤原因。

其他:就是一個標準I/O流。

通過fgets()來獲取執行輸出。

下面舉個栗子

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BUFFER_SIZE        (1024)


int execute_shell_command(const char *cmd, char * result)
{
    if((cmd == NULL) || (result == NULL))
        return -1;
 
   FILE * stream = NULL;
   if((stream = popen(cmd, "r")) == NULL)
   {
         perror("popen");
   }
   while(fget(result, BUFFER_SIZE, stream) != NULL)
   {
        pclose(stream);
        stream = NULL;
        return 0;
   }       
    
}


void main(void)
{

    char * command = "pwd 2>/dev/null; echo $?";
    char result[BUFFER_SIZE] = {0};
    if(execute_shell_command(command, result)!=-1)
    {
          printf("reslut = %d\n", result);      
    }
    else
    {
         printf("execute shell %s failed\n", command);
    }
    
    
}

 

3.6 Linux 下有名管道

有名管道可以在不具有親緣關係的進程間實現數據通信。與匿名管道不同,有名管道可通過路徑名指出,並且在文件系統中是可見的。匿名管道文件描述符指向的文件(緩衝區)是在內存上的。

有名管道FIFO(First in first out)是特殊的文件,它是嚴格地遵循先進先出規則,即對管道和FIFO的讀總是從開始出返回數據,對它們的寫則是把數據添加到末尾。注意不支持lseek等文件定位操作。

有名管道之所以可以實現進程間的通信在於通過同一路徑名,可以看到同一份資源,這份資源以FIFO的文件形式存在於文件系統中

在創建管道成功之後,就可以使用open、read、write這些函數了。

創建有名管道----在文件系統中創建一個專用文件,該文件用於提供FIFO功能。

頭文件:#include <sys/types.h>

          #include <sys/stat.h>

函數原型: int mkfifo(const char * filename, mode_t mode);

參數: filename-----指定了文件名

         mode-----指定了新建文件的訪問權限,與文件的訪問權限相同,如0644.

O_RDONLY   讀管道

O_WRONLY  寫管道

O_RDWR       讀寫管道

O_NONBLOCK    非阻塞

O_CREAT             如文件不存在,則創建一個新的文件,並用第三的參數爲其設置權限。

O_EXCL            如使用O_CREAT時文件存在,那麼可返回錯誤信息。這一參數可測試文件是否存在。

返回值: 0 函數執行成功; -1:執行失敗,可查詢errno獲取錯誤詳細信息。

創建一個名爲pathname的文件系統節點(文件、設備文件、有名管道),其屬性有mode和dev指定。

頭文件:#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

函數原型:  int mknod(const char * pathname, mode_t mode, dev_t dev);

pathname:

mode: 指定要用到的權限和創建的節點的類型。

權限:(mode&(~umask))

類型: S_IFREG, S_IFCHR, S_IFBLK, S_FIFO 或S_IFSOCK,以分別指定普通文件(創建後爲空),字符特殊文件、塊特殊文件、FIFO(有名管道)、UNIX域套接字。

dev:

mknod是比較老的函數,而mkfifo函數更加簡單和規範。儘量使用mkfifo而不是mknod.

 

需要注意的是:有名管道FIFO這一專用文件,讀寫打開方式有所不同,有兩種方式:阻塞方式與非阻塞方式。在缺省狀態下,open打開的是以阻塞方式打開。

在非阻塞方式下: 只讀方式打開管道,立即返回。以寫方式打開管道,若之前沒有以讀方式打開管道的進程,則返回失敗,錯誤信息爲ENXIO。

在阻塞方式下:當打開一個管道用於讀時,如果沒有打開管道用於寫的進程,則該打開操作將被阻塞,直到有一個進程用寫的方式打開該管道爲止。同樣,先打開寫,也要等有進程打開讀。 

具體使用如下所示:

下面以阻塞的方式打開匿名管道,看看讀寫的阻塞情況。

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO   "/tmp/myfifo"

int main(void)
{
    char* buf = "I am jimmy.";
    int fd;
    if((mkfifo(FIFO, O_CREAT|O_EXCL) < 0)&&(errno != EEXIST))  /*創建有名管道,訪問權限爲644*/
    {
         printf("cannot crate fifoserver\n");      /*錯誤處理*/
                    
    }
    printf("Preparing for reading bytes...\n");
    if(fd = open("p1", O_WRONLY, 0) < 0)
    {
         perror("open");
         exit(-1);
    }
    write(fd, buf, strlen(buf));
     
    
    close(fd);
    sleep(5);   
    unlink(FIFO);
    return 0;
}


int main(void)
{
    char buf[1024] = {0};
    int fd;
   
    if(fd = open("p1", O_RDONLY, 0) < 0)
    {
         perror("open");
         exit(-1);
    }
   if( read(fd, buf, 1024) > 0)
  {
      printf("read %s\n",buf); 
  }
   
    close(fd);
  
    return 0;
}

 

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