LINUX inner-process communication

xiao_dingo發表於2019-05-09

程式間通訊方式

管道

管道是針對對本地計算機的兩個程式之間的通訊而設計的通訊方式,管道建立後,實際獲得兩個檔案描述符,一個讀取另一個寫入。最常見的IPC機制,通過PIPE系統呼叫。管道是單工的,資料只能向一個方向流動,需要雙向通訊時,需要建立起兩個管道。管道的本質是核心中的快取。

管道特性:

  1. 可以通過兩個管道來建立一個雙向的管道
  2. 管道是阻塞性的,當程式從管道中讀取資料,若沒有資料程式會阻塞
  3. 管道有大小限制,管道滿再放則會報錯
  4. 不完整管道
  • 當讀一個寫端已經關閉的管道時,在所有資料被讀取後,read返回0,以表示到達了檔案尾部
  • 如果寫一個讀端已經關閉的管道,剛產生訊號SIGPIPE,如果忽略該訊號或捕捉該訊號並從處理程式返回,則write返回-1,同時errno設定為EPIPE

管道的分類

匿名管道

  1. 在關係程式中進行(父程式各子程式,兄弟程式之間)
  2. 由PIPE系統呼叫
  3. 管道位於核心空間,其實是一塊快取
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

/**
*Desc:扇形多執行緒之間管道通訊
*author:xiao_dingo
*since:2018-03-07
*email:wwc0524@163.com
*/

char *cmd1[3] = {"/bin/cat","/etc/passwd",NULL};
char *cmd2[3] = {"/bin/grep","root",NULL};

int main(void){

        int fd[2];
        if(pipe(fd) < 0){
                perror("pipe error");
        }

        int i = 0;
        pid_t pid;
        for(;i < 2; i++){
                pid = fork();
                if(pid < 0){
                        perror("fork error");
                        exit(1);
                }else if(pid == 0){//child process

                        if(i == 0){
                                close(fd[0]);

                                //將標準輸出重定向到管道的寫端
                                if(dup2(fd[1],STDOUT_FILENO) != STDOUT_FILENO){
                                perror("dup2 error");
                                }
                                close(fd[1]);
                                if(execvp(cmd1[0],cmd1) < 0){
                                        perror("execvp error");
                                        exit(1);
                                }
                                break;
                        }
                        if(i == 1){
                                close(fd[1]);

                                //將標準輸入重定向到管道的讀端
                                if(dup2(fd[0],STDIN_FILENO) != STDIN_FILENO){
                                perror("dup2 error");
                                }       
                                close(fd[0]);
                                if(execvp(cmd2[0],cmd2) < 0){
                                        perror("execvp error");
                                        exit(1);
                                }
                                break;
                        }
                }else{//parent process
                        if(i == 1){
                                close(fd[0]);
                                close(fd[1]);
                                wait(NULL);
                                wait(NULL);
                        }
                }
        }
        exit(0);
}
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

/**
*Desc:不完整管道之間操作
*author:xiao_dingo
*since:2018-03-08
*email:wwc0524@163.com
*/

void sig_handler(int signo){
        if(signo == SIGPIPE){
                printf("sigpipe occured
");
        }
}

void main(void){
        int fd[2];
        if(pipe(fd) < 0){
                perror("pipe error");
                exit(1);
        }
        pid_t pid;
        if((pid = fork()) < 0){
                perror("fork error");
                exit(1);
        }else if(pid > 0){//parent process
                sleep(5);
                close(fd[0]);
                if(signal(SIGPIPE,sig_handler) == SIG_ERR){
                        perror("signal sigpipe error");
                        exit(1);
                }
                char *s = "1234";
                if(write(fd[1],s,sizeof(s)) != sizeof(s)){
                        fprintf(stderr,"%s,%s
",strerror(errno),(errno == EPIPE) ? "EPIPE" : ",UNKNOW");
                }
        }else{//child process
                close(fd[0]);
                close(fd[1]);
        }
}

命名管道(FIFO)

  1. 兩個沒有任何關係的進行之間通訊可以通過命名管道進行資料傳輸,本質是核心中的一塊快取,在檔案系統中以一個特殊的裝置檔案(管道檔案)存在。在檔案系統中的管道檔案只有一個索引塊存放檔案路徑,沒有資料塊,所有資料存放在核心中。
  2. 通過系統呼叫mkfifo建立
  3. 命名管道必須讀和寫同時開啟,否則會進入阻塞
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* pathname,mode_t mode);

訊息佇列

System v IPC物件(訊息佇列,共享記憶體和訊號量)存在於核心中而不是檔案系統中,由使用者控制釋放(使用者管理IPC物件的生命週期),不像管道和釋放由核心控制。IPC物件通過識別符號來引用和訪問,所有的IPC物件在核心空間有唯一標識ID,在使用者空間的唯一標識稱為Key

訊息佇列特性

  1. 訊息佇列是核心中的一個連結串列
  2. 使用者程式將資料傳輸到核心後,核心重新新增一些如使用者ID,組ID,讀寫程式的ID和優先集等相關資訊後並打成一個資料包稱為訊息
  3. 允許一個或者多個程式往訊息佇列中寫訊息和讀訊息,但一個訊息只能被一個程式讀取,讀取完畢就自動刪除
  4. 訊息佇列具有一定的FIFO的特性,訊息可以按照順序傳送到佇列中,也可以幾種不同的方式從佇列中讀取,訊息佇列在核心中用一個唯一的IPC標識ID表示
  5. 訊息佇列的實現包括建立和開啟佇列,傳送訊息,讀取訊息,控制訊息佇列四種操作
  6. linux 系統檢視命令ipcs 刪除ipcrm
#include <sys/msg.h>
int msgget(key_t key,int flag);//查詢
int msgctl(int msgid,int cmd,struct msgid_ds *buf);//控制
int msgsnd(int magid,const void *ptr,szie_t nbytes,int flag);//傳送
ssize_t msgrvc(int msgqid,void *ptr,size_t nbytes,long type,int flag);//接收

共享記憶體

共享記憶體允許系統內兩個或多個程式共享同一塊記憶體空間,並且資料不用在客戶程式和伺服器程式間複製,因此共享記憶體是通訊速度最快的一種IPC。
實現的機制簡單描述如下:一個程式在系統中申請開闢了一塊共享記憶體空間,然後使用這個共享記憶體空間的各個程式分別開啟這個共享記憶體空間,並將這個記憶體空間對映到自己的程式空間上,這樣各個程式就可以共同使用這個共享記憶體空間,就如同使用自己程式地址空間的記憶體一樣
要實現共享記憶體空間,核心做了許多工作:比如給每個共享記憶體塊分發一個“身份證”、允許使用者程式將共享記憶體對映到各自的地址空間上、在程式提出申請以後將共享記憶體和程式地址空間脫離,並在適當的時候講共享記憶體刪除,讓其回到可以被建立的狀態。
使用者利用共享記憶體實現程式間的通訊,實際上就是使用核心提供的服務完成對共享記憶體的建立、對映、脫離、刪除等。當建立並對映成功以後,程式間就能通過共享記憶體實現資料的互動。

核心提供的服務

/**
*shmget實現共享記憶體的建立或者開啟
*當共享記憶體的鍵值key 尚未存在時,呼叫這個函式並且指定shmflg 引數為IPC_CREAT 可以建立一個大小為 size 的共享記憶體空間。假設key指定的共享記憶體已經存在,呼叫這個函式可以開啟這個共享記憶體,但不會建立。
*
*/
int shmget(key_t key, size_t size, int shmflg);
/**
*該函式將一個共享記憶體空間對映到呼叫程式的地址空間上,並且返回在程式地址空間中的地址。使用者拿到改地址後就可以通過這個地址間接的訪問共享記憶體。
*shmid 引數就是shmget 函式的返回值,shmaddr 引數實際上是指出了共享記憶體對映到程式地址空間上的位置,但是我們一般不會指定這個地址,而是令其為NULL ,讓核心選擇一個合適的地址。shmflg 引數是配合著shmaddr 引數使用的,在shmaddr 為NULL時就變得沒有實際意義,因此通常指定為0
*/
void *shmat(int shmid,const void* shmaddr,int shmflg);
/**
*這個函式將一個程式已經對映了的共享記憶體脫離程式地址空間。shmaddr 引數就是在程式地址空間的地址,實際就是shmat 函式的返回值
*/
int shmdt(const void* shmaddr);
/**
*此函式實際上有很多的功能,但是我們通長用它將一個已經建立了的共享記憶體刪除,所謂刪除實際就是將它放回可以被建立的共享記憶體佇列中。指定cmd 引數為IPC_RMID,就可以將shmid鍵值指定的共享記憶體刪除,而buf實際上是可以獲取這共享記憶體在核心中的狀態,如果不想了解可以指定為0
*/
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

程式訊號量

用於程式間的huchi與同步,每種共享資源對應一個訊號量,為了便於大量共享資源的操作引入了訊號量集,可對所有資訊量一次性操作,對訊號量集中所有操作可以要求全部成功,也可以部分成功。
它是一個特殊變數,只允許對它進行等待和傳送訊號這兩種操作。

  • P(訊號量變數sv):等待。如果sv大於0,減小sv。如果sv為0,掛起這個程式的執行。
  • V(訊號量變數sv):傳送訊號。如果有程式被掛起等待sv,使其恢復執行。如果沒有進行被掛起等待sv,增加sv。
#include  <sys/sem.h>
/**
*
*
*/
int semget(key_t key,int nsems,int semflg);

相關文章