linux 之进程间通信-------------InterProcess Communication
進(jìn)程間通信至少可以通過傳送打開文件來實現(xiàn),不同的進(jìn)程通過一個或多個文件來傳遞信息,事實上,在很多應(yīng)用系統(tǒng)里,都使用了這種方法。但一般說來,進(jìn)程間 通信(IPC:InterProcess Communication)不包括這種似乎比較低級的通信方法。Unix系統(tǒng)中實現(xiàn)進(jìn)程間通信的方法很多,而且不幸的是,極少方法能在所有的Unix系 統(tǒng)中進(jìn)行移植(唯一一種是半雙工的管道,這也是最原始的一種通信方式)。而Linux作為一種新興的操作系統(tǒng),幾乎支持所有的Unix下常用的進(jìn)程間通信 方法:管道、消息隊列、共享內(nèi)存、信號量、套接口等等。
1、管道(pipe)
管道是進(jìn)程間通信中最古老的方式,它包括無名管道和有名管道兩種,
無名管道用于父進(jìn)程和子進(jìn)程間的通信,
有名管道用于運行于同一臺機(jī)器上的任意兩個進(jìn)程間的通信。
無名管道由pipe()函數(shù)創(chuàng)建:
#include? <unistd.h>
int pipe(int? filedis[2]);
參數(shù)filedis返回兩個文件描述符:filedes[0]為讀而打開,filedes[1]為寫而打開;filedes[1]的輸出是filedes[0]的輸入
1 1 #include <stdio.h> 2 2 #include <string.h> 3 3 #include <stdlib.h> 4 4 #include <errno.h> 5 5 #include <unistd.h> 6 6 7 7 #define INPUT 0 8 8 #define OUTPUT 1 9 9 10 10 int main() 11 11 { 12 12 int file_descriptors[2]; 13 13 pid_t pid; 14 14 char buf[256]; 15 15 char send1[256]; 16 16 int returned_count; 17 17 18 18 //創(chuàng)建無名管道 19 19 pipe(file_descriptors); 20 20 //創(chuàng)建子進(jìn)程 21 21 if((pid=fork()) == -1) 22 22 { 23 23 printf("Error in fork\n"); 24 24 exit(1); 25 25 } 26 26 //執(zhí)行子進(jìn)程 27 27 if(pid == 0) 28 28 { 29 29 printf("int the child process....\n"); 30 30 fgets(send1,256, stdin); 31 31 32 32 //子進(jìn)程向父進(jìn)程寫數(shù)據(jù),關(guān)閉管道的讀端; 33 33 close(file_descriptors[INPUT]); 34 34 write(file_descriptors[OUTPUT], send1, strlen(send1)); 35 35 exit(0); 36 36 } 37 37 else //執(zhí)行父進(jìn)程 38 38 { 39 39 printf("int the parent process...\n"); 40 40 //父進(jìn)程從管道讀取子進(jìn)程寫的數(shù)據(jù),關(guān)閉管道的寫端 41 41 close(file_descriptors[OUTPUT]); 42 42 returned_count = read(file_descriptors[INPUT], buf, sizeof(buf)); 43 43 printf("%d bytest of data received from child process: %s\n" 44 44 ,returned_count,buf); 45 45 } 46 46 47 47 return 0; 48 48 } 49 50 [root@cp ~]# ./a.out 51 int the parent process... 52 int the child process.... 53 lkfjasldkf 54 11 bytest of data received from child process: lkfjasldkf在linux系統(tǒng)下,有名管道可由兩種方式創(chuàng)建:命令行方式mknod? 系統(tǒng)調(diào)用 和函數(shù)mkfifo。
生成了有名管道后,就可以使用一般的文件I/O函數(shù)如open, close, read, write等來對它進(jìn)行操作。
什么是命名管道稱為FIFO文件,它是一種特殊類型的文件,它在文件系統(tǒng)中以文件名的形式存在;
由于Linux中所有的事物都可被視為文件,所以對命名管道的使用也就變得與文件操作非常統(tǒng)一,也使得它的使用非常方便,同時我們也可以像平常的文件名一樣在命令中使用。
使用下面兩個函數(shù)均可以創(chuàng)建一個命名管道,函數(shù)原型如下:
#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);
這兩個函數(shù)都能創(chuàng)建一個FIFO文件,注意是創(chuàng)建一個真實存在于文件系統(tǒng)中的文件,filename指定了文件名,而mode則指定了文件的讀寫權(quán)限;
mknod 是比較老的函數(shù),而使用mkfifo函數(shù)更加簡單和規(guī)范,所以建議在可能的情況下,盡量使用mkfifo而不是mknod。
訪問命名管道
1)打開FIFO文件
與打開其他文件一樣,FIFO文件也可以使用open調(diào)用來打開。注意,mkfifo函數(shù)僅僅只是創(chuàng)建一個FIFO文件,要使用命名管道還要將其打開。
注意點:
1、程序不能以O(shè)_RDWR模式打開FIFO文件進(jìn)行讀寫操作,而其行為沒有明確定義。因為假如一個管道以讀/寫方式打開,進(jìn)程就會讀回自己的輸出,同時我們通常使用FIFO只是為了單向的數(shù)據(jù)傳遞
2、傳遞給open調(diào)用的是FIFO的路徑名,而不是正常的文件。
打開FIFO文件通常有四種方式
open(const? char *path,? O_RDONLY);?? //只讀? 并默認(rèn)是阻塞
open(const? char? *path,? O_RDONLY | O_NOBLOCK);? //無阻塞只讀
open(const? char? *path,?? O_WRONLY);?? //只寫 并默認(rèn)是阻塞
open(const? char? *path,??? O_WRONLY | O_NONBLOCK);? //無阻塞只寫
open調(diào)用的阻塞是怎么一回事? 很簡單,對于只讀(O_RDONLY)打開的FIFO文件,如果open調(diào)用是阻塞的,除非有一個進(jìn)程以寫方式打開同一個FIFO, 否則它讀不到數(shù)據(jù)就不會返回; 如果open調(diào)用是非阻塞的,則即使沒有其他進(jìn)程以寫方式打開同一FIFO方式,open調(diào)用將成功并立即返回。
對于以只寫方式(O_WRONLY)打開的FIFO文件,如果open調(diào)用是阻塞的(即第二個參數(shù)為O_WRONLY),open調(diào)用將被阻塞,直到有一 個進(jìn)程以只讀方式打開同一個FIFO文件為止;如果open調(diào)用是非阻塞的(即第二個參數(shù)為O_WRONLY?| O_NONBLOCK),open總會立即返回,但如果沒有其他進(jìn)程以只讀方式打開同一個FIFO文件,open調(diào)用將返回-1,并且FIFO也不會被打 開。
使用FIFO實現(xiàn)進(jìn)程間的通信
兩個進(jìn)程如何通過FIFO實習(xí)通信,首先要有兩個進(jìn)程,一個源文件為fifowrite.c,? 它在需要時創(chuàng)建管道,然后向管道寫入數(shù)據(jù),數(shù)據(jù)有文件Data.txt提供,大小為10M, 內(nèi)容都是字符‘0’, 另一個源文件為fiforead.c,它從FIFO中讀取數(shù)據(jù),并把讀到的數(shù)據(jù)保存到另一個文件DataFormFIFO.txt中,
命名管道的安全問題
前面指的是兩個進(jìn)程之間通信問題,也就是說,一個進(jìn)程向FIFO文件寫數(shù)據(jù),而另一個進(jìn)程從FIFO文件中讀取數(shù)據(jù)。
假如只使用一個FIFO文件,如果有多個進(jìn)程同時向同一個FIFO文件寫數(shù)據(jù),而只有一個讀FIFO進(jìn)程在同一個FIFO文件中讀取數(shù)據(jù)時,則會發(fā)生數(shù)據(jù)塊的相互交錯問題。而在實際中多個不同進(jìn)程向一個FIFO讀進(jìn)程發(fā)送數(shù)據(jù)是很常見的;
為了解決這一問題,就是讓寫操作的原子化。即: 系統(tǒng)規(guī)定:在一個以O(shè)_WRONLY(阻塞方式)打開的FIFO中,如果寫入的數(shù)據(jù)長度小于等待PIPE_BUF,? 結(jié)果是 要么寫入全部字節(jié), 要么 一個字節(jié)都不寫入。??? 如果所有的寫請求都是發(fā)往一個阻塞的FIFO時,并且每個寫請求的數(shù)據(jù)長度小于等于PIPE_BUF字節(jié),系統(tǒng)就可以確保數(shù)據(jù)決不會交錯在一起;
為了數(shù)據(jù)的安全,我們很多時候要采用阻塞的FIFO,讓寫操作變成原子操作。
//fiforead.c1 #include <unistd.h> 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <fcntl.h> 5 #include <sys/types.h> 6 #include <limits.h> 7 #include <string.h> 8 9 int main() 10 { 11 int pipe_fd = -1; 12 int data_fd = -1; 13 const char *fifo_name = "./my_fifo"; 14 int res = 0; 15 int open_mode = O_RDONLY; 16 char buffer[PIPE_BUF + 1]; 17 int bytes_read = 0; 18 int bytes_write = 0; 19 20 memset(buffer, '\0', sizeof(buffer)); 21 printf("Process %d opening FIFOO O_RDONLY \n", getpid()); 22 23 //以只讀阻塞方式打開管道文件, 名字必須與fifowrite.c文件中的FIFO同名 24 pipe_fd = open(fifo_name, open_mode); 25 26 //以只寫方式創(chuàng)建保存數(shù)據(jù)的文件 27 data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644); 28 printf("Process %d result %d\n", getpid(), pipe_fd); 29 30 if(pipe_fd != -1) 31 { 32 do 33 { 34 //讀取FIFO中的數(shù)據(jù),并把它保存在文件DataFormFIFO.txt文件中 35 res = read(pipe_fd, buffer, PIPE_BUF); 36 bytes_write = write(data_fd, buffer, res); 37 bytes_read += res; 38 }while(res >0); 39 close(pipe_fd); 40 close(data_fd); 41 } 42 else 43 { 44 exit(EXIT_FAILURE); 45 } 46 printf("Process %d finished, %d bytes read \n", getpid(), bytes_read); 47 exit(EXIT_SUCCESS); 48 return 0; 49 }
[root@cp pipe]# gcc -o write fifowrite.c
[root@cp pipe]# ./write
//該線程處于阻塞狀態(tài);
1 #include <unistd.h> 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <fcntl.h> 5 #include <sys/types.h> 6 #include <limits.h> 7 #include <string.h> 8 9 int main() 10 { 11 int pipe_fd = -1; 12 int data_fd = -1; 13 const char *fifo_name = "./my_fifo"; 14 int res = 0; 15 int open_mode = O_RDONLY; 16 char buffer[PIPE_BUF + 1]; 17 int bytes_read = 0; 18 int bytes_write = 0; 19 20 memset(buffer, '\0', sizeof(buffer)); 21 printf("Process %d opening FIFOO O_RDONLY \n", getpid()); 22 23 //以只讀阻塞方式打開管道文件, 名字必須與fifowrite.c文件中的FIFO同名 24 pipe_fd = open(fifo_name, open_mode); 25 26 //以只寫方式創(chuàng)建保存數(shù)據(jù)的文件 27 data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644); 28 printf("Process %d result %d\n", getpid(), pipe_fd); 29 30 if(pipe_fd != -1) 31 { 32 do 33 { 34 //讀取FIFO中的數(shù)據(jù),并把它保存在文件DataFormFIFO.txt文件中 35 res = read(pipe_fd, buffer, PIPE_BUF); 36 bytes_write = write(data_fd, buffer, res); 37 bytes_read += res; 38 }while(res >0); 39 close(pipe_fd); 40 close(data_fd); 41 } 42 else 43 { 44 exit(EXIT_FAILURE); 45 } 46 printf("Process %d finished, %d bytes read \n", getpid(), bytes_read); 47 exit(EXIT_SUCCESS); 48 return 0; 49 }
//[root@cp pipe]# gcc -o write fifowrite.c
[root@cp pipe]# ./write
2、消息隊列
3、共享內(nèi)存
共享內(nèi)存是運行在同一臺機(jī)器上的進(jìn)程間通信最快的方式,因為數(shù)據(jù)不需要在不同的進(jìn)程間復(fù)制。通常由一個進(jìn)程創(chuàng)建一塊共享內(nèi)存區(qū),其余進(jìn)程對這塊內(nèi)存區(qū)進(jìn)行讀寫。得到共享內(nèi)存有兩種方式:映射/dev/mem設(shè)備和內(nèi)存映像文件。前一種方式不給系統(tǒng)帶來額外的開銷,但現(xiàn)實中并不常用,因為它控制存取的將是實際的物理內(nèi)存,在linux系統(tǒng)下,這只有通過限制linux系統(tǒng)存取的內(nèi)存才可以做到,這當(dāng)然不太實際。常用的方式是通過shmxxx函數(shù)族來實現(xiàn)利用共享內(nèi)存進(jìn)行存儲的。
首先是用的函數(shù)是shmget,它獲得一個共享存儲標(biāo)識符。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t? key, int? size,? int flag);
這個函數(shù)有點類似malloc函數(shù),系統(tǒng)按照請求分配size大小的內(nèi)存用作共享內(nèi)存。linux系統(tǒng)內(nèi)核中每個IPC結(jié)構(gòu)都有一個非負(fù)整數(shù)的標(biāo)識符,這樣對一個消息隊列發(fā)送消息時總要引用標(biāo)識符就可以了。這個標(biāo)識符是內(nèi)核由IPC結(jié)構(gòu)的關(guān)鍵字得到的,這個關(guān)鍵字,就是上面第一個函數(shù)的key。 數(shù)據(jù)類型key_t是頭文件sys/types.h 中定義的,它是一個長整形的數(shù)據(jù)。
當(dāng)共享內(nèi)存創(chuàng)建后,其余進(jìn)程可以調(diào)用shmat() 將其連接到自身的地址空間中;
void? *shmat(int? shmid, void *addr,? int? flag);
shmid 為shmget函數(shù)返回的共享存儲標(biāo)識符, addr 和flag參數(shù)決定了以什么方式來確定連接的地址,函數(shù)的返回值即是該進(jìn)程數(shù)據(jù)段所連接的實際地址, 進(jìn)程可以對此進(jìn)程進(jìn)行讀寫操作;
使用共享存儲來實現(xiàn)進(jìn)程間通信的注意點是對數(shù)據(jù)存取的同步,必須確保當(dāng)一個進(jìn)程去讀取數(shù)據(jù)時,它所想要的數(shù)據(jù)已經(jīng)寫好了。通常,信號量被要來實現(xiàn)對共享存儲數(shù)據(jù)存取的同步,另外,可以通過使用shmctl函數(shù)設(shè)置共享存儲內(nèi)存的某些標(biāo)志位如SHM_LOCK,
SHM_UNLOCK等來實現(xiàn)。
4、信號量
為了防止出現(xiàn)因多個程序同時訪問一個共享資源而引發(fā)的一系列問題,我們需要一種方法,它可以通過生成并使用令牌來授權(quán),在任一時刻只能有一個執(zhí)行線程訪問代碼的臨界區(qū)域。臨界區(qū)域是指執(zhí)行數(shù)據(jù)更新的代碼需要獨占式地執(zhí)行。而信號量就可以提供這樣的一種訪問機(jī)制,讓一個臨界區(qū)同一時間只有一個線程在訪問它,也就是說信號量是用來協(xié)調(diào)進(jìn)程對共享資源的訪問的。
信號量是一個特殊的變量,程序?qū)ζ湓L問都是原子操作,且只允許對它進(jìn)行等待(即P(信號量)) 和? 發(fā)送 (即V(信號變量))操作。最簡單的信號量是只能取0和1的變量,這也是信號量最常見的一種形式,叫做二進(jìn)制信號量。 還有一種可以取多個正整數(shù)的信號量稱為通用信號量,下面主要討論二進(jìn)制信號量;
信號量的工作原理
由于信號量只能進(jìn)行兩種操作等待和發(fā)送信號,即P(sv)和V(sv),他們的行為是這樣的:
P(sv):如果sv的值大于零,就給它減1;如果它的值為零,就掛起該進(jìn)程的執(zhí)行。
V(sv):如果有其他進(jìn)程因等待sv而被掛起,就讓它恢復(fù)運行,如果沒有進(jìn)程因等待sv而掛起,就給它加1;
就是兩個進(jìn)程共享信號量sv,
一旦其中一個進(jìn)程執(zhí)行了P(sv)操作,它將得到信號量,并可以進(jìn)入臨界區(qū),使sv減1。
而第二個進(jìn)程將被阻止進(jìn)入臨界區(qū), 因為當(dāng)它試圖執(zhí)行P(sv)時,sv為0,它會被掛起以等待第一個進(jìn)程離開臨界區(qū)域并執(zhí)行V(sv)釋放信號量,這時第二個進(jìn)程就可以恢復(fù)執(zhí)行。
Linux的信號量機(jī)制
Linux提供了一組精心設(shè)計的信號量接口來對信號進(jìn)行操作,它們不只是針對二進(jìn)制信號量,下面將會對這些函數(shù)進(jìn)行介紹,但請注意,這些函數(shù)都是用來對成組的信號量值進(jìn)行操作的。它們聲明在頭文件sys/sem.h中。 1、semget函數(shù) 它的作用是創(chuàng)建一個新信號量或取得一個已有的信號量,原型為: int? semget(key_t key, int num_sems,? int sem_flags); 參數(shù)說明: 第一個參數(shù):key是整數(shù)值(唯一非零),不相關(guān)的進(jìn)程可以通過它訪問一個信號量,它代表程序可能要使用的某個資源,程序?qū)λ行盘柫康脑L問都是間接的, 程序先通過調(diào)用semget函數(shù)并提供一個鍵,再由系統(tǒng)生成一個相應(yīng)的信號標(biāo)識符(semget函數(shù)的返回值),只有semget函數(shù)才直接使用信號量鍵;所有其他的信號量函數(shù)使用由semget函數(shù)返回的信號量標(biāo)識符,如果多個程序使用相同的key值,key將負(fù)責(zé)協(xié)調(diào)工作; 第二個參數(shù):num_sems指定需要的信號量數(shù)目,它的值幾乎總是1; 第三個參數(shù):sem_flags 是一組標(biāo)志,當(dāng)想要在信號量不存在的情況時則創(chuàng)建一個新的信號量,可以和值IPC_CREAT標(biāo)志后,即使給出的鍵是一個已有信號量的鍵,也不會產(chǎn)生錯誤。而IPC_CREAT|IPC_EXCL則可以創(chuàng)建一個新的,唯一的信號量,如果信號已存在,返回一個錯誤; semget函數(shù)成功返回一個相應(yīng)信號標(biāo)識符(非零),失敗返回-1; semop函數(shù) 功能:改變信號量的值,原型為: int? semop(int sem_id, struct sembuf *sem_opa, size_t? num_sem_ops); 參數(shù)說明: 第一個參數(shù):sem_id是由semget返回的信號量標(biāo)識符, sembuf結(jié)構(gòu)定義如下: struct sembuf{ short sem_num;? //除非使用一組信號量,否則它為0 short?sem_op;?? //信號量在一次操作中需要改變的數(shù)據(jù),通常是兩個數(shù),一個是-1,即P(等待)操作 ?//一個是+1,即V(發(fā)送信號)操作。?????????????? short?sem_flg;//通常為SEM_UNDO,使操作系統(tǒng)跟蹤信號,//并在進(jìn)程沒有釋放該信號量而終止時,操作系統(tǒng)釋放信號量 };
信號量稱為信號燈,它是用來協(xié)調(diào)不同進(jìn)程間的數(shù)據(jù)對象的,而最主要的應(yīng)用是前一節(jié)的共享內(nèi)存方式的進(jìn)程間通信。本質(zhì)上,信號量是一個計數(shù)器,它用來記錄對某個資源(如共享內(nèi)存)的存取狀況,一般來說,為了獲得共享資源,進(jìn)程需要執(zhí)行下列操作:
1、測試控制該資源的信號量
2、若此信號量的值為正,則允許進(jìn)行使用該資源。進(jìn)程將信號量減1
3、若此信號量為0,則該資源目前不可用,進(jìn)程進(jìn)入睡眠狀態(tài),直到信號量大于0.進(jìn)程被喚醒,轉(zhuǎn)入步驟1;
4、當(dāng)進(jìn)程不再使用一個信號量控制的資源時,信號量加1。如果當(dāng)時有進(jìn)程正在睡眠等待此信號量,則喚醒此進(jìn)程。
維護(hù)信號量狀態(tài)的是Linux內(nèi)核操作系統(tǒng)而不是用戶進(jìn)程。我們可以從頭文件/usr/scr/linux/include/linux/sem.h中看到內(nèi)核用來維護(hù)信號量狀態(tài)的各個結(jié)構(gòu)的定義。信號量是一個數(shù)據(jù)集合,用戶可以單獨使用這一集合的每個元素。要調(diào)用的第一個函數(shù)是semget,用以獲得一個信號量ID;
?
?
?
?
?
?
?
?
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/chris-cp/p/3532216.html
總結(jié)
以上是生活随笔為你收集整理的linux 之进程间通信-------------InterProcess Communication的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: git config配置文件
- 下一篇: 11g RAC不能启动ohasd进程