提要
Linux中進程間的通訊機制主要有:管道FIFO,訊號量,訊息,共用記憶體區,通訊端。程式員在使用中可以根據不同的需求進行選擇。
管道
管道是由核心管理的一個緩衝區,相當於我們放入記憶體中的一個紙條。管道的一端串連一個進程的輸出。這個進程會向管道中放入資訊。管道的另一端串連一個進程的輸入,這個進程取出被放入管道的資訊。一個緩衝區不需要很大,它被設計成為環形的資料結構,以便管道可以被迴圈利用。當管道中沒有資訊的話,從管道中讀取的進程會等待,直到另一端的進程放入資訊。當管道被放滿資訊的時候,嘗試放入資訊的進程會等待,直到另一端的進程取出資訊。當兩個進程都終結的時候,管道也自動消失。
下面是一個簡單的例子.先看兩個linux中常用的命令
grep
功能說明:尋找檔案裡合格字串。
語 法:grep [-abcEFGhHilLnqrsvVwxy][-A<顯示列數>][-B<顯示列數>][-C<顯示列數>][-d<進行動作>][-e<範本樣式>][-f<範本檔案>][--help][範本樣式][檔案或目錄...]
補充說明:grep 指令用於尋找內容包含指定的範本樣式的檔案,如果發現某檔案的內容符合所指定的範本樣式,預設grep指令會把含有範本樣式的那一列顯示出來。若不指定任何檔案名稱,或是所給予的檔案名稱為“-”,則grep指令會從標準輸入裝置讀取資料。
ls
功能說明:列出目標目錄中所有的子目錄和檔案。
語 法:ls [選項] [目錄名]
通過 " | " 可以建立一個管道,把這兩個進程連結在一起。
終端運行
目錄下檔案名稱中帶in的檔案或檔案夾會顯示出來:
在linux的核心中實際上執行的操作如下:
1.調用pipe() 系統調用,假設pipe()返迴文件描述符3(管道的讀通道)和4(管道的寫通道)。
2.兩次調用fork() 系統調用。
3.兩次調用close() 系統調用來釋放檔案描述符3和4.
使用管道
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>int pipe_default[2];int main(){pid_t pid;char buffer[32];memset(buffer, 0, 32);if(pipe(pipe_default) < 0){printf("Faild to create pipe.\n");return 0;}//Child processif(0 == (pid =fork())){close(pipe_default[1]);if(read(pipe_default[0], buffer, 32) > 0){printf("Receive data from server, %s!\n", buffer);}close(pipe_default[0]);}else //Parent process{close(pipe_default[0]);if(-1 != write(pipe_default[1], "Fuck", strlen("Fuck"))){printf("Send data to client,Fuck!\n");}close(pipe_default[1]);waitpid(pid,NULL,0);}return 1;}
主要解釋一下幾個相關的函數。
int pipe(int filedes[2]);
filedes[0]用於讀出資料,讀取時必須關閉寫入端,即close(filedes[1]);
filedes[1]用於寫入資料,寫入時必須關閉讀取端,即close(filedes[0])
void *memset(void *s, char ch, size_t n);
將s中前n個位元組 (typedef unsigned int size_t)用 ch 替換並返回 s 。
pid_t waitpid(pid_t pid,int * status,int options);
暫時停止目前進程的執行,直到有訊號來到或子進程結束。
read和write分別向管道中傳送資料。
訊號量
訊號量在建立時需要設定一個初始值,表示同時可以有幾個任務可以訪問該訊號量保護的共用資源,初始值為1就變成互斥鎖(Mutex),即同時只能有一個任務可以訪問訊號量保護的共用資源。
一個任務要想訪問共用資源,首先必須得到訊號量,擷取訊號量的操作將把訊號量的值減1,若當前訊號量的值為負數,表明無法獲得訊號量,該任務必須掛起在該訊號量的等待隊列等待該訊號量可用;若當前訊號量的值為非負數,表示可以獲得訊號量,因而可以立刻訪問被該訊號量保護的共用資源。
當任務訪問完被訊號量保護的共用資源後,必須釋放訊號量,釋放訊號量通過把訊號量的值加1實現,如果訊號量的值為非正數,表明有任務等待當前訊號量,因此它也喚醒所有等待該訊號量的任務。
PV原子操作的具體定義如下:(好好理解,很重要的啊)
● P操作:如果有可用的資源(訊號量值>0),則此操作所在的進程佔用一個資源(此時訊號量值減1,進入臨界區代碼);如果沒有可用的資源(訊號量值=0),則此操作所在的進程被阻塞直到系統將資源分派給該進程(進入等待隊列,一直等到資源輪到該進程)。
● V操作:如果在該訊號量的等待隊列中有進程在等待資源,則喚醒一個阻塞進程;如果沒有進程等待它,則釋放一個資源(即訊號量值加1)。
一個實現PV操作的例子。
#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#define DELAY_TIME 5union semun{int val;struct semid_ds *buf;unsigned short *array;};int initsem(int sem_id, int init_value);int del_sem(int sem_id);int sem_p(int sem_id);int sem_v(int sem_id);int main(void){pid_t result;int sem_id;sem_id = semget(ftok(".",'a'),1,0666|IPC_CREAT);init_sem(sem_id, 0);result = fork();if(result == -1){perror("fork error!\n");}else if(result == 0){printf("Child process at id:%d, I run first.\n",getpid());sleep(DELAY_TIME);sem_v(sem_id);}else{sem_p(sem_id);printf("Parent process at id:%d\n",getpid());sem_v(sem_id);del_sem(sem_id);}return 0;}int init_sem(int sem_id, int init_value){union semun sem_union;sem_union.val = init_value;if(semctl(sem_id, 0, SETVAL, sem_union) == 1){perror("Init semaphore!");return -1;}return 0;}int del_sem(int sem_id){union semun sem_union;if(semctl(sem_id, 0, IPC_RMID, sem_union) == 1){perror("Delete semaphore!");return -1;}}int sem_p(int sem_id){struct sembuf sem_b;sem_b.sem_num = 0;sem_b.sem_op = -1;sem_b.sem_flg = SEM_UNDO;if(semop(sem_id, &sem_b, 1) == -1){perror("P operation.");return -1;}return 0;}int sem_v(int sem_id){struct sembuf sem_b;sem_b.sem_num = 0;sem_b.sem_op = 1;sem_b.sem_flg = SEM_UNDO;if(semop(sem_id, &sem_b, 1) == -1){perror("P operation.");return -1;}return 0;}
在程式中,由於使用了訊號量,確保了每次執行子進程運行在父進程之前。
參考
Linux處理序間通訊(六)---訊號量通訊之semget()、semctl()、semop()及其基礎實驗 - http://blog.csdn.net/mybelief321/article/details/9086151
Linux環境處理序間通訊(一) - http://www.ibm.com/developerworks/cn/linux/l-ipc/part1/