進程間的通訊方式:linux
1.管道(pipe)及有名管道(named pipe):程序員
管道可用於具備親緣關係進程間的通訊,有名管道除了具備管道所具備的功能外,它還容許無親緣關係進程間的通訊。 編程
2.信號(signal):數組
信號是在軟件層次上對中斷機制的一種模擬,它是比較複雜的通訊方式,用於通知進程有某事件發生,一個進程收到一個信號與處理器收到一箇中斷請求效果上能夠說是一致得。安全
3.消息隊列(message queue):服務器
消息隊列是消息的連接表,它克服了上兩種通訊方式中信號量有限的缺點,具備寫權限得進程能夠按照必定得規則向消息隊列中添加新信息;對消息隊列有讀權限得進程則能夠從消息隊列中讀取信息。 網絡
消息緩衝通訊技術是由Hansen首先提出的,其基本思想是:根據」生產者-消費者」原理,利用內存中公用消息緩衝區實現進程之間的信息交換.
內存中開闢了若干消息緩衝區,用以存放消息.每當一個進程向另外一個進程發送消息時,便申請一個消息緩衝區,並把已準備好的消息送到緩衝區,而後把該消息緩衝區插入到接收進程的消息隊列中,最後通知接收進程.接收進程收到發送里程發來的通知後,從本進程的消息隊列中摘下一消息緩衝區,取出所需的信息,而後把消息緩衝區不按期給系統.系統負責管理公用消息緩衝區以及消息的傳遞.
一個進程能夠給若干個進程發送消息,反之,一個進程能夠接收不一樣進程發來的消息.顯然,進程中關於消息隊列的操做是臨界區.當發送進程正往接收進程的消息隊列中添加一條消息時,接收進程不能同時從該消息隊列中到出消息:反之也同樣.
消息緩衝區通訊機制包含如下列內容:
(1) 消息緩衝區,這是一個由如下幾項組成的數據結構:
一、 消息長度
二、 消息正文
三、 發送者
四、 消息隊列指針
(2)消息隊列首指針m-q,通常保存在PCB中。
(1) 互斥信號量m-mutex,初值爲1,用於互斥訪問消息隊列,在PCB中設置。
(2) 同步信號量m-syn,初值爲0,用於消息計數,在PCB中設置。
(3) 發送消息原語send
(4) 接收消息原語receive(a) 數據結構
4.共享內存(shared memory):多線程
能夠說這是最有用的進程間通訊方式。它使得多個進程能夠訪問同一塊內存空間,不一樣進程能夠及時看到對方進程中對共享內存中數據得更新。這種方式須要依靠某種同步操做,如互斥鎖和信號量等。socket
這種通訊模式須要解決兩個問題:第一個問題是怎樣提供共享內存;第二個是公共內存的互斥關係則是程序開發人員的責任。
5.信號量(semaphore):
主要做爲進程之間及同一種進程的不一樣線程之間得同步和互斥手段。
6.套接字(socket);
這是一種更爲通常得進程間通訊機制,它可用於網絡中不一樣機器之間的進程間通訊,應用很是普遍。
http://blog.csdn.net/eroswang/archive/2007/09/04/1772350.aspx
linux下的進程間通訊-詳解
詳細的講述進程間通訊在這裏絕對是不可能的事情,並且筆者很難有信心說本身對這一部份內容的認識達到了什麼樣的地步,因此在這一節的開頭首先向你們推薦著 名做者Richard Stevens的著名做品:《Advanced Programming in the UNIX Environment》,它的中文譯本《UNIX環境高級編程》已有機械工業出版社出版,原文精彩,譯文一樣地道,若是你的確對在Linux下編程有濃 厚的興趣,那麼趕忙將這本書擺到你的書桌上或計算機旁邊來。說這麼多實在是難抑心中的景仰之情,言歸正傳,在這一節裏,咱們將介紹進程間通訊最最初步和最 最簡單的一些知識和概念。
首先,進程間通訊至少能夠經過傳送打開文件來實現,不一樣的進程經過一個或多個文件來傳遞信息,事實上,在不少應用系統裏,都使用了這種方法。但通常說來, 進程間通訊(IPC:InterProcess Communication)不包括這種彷佛比較低級的通訊方法。Unix系統中實現進程間通訊的方法不少,並且不幸的是,極少方法能在全部的Unix系 統中進行移植(惟一一種是半雙工的管道,這也是最原始的一種通訊方式)。而Linux做爲一種新興的操做系統,幾乎支持全部的Unix下經常使用的進程間通訊 方法:管道、消息隊列、共享內存、信號量、套接口等等。下面咱們將逐一介紹。
2.3.1 管道
管道是進程間通訊中最古老的方式,它包括無名管道和有名管道兩種,前者用於父進程和子進程間的通訊,後者用於運行於同一臺機器上的任意兩個進程間的通訊。
無名管道由pipe()函數建立:
#include <unistd.h>
int pipe(int filedis[2]);
參數filedis返回兩個文件描述符:filedes[0]爲讀而打開,filedes[1]爲寫而打開。filedes[1]的輸出是filedes[0]的輸入。下面的例子示範瞭如何在父進程和子進程間實現通訊。
#define INPUT 0
#define OUTPUT 1
void main() {
int file_descriptors[2];
/*定義子進程號 */
pid_t pid;
char buf[256];
int returned_count;
/*建立無名管道*/
pipe(file_descriptors);
/*建立子進程*/
if((pid = fork()) == -1) {
printf("Error in fork\n");
exit(1);
}
/*執行子進程*/
if(pid == 0) {
printf("in the spawned (child) process...\n");
/*子進程向父進程寫數據,關閉管道的讀端*/
close(file_descriptors[INPUT]);
write(file_descriptors[OUTPUT], "test data", strlen("test data"));
exit(0);
} else {
/*執行父進程*/
printf("in the spawning (parent) process...\n");
/*父進程從管道讀取子進程寫的數據,關閉管道的寫端*/
close(file_descriptors[OUTPUT]);
returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
printf("%d bytes of data received from spawned process: %s\n",
returned_count, buf);
}
}
在Linux系統下,有名管道可由兩種方式建立:命令行方式mknod系統調用和函數mkfifo。下面的兩種途徑都在當前目錄下生成了一個名爲myfifo的有名管道:
方式一:mkfifo("myfifo","rw");
方式二:mknod myfifo p
生成了有名管道後,就可使用通常的文件I/O函數如open、close、read、write等來對它進行操做。下面便是一個簡單的例子,假設咱們已經建立了一個名爲myfifo的有名管道。
/* 進程一:讀有名管道*/
#include <stdio.h>
#include <unistd.h>
void main() {
FILE * in_file;
int count = 1;
char buf[80];
in_file = fopen("mypipe", "r");
if (in_file == NULL) {
printf("Error in fdopen.\n");
exit(1);
}
while ((count = fread(buf, 1, 80, in_file)) > 0)
printf("received from pipe: %s\n", buf);
fclose(in_file);
}
/* 進程二:寫有名管道*/
#include <stdio.h>
#include <unistd.h>
void main() {
FILE * out_file;
int count = 1;
char buf[80];
out_file = fopen("mypipe", "w");
if (out_file == NULL) {
printf("Error opening pipe.");
exit(1);
}
sprintf(buf,"this is test data for the named pipe example\n");
fwrite(buf, 1, 80, out_file);
fclose(out_file);
}
2.3.2 消息隊列
消息隊列用於運行於同一臺機器上的進程間通訊,它和管道很類似,是一個在系統內核中用來保存消息的隊列,它在系統內核中是以消息鏈表的形式出現。消息鏈表中節點的結構用msg聲明。
事實上,它是一種正逐漸被淘汰的通訊方式,咱們能夠用流管道或者套接口的方式來取代它,因此,咱們對此方式也再也不解釋,也建議讀者忽略這種方式。
2.3.3 共享內存
共享內存是運行在同一臺機器上的進程間通訊最快的方式,由於數據不須要在不一樣的進程間複製。一般由一個進程建立一塊共享內存區,其他進程對這塊內存區進行 讀寫。獲得共享內存有兩種方式:映射/dev/mem設備和內存映像文件。前一種方式不給系統帶來額外的開銷,但在現實中並不經常使用,由於它控制存取的將是 實際的物理內存,在Linux系統下,這隻有經過限制Linux系統存取的內存才能夠作到,這固然不太實際。經常使用的方式是經過shmXXX函數族來實現利 用共享內存進行存儲的。
首先要用的函數是shmget,它得到一個共享存儲標識符。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int flag);
這個函數有點相似你們熟悉的malloc函數,系統按照請求分配size大小的內存用做共享內存。Linux系統內核中每一個IPC結構都有的一個非負整數 的標識符,這樣對一個消息隊列發送消息時只要引用標識符就能夠了。這個標識符是內核由IPC結構的關鍵字獲得的,這個關鍵字,就是上面第一個函數的 key。數據類型key_t是在頭文件sys/types.h中定義的,它是一個長整形的數據。在咱們後面的章節中,還會碰到這個關鍵字。
當共享內存建立後,其他進程能夠調用shmat()將其鏈接到自身的地址空間中。
void *shmat(int shmid, void *addr, int flag);
shmid爲shmget函數返回的共享存儲標識符,addr和flag參數決定了以什麼方式來肯定鏈接的地址,函數的返回值便是該進程數據段所鏈接的實際地址,進程能夠對此進程進行讀寫操做。
使用共享存儲來實現進程間通訊的注意點是對數據存取的同步,必須確保當一個進程去讀取數據時,它所想要的數據已經寫好了。一般,信號量被要來實現對共享存 儲數據存取的同步,另外,能夠經過使用shmctl函數設置共享存儲內存的某些標誌位如SHM_LOCK、SHM_UNLOCK等來實現。
2.3.4 信號量
信號量又稱爲信號燈,它是用來協調不一樣進程間的數據對象的,而最主要的應用是前一節的共享內存方式的進程間通訊。本質上,信號量是一個計數器,它用來記錄對某個資源(如共享內存)的存取情況。通常說來,爲了得到共享資源,進程須要執行下列操做:
(1) 測試控制該資源的信號量。
(2) 若此信號量的值爲正,則容許進行使用該資源。進程將信號量減1。
(3) 若此信號量爲0,則該資源目前不可用,進程進入睡眠狀態,直至信號量值大於0,進程被喚醒,轉入步驟(1)。
(4) 當進程再也不使用一個信號量控制的資源時,信號量值加1。若是此時有進程正在睡眠等待此信號量,則喚醒此進程。
維護信號量狀態的是Linux內核操做系統而不是用戶進程。咱們能夠從頭文件/usr/src/linux/include /linux /sem.h 中看到內核用來維護信號量狀態的各個結構的定義。信號量是一個數據集合,用戶能夠單獨使用這一集合的每一個元素。要調用的第一個函數是semget,用以獲 得一個信號量ID。
struct sem {
short sempid;/* pid of last operaton */
ushort semval;/* current value */
ushort semncnt;/* num procs awaiting increase in semval */
ushort semzcnt;/* num procs awaiting semval = 0 */
}
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int flag);
key是前面講過的IPC結構的關鍵字,flag未來決定是建立新的信號量集合,仍是引用一個現有的信號量集合。nsems是該集合中的信號量數。若是是建立新 集合(通常在服務器中),則必須指定nsems;若是是引用一個現有的信號量集合(通常在客戶機中)則將nsems指定爲0。
semctl函數用來對信號量進行操做。
int semctl(int semid, int semnum, int cmd, union semun arg);
不一樣的操做是經過cmd參數來實現的,在頭文件sem.h中定義了7種不一樣的操做,實際編程時能夠參照使用。
semop函數自動執行信號量集合上的操做數組。
int semop(int semid, struct sembuf semoparray[], size_t nops);
semoparray是一個指針,它指向一個信號量操做數組。nops規定該數組中操做的數量。
下面,咱們看一個具體的例子,它建立一個特定的IPC結構的關鍵字和一個信號量,創建此信號量的索引,修改索引指向的信號量的值,最後咱們清除信號量。在下面的代碼中,函數ftok生成咱們上文所說的惟一的IPC關鍵字。
#include <stdio.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
void main() {
key_t unique_key; /* 定義一個IPC關鍵字*/
int id;
struct sembuf lock_it;
union semun options;
int i;
unique_key = ftok(".", 'a'); /* 生成關鍵字,字符'a'是一個隨機種子*/
/* 建立一個新的信號量集合*/
id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
printf("semaphore id=%d\n", id);
options.val = 1; /*設置變量值*/
semctl(id, 0, SETVAL, options); /*設置索引0的信號量*/
/*打印出信號量的值*/
i = semctl(id, 0, GETVAL, 0);
printf("value of semaphore at index 0 is %d\n", i);
/*下面從新設置信號量*/
lock_it.sem_num = 0; /*設置哪一個信號量*/
lock_it.sem_op = -1; /*定義操做*/
lock_it.sem_flg = IPC_NOWAIT; /*操做方式*/
if (semop(id, &lock_it, 1) == -1) {
printf("can not lock semaphore.\n");
exit(1);
}
i = semctl(id, 0, GETVAL, 0);
printf("value of semaphore at index 0 is %d\n", i);
/*清除信號量*/
semctl(id, 0, IPC_RMID, 0);
}
可使用系統調用semget()建立一個新的信號量集,或者存取一個已經存在的信號量集:
系統調用:semop();
調用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);
返回值:0,若是成功。-1,若是失敗:errno=E2BIG(nsops大於最大的ops數目)
EACCESS(權限不夠)
EAGAIN(使用了IPC_NOWAIT,但操做不能繼續進行)
EFAULT(sops指向的地址無效)
EIDRM(信號量集已經刪除)
EINTR(當睡眠時接收到其餘信號)
EINVAL(信號量集不存在,或者semid無效)
ENOMEM(使用了SEM_UNDO,但無足夠的內存建立所需的數據結構)
ERANGE(信號量值超出範圍)
系統調用:semctl();
原型:int semctl(int semid,int semnum,int cmd,union semunarg);
返回值:若是成功,則爲一個正數。
若是失敗,則爲-1:errno=EACCESS(權限不夠)
EFAULT(arg指向的地址無效)
EIDRM(信號量集已經刪除)
EINVAL(信號量集不存在,或者semid無效)
EPERM(EUID沒有cmd的權利)
ERANGE(信號量值超出範圍)
2.3.5 套接口 套接口(socket)編程是實現Linux系統和其餘大多數操做系統中進程間通訊的主要方式之一。咱們熟知的WWW服務、FTP服務、TELNET服務 等都是基於套接口編程來實現的。除了在異地的計算機進程間之外,套接口一樣適用於本地同一臺計算機內部的進程間通訊。關於套接口的經典教材一樣是 Richard Stevens編著的《Unix網絡編程:聯網的API和套接字》,清華大學出版社出版了該書的影印版。它一樣是Linux程序員的必備書籍之一。 關於這一部分的內容,能夠參照本文做者的另外一篇文章《設計本身的網絡螞蟻》,那裏由經常使用的幾個套接口函數的介紹和示例程序。這一部分或許是Linux進程 間通訊編程中最須關注和最吸引人的一部分,畢竟,Internet 正在咱們身邊以難以想象的速度發展着,若是一個程序員在設計編寫他下一個程序的時候,根本沒有考慮到網絡,考慮到Internet,那麼,能夠說,他的設 計很難成功。 3 Linux的進程和Win32的進程/線程比較 熟悉WIN32編程的人必定知道,WIN32的進程管理方式與Linux上有着很大區別,在UNIX裏,只有進程的概念,但在WIN32裏卻還有一個"線程"的概念,那麼Linux和WIN32在這裏究竟有着什麼區別呢? WIN32裏的進程/線程是繼承自OS/2的。在WIN32裏,"進程"是指一個程序,而"線程"是一個"進程"裏的一個執行"線索"。從核心上講, WIN32的多進程與Linux並沒有多大的區別,在WIN32裏的線程才至關於Linux的進程,是一個實際正在執行的代碼。可是,WIN32裏同一個進 程裏各個線程之間是共享數據段的。這纔是與Linux的進程最大的不一樣。 下面這段程序顯示了WIN32下一個進程如何啓動一個線程。 int g; DWORD WINAPI ChildProcess( LPVOID lpParameter ){ int i; for ( i = 1; i <1000; i ++) { g ++; printf( "This is Child Thread: %d\n", g ); } ExitThread( 0 ); }; void main() { int threadID; int i; g = 0; CreateThread( NULL, 0, ChildProcess, NULL, 0, &threadID ); for ( i = 1; i <1000; i ++) { g ++; printf( "This is Parent Thread: %d\n", g ); } } 在WIN32下,使用CreateThread函數建立線程,與Linux下建立進程不一樣,WIN32線程不是從建立處開始運行的,而是由 CreateThread指定一個函數,線程就從那個函數處開始運行。此程序同前面的UNIX程序同樣,由兩個線程各打印1000條信息。 threadID是子線程的線程號,另外,全局變量g是子線程與父線程共享的,這就是與Linux最大的不一樣之處。你們能夠看出,WIN32的進程/線程 要比Linux複雜,在Linux要實現相似WIN32的線程並不難,只要fork之後,讓子進程調用ThreadProc函數,而且爲全局變量開設共享 數據區就好了,但在WIN32下就沒法實現相似fork的功能了。因此如今WIN32下的C語言編譯器所提供的庫函數雖然已經能兼容大多數 Linux/UNIX的庫函數,但卻仍沒法實現fork。 對於多任務系統,共享數據區是必要的,但也是一個容易引發混亂的問題,在WIN32下,一個程序員很容易忘記線程之間的數據是共享的這一狀況,一個線程修 改過一個變量後,另外一個線程卻又修改了它,結果引發程序出問題。但在Linux下,因爲變量原本並不共享,而由程序員來顯式地指定要共享的數據,使程序變 得更清晰與安全。 至於WIN32的"進程"概念,其含義則是"應用程序",也就是至關於UNIX下的exec了。 Linux也有本身的多線程函數pthread,它既不一樣於Linux的進程,也不一樣於WIN32下的進程,關於pthread的介紹和如何在Linux環境下編寫多線程程序咱們將在另外一篇文章《Linux下的多線程編程》中講述。