我們先來說說進程間通信(IPC)的一般目的,大概有數據傳輸、共享數據、通知事件、資源共享和進程控制等。但是我們知道,對於每一個進程來說這個進程看到屬於它的一塊內存資源,這塊資源是它所獨占的,所以進程之間的通信就會比較麻煩,原理就是需要讓不同的進程間能夠看到一份公共的資源。所以交換數據必須通過內核,在內核中開辟一塊緩沖區,進程1把數據從用戶空間 拷到內核緩沖區,進程2再從內核緩沖區把數據讀走,內核提供的這種機制稱為進程間通信。一般我們采用的進程間通信方式有
-
管道(pipe)和有名管道(FIFO)
-
信號(signal)
-
消息隊列
-
共享內存
-
信號量
-
套接字(socket)
我們先來從最簡單的通信方式來說起;
匿名管道 pipe
---------------------------------------------------------------------------------------------------------------------------------
管道的創建
管道是一種最基本的進程間通信機制。管道由pipe函數來創建:
調用pipe函數,會在內核中開辟出一塊緩沖區用來進行進程間通信,這塊緩沖區稱為管道,它有一個讀端和一個寫端。
pipe函數接受一個參數,是包含兩個整數的數組,如果調用成功,會通過pipefd[2]傳出給用戶程序兩個文件描述符,需要注意pipefd [0]指向管道的讀端, pipefd [1]指向管道的寫端,那么此時這個管道對於用戶程序就是一個文件,可以通過read(pipefd [0]);或者write(pipefd [1])進行操作。pipe函數調用成功返回0,否則返回-1..
那么再來看看通過管道進行通信的步驟:
》父進程創建管道,得到兩個文件描述符指向管道的兩端
》利用fork函數創建出子進程,則子進程也得到兩個文件描述符指向同一管道
》父進程關閉讀端(pipe[0]),子進程關閉寫端pipe[1],則此時父進程可以往管道中進行寫操作,子進程可以從管道中讀,從而實現了通過管道的進程間通信。
示例代碼:
#include<stdio.h> #include<unistd.h> #include<string.h> int main() { int _pipe[2]; int ret=pipe(_pipe); if(ret<0) { perror("pipe\n"); } pid_t id=fork(); if(id<0) { perror("fork\n"); } else if(id==0) // child { close(_pipe[0]); int i=0; char *mesg=NULL; while(i<100) { mesg="I am child"; write(_pipe[1],mesg,strlen(mesg)+1); sleep(1); ++i; } } else //father { close(_pipe[1]); int j=0; char _mesg[100]; while(j<100) { memset(_mesg,'\0',sizeof(_mesg )); read(_pipe[0],_mesg,sizeof(_mesg)); printf("%s\n",_mesg); j++; } } return 0; }
結果演示:
pipe的特點:
1. 只能單向通信
2. 只能血緣關系的進程進行通信
3. 依賴於文件系統
4、生命周期隨進程
5. 面向字節流的服務
6. 管道內部提供了同步機制
說明:因為管道通信是單向的,在上面的例子中我們是通過子進程寫父進程來讀,如果想要同時父進程寫而子進程來讀,就需要再打開另外的管道;
管道的讀寫端通過打開的文件描述符來傳遞,因此要通信的兩個進程必須從它們的公共祖先那里繼承管道的件描述符。 上面的例子是父進程把文件描述符傳給子進程之后父子進程之 間通信,也可以父進程fork兩次,把文件描述符傳給兩個子進程,然后兩個子進程之間通信, 總之 需要通過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通信。
四個特殊情況:
》 如果所有指向管道寫端的文件描述符都關閉了,而仍然有進程從管道的讀端讀數據,那么管道中剩余的數據都被讀取后,再次read會返回0,就像讀到文件末尾一樣
》 如果有指向管道寫端的文件描述符沒關閉,而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那么管道中剩余的數據都被讀取后,再次read會阻塞,直到管道中有數據可讀了才讀取數據並返回。
》 如果所有指向管道讀端的文件描述符都關閉了,這時有進程指向管道的寫端write,那么該進程會收到信號SIGPIPE,通常會導致進程異常終止。
》 如果有指向管道讀端的文件描述符沒關閉,而持有管道寫端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那么在管道被寫滿時再write會阻塞,直到管道中有空位置了才寫入數據並返回。
命名管道FIFO
---------------------------------------------------------------------------------------------------------------------------------
在管道中,只有具有血緣關系的進程才能進行通信,對於后來的命名管道,就解決了這個問題。FIFO不同於管道之處在於它提供一個路徑名與之關聯,以FIFO的文件形式存儲於文件系統中。命名管道是一個設備文件,因此,即使進程與創建FIFO的進程不存在親緣關系,只要可以訪問該路徑,就能夠通過FIFO相互通信。值得注意的是, FIFO(first input first output)總是按照先進先出的原則工作,第一個被寫入的數據將首先從管道中讀出。
命名管道的創建
創建命名管道的系統函數有兩個: mknod和mkfifo。兩個函數均定義在頭文件sys/stat.h,
函數原型如下:
#include <sys/types.h>
#include <sys/stat.h>
int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const char *path,mode_t mode);
函數mknod參數中path為創建的命名管道的全路徑名: mod為創建的命名管道的模指
明其存取權限; dev為設備值,該值取決於文件創建的種類,它只在創建設備文件時才會用到。這兩個函數調用成功都返回0,失敗都返回-1。下面使用mknod函數創建了一個命名管道:
umask(0);
if (mknod("/tmp/fifo",S_IFIFO | 0666) == -1)
{
perror("mkfifo error");
exit(1);
}
函數mkfifo前兩個參數的含義和mknod相同。下,面是使用mkfifo的示例代碼:
umask(0);
if (mkfifo("/tmp/fifo",S_IFIFO|0666) == -1)
{
perror("mkfifo error!");
exit(1);
}
"S_IFIFO|0666"指明創建一個命名管道且存取權限為0666,即創建者、與創建者同組的
用戶、其他用戶對該命名管道的訪問權限都是可讀可寫( 這里要注意umask對生成的
管道文件權限的影響) 。
命名管道創建后就可以使用了,命名管道和管道的使用方法法基本是相同的。只是使用命
名管道時,必須先調用open()將其打開。因為命名管道是一個存在於硬盤上的文件,而管道
是存在於內存中的特殊文件。
需要注意的是,調用open()打開命名管道的進程可能會被阻塞。但如果同時用讀寫方式
( O_RDWR)打開,則一定不會導致阻塞;如果以只讀方式( O_RDONLY)打開,則調
用open()函數的進程將會被阻塞直到有寫方打開管道;同樣以寫方式( O_WRONLY)打開
也會阻塞直到有讀方式打開管道。
運行示例
-----------------------------------------------------------
那么此時我們早server.c中創建命名管道並打開,對管道中進行寫操作,在client.c中進行讀操作,把讀到的內容進行打印,就實現了我們的使用命名管道通信。
Server.c: #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<string.h> #include<sys/stat.h> #include<fcntl.h> #define _PATH_NAME_ "/tmp/file.tmp" #define _SIZE_ 100 int main() { int ret=mkfifo(_PATH_NAME_,S_IFIFO|0666); if(ret==-1){ printf("make fifo error\n"); return 1; } char buf[_SIZE_]; memset(buf,'\0',sizeof(buf)); int fd=open(_PATH_NAME_,O_WRONLY); while(1) { //scanf("%s",buf); fgets(buf,sizeof(buf)-1,stdin); int ret=write(fd,buf,strlen(buf)+1); if(ret<0){ printf("write error"); break; } } close(fd); return 0; } Client.c: #include<stdio.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> #include<sys/types.h> #include<string.h> #define _PATH_NAME "/tmp/file.tmp" #define _SIZE_ 100 int main() { int fd=open(_PATH_NAME,O_RDONLY); if(fd<0){ printf("open file error"); return 1; } char buf[_SIZE_]; memset(buf,'\0',sizeof(buf)); while(1) { int ret=read(fd,buf,sizeof(buf)); if(ret<0){ printf("read end or error\n"); break; } printf("%s",buf); } close(fd); return 0; }
結果演示:
可以看到我實在服務端發送了“Hello” “I am aerver”,在客戶端可以收到此內容,完成簡單的進程間通信。