轉載自:https://blog.csdn.net/qq_36829091/article/details/80138836
每一個進程來說這個進程看到屬於它的一塊內存資源,這塊資源是它所獨占的,所以進程之間的通信就會比較麻煩,原理就是需要讓不同的進程間能夠看到一份公共的資源。所以交換數據必須通過內核,在內核中開辟一塊緩沖區,進程1把數據從用戶空間 拷到內核緩沖區,進程2再從內核緩沖區把數據讀走,內核提供的這種機制稱為進程間通信。一般我們采用的進程間通信方式有:
- 管道(pipe)和有名管道(FIFO)
- 信號(signal)
- 消息隊列
- 共享內存
- 信號量
- 套接字(socket)
我們先來從最簡單的通信方式來說起。
匿名管道
也簡稱管道,
管道的創建
管道是一種最基本的進程間通信機制。管道由pipe函數來創建:
SYNOPSIS #include <unistd.h> int pipe(int pipefd[2]);
調用pipe函數,會在內核中開辟出一塊緩沖區用來進行進程間通信,這塊緩沖區稱為管道,它有一個讀端和一個寫端。
pipe函數接受一個參數,是包含兩個整數的數組,如果調用成功,會通過pipefd[2]傳出給用戶程序兩個文件描述符,需要注意pipefd [0]指向管道的讀端, pipefd [1]指向管道的寫端,那么此時這個管道對於用戶程序就是一個文件,可以通過read(pipefd [0]);或者write(pipefd [1])進行操作。pipe函數調用成功返回0,否則返回-1..
那么再來看看通過管道進行通信的步驟:
1.父進程創建管道,得到兩個文件描述符指向管道的兩端
2. 利用fork函數創建子進程,則子進程也得到兩個文件描述符指向同一管道
3. 父進程關閉讀端(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) { close(_pipe[0]); int i = 0; char* msg = NULL; while(i < 100) { msg = "I am child"; write(_pipe[1], msg, strlen(msg)); sleep(1); ++i; } } else { close(_pipe[1]); int i = 0; char msg[100]; while(i < 100) { memset(msg, '\0', sizeof(msg)); read(_pipe[0], msg, sizeof(msg)); printf("%s\n", msg); ++i; } } return 0; }
pipe的特點:
1. 只能單向通信
2. 只能血緣關系的進程進行通信(父子進程、兄弟進程)
3. 依賴於文件系統
4. 生命周期隨進程(在內存中,進程結束被釋放)
5. 面向字節流的服務
6. 管道內部提供了同步機制(鎖、等待隊列、信號)
說明:因為管道通信是單向的,在上面的例子中我們是通過子進程寫父進程來讀,如果想要同時父進程寫而子進程來讀,就需要再打開另外的管道;
管道的讀寫端通過打開的文件描述符來傳遞,因此要通信的兩個進程必須從它們的公共祖先那里繼承管道的件描述符。 上面的例子是父進程把文件描述符傳給子進程之后父子進程之 間通信,也可以父進程fork兩次,把文件描述符傳給兩個子進程,然后兩個子進程之間通信, 總之 需要通過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通信。
命名管道
在管道中,只有具有血緣關系的進程才能進行通信,對於后來的命名管道,就解決了這個問題。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);
參數中path為創建的命名管道的全路徑名: mod為創建的命名管道的指明其存取權限;
運行示例:
那么此時我們早server.c中創建命名管道並打開,對管道中進行寫操作,在client.c中進行讀操作,把讀到的內容進行打印,就實現了我們的使用命名管道通信。
/*Server*/ #include<stdio.h> #include<unistd.h> #include<string.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 file error\n"); return -1; } char buf[_SIZE_]; memset(buf, '\0', sizeof(buf)); int fd = open(_PATH_NAME_, O_WRONLY); while(true) { fget(buf, sizeof(buf)-1, stdin); int ret = write(fd, buf, strlen(buf)+1); if(ret < 0) { printf("write error\n"); break; } } close(fd); return 0; }
/*Client*/ #include<stdio.h> #include<unistd.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\n"); return 1; } char buf[_SIZE_]; memset(buf, '\0', sizeof(buf)); while(true) { int ret = read(fd, buf, sizeof(buf)); if(ret < 0) { printf("read error\n"); break; } printf("%s\n", buf); } close(fd); return 0; }