此為牛客Linux C++和黑馬Linux系統編程課程筆記。
0. 關于進程通信
Linux環境下,進程地址空間相互獨立,每個進程各自有不同的用戶地址空間。任何一個進程的全局變量在另一個進程中都看不到,所以進程和進程之間不能相互訪問,要交換數據必須通過內核,在內核中開辟一塊緩沖區,進程1把數據從用戶空間拷到內核緩沖區,進程2再從內核緩沖區把數據讀走,內核提供的這種機制稱為進程間通信(IPC,InterProcess Communication)。
在進程間完成數據傳遞需要借助操作系統提供特殊的方法,如:文件、管道、信號、共享內存、消息隊列、套接字、命名管道等。隨著計算機的蓬勃發展,一些方法由于自身設計缺陷被淘汰或者棄用。現今常用的進程間通信方式有:
① 管道 (使用最簡單)
② 信號 (開銷最小)
③ 共享映射區 (無血緣關系)
④ 本地套接字 (最穩定)
1. 匿名管道
管道是一種最基本的IPC機制,作用于有血緣關系的進程之間,完成數據傳遞。調用pipe系統函數即可創建一個管道。有如下特點:
匿名管道采用了循環隊列,可將寫指針看作隊列頭,讀指針看作隊列尾:
2. pipe函數
Linux中使用pipe函數創建管道:
#include <unistd.h>
int pipe(int pipefd[2]);
功能:
創建一個匿名管道,用于進程間通信。
參數:
int pipefd[2] 這個數組是一個傳出參數。
pipefd[0] 對應的是管道的讀端
pipefd[1] 對應的是管道的寫端
返回值:
成功 0
失敗 -1
注意: 管道默認是阻塞的:如果管道中沒有數據,read阻塞,如果管道滿了,write阻塞匿名管道只能用于具有關系的進程之間的通信(父子進程,兄弟進程)
當調用pipe后,當前進程的文件描述符表中就已經有兩個文件描述符分別指向管道的讀端和寫端,pipefd[0]和pipefd[1]返回這兩個文件描述符。
如下示例程序能夠實現:子進程發送數據給父進程,父進程讀取到數據輸出到終端。
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{// 子進程發送數據給父進程,父進程讀取到數據輸出int pipefd[2];int ret = pipe(pipefd);if(ret == -1) {perror("pipe error");exit(0);}pid_t pid = fork();if(pid > 0) {char buffer[1024] = {0};read(pipefd[0], buffer, sizeof(buffer)); // 如果管道為空,此處阻塞printf("recieved : %s", buffer);} else if(pid == 0) {char* content = "hello, im child process";write(pipefd[1], content, strlen(content));}return 0;
}
創建管道,使用read和write分別在pipefd[0]中讀數據,在pipefd[1]中寫數據。
運行結果如下:
可見父進程中收到了子進程中傳遞的消息。
3. pipe管道的讀寫特點:
使用管道時,需要注意以下幾種特殊的情況(假設都是阻塞I/O操作)
1.所有的指向管道寫端的文件描述符都關閉了(管道寫端引用計數為0),有進程從管道的讀端
讀數據,那么管道中剩余的數據被讀取以后,再次read會返回0,就像讀到文件末尾一樣。
2.如果有指向管道寫端的文件描述符沒有關閉(管道的寫端引用計數大于0),而持有管道寫端的進程
也沒有往管道中寫數據,這個時候有進程從管道中讀取數據,那么管道中剩余的數據被讀取后,
再次read會阻塞,直到管道中有數據可以讀了才讀取數據并返回。
3.如果所有指向管道讀端的文件描述符都關閉了(管道的讀端引用計數為0),這個時候有進程
向管道中寫數據,那么該進程會收到一個信號SIGPIPE, 通常會導致進程異常終止。
4.如果有指向管道讀端的文件描述符沒有關閉(管道的讀端引用計數大于0),而持有管道讀端的進程
也沒有從管道中讀數據,這時有進程向管道中寫數據,那么在管道被寫滿的時候再次write會阻塞,
直到管道中有空位置才能再次寫入數據并返回。
總結:
????讀管道:
???????? 管道中有數據,read返回實際讀到的字節數。
???????? 管道中無數據:
???????????????? 寫端被全部關閉,read返回0(相當于讀到文件的末尾)
??????????????? ?寫端沒有完全關閉,read阻塞等待
????寫管道:
????????管道讀端全部被關閉,進程異常終止(進程收到SIGPIPE信號)
????????管道讀端沒有全部關閉:
????????????????管道已滿,write阻塞
????????????????管道沒有滿,write將數據寫入,并返回實際寫入的字節數
4. 有名管道
5. mkfifo函數
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
參數:
- pathname: 管道名稱的路徑
- mode: 文件的權限 和 open 的 mode 是一樣的
是一個八進制的數
返回值:成功返回0,失敗返回-1,并設置錯誤號
6. 有名管道的注意事項:
1.一個為只讀而打開一個管道的進程會阻塞,直到另外一個進程為只寫打開管道;
2.一個為只寫而打開一個管道的進程會阻塞,直到另外一個進程為只讀打開管道
讀管道:
????管道中有數據,read返回實際讀到的字節數
????管道中無數據:
????????管道寫端被全部關閉,read返回0,(相當于讀到文件末尾)
????????寫端沒有全部被關閉,read阻塞等待
寫管道:
????管道讀端被全部關閉:進行異常終止(收到一個SIGPIPE信號)
????管道讀端沒有全部關閉:
????????管道已經滿了,write會阻塞
????????管道沒有滿,write將數據寫入,并返回實際寫入的字節數。
7. 使用FIFO實現簡單的聊天功能
chatterA.c:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>int main()
{// 第一步,首先判斷有名管道是否存在int ret = access("fifo1", F_OK);if(ret == -1) {// 說明管道文件不存在,則創建管道printf("管道不存在,創建管道\n");ret = mkfifo("fifo1", 0664);if(ret == -1) {// 如果創建管道失敗perror("mkfifo");exit(0);}}ret = access("fifo2", F_OK);if(ret == -1) {printf("管道不存在,創建管道\n");ret = mkfifo("fifo2", 0664);if(ret == -1) {perror("mkfifo");exit(0);}}// 第二步,以只寫的方式打開fifo1,以只讀的方式打開fifo2// fifo1管道負責chatter1寫chatter2讀// fifo2管道負責chatter1讀chatter2寫int fd1 = open("fifo1", O_WRONLY);if(fd1 == -1) {perror("open");exit(0);}printf("打開fifo1成功,等待寫入...\n");int fd2 = open("fifo2", O_RDONLY);if(fd2 == -1) {perror("open");exit(0);}printf("打開fifo2成功,等待讀取...\n");// 第三步,循環地往管道fifo1里寫入數據char buffer[1024] = {0};while(1) {// 把buffer置空以便重復寫入memset(buffer, 0, 1024); // 獲取標準輸入的數據fgets(buffer, 1024, stdin);ret = write(fd1, buffer, strlen(buffer));if(ret == -1) {perror("write");exit(0);}// 第四步,循環地從管道fifo2中讀出數據memset(buffer, 0, 1024);ret = read(fd2, buffer, 1024);if(ret <= 0) {perror("read");exit(0);}printf("chatterB傳來消息: %s\n", buffer);}close(fd1);close(fd2);return 0;
}
chatterB:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>int main()
{// 與chatterA完全對稱,往fifo2中寫,從fifo1中讀int ret = access("fifo1", F_OK);if(ret == -1) {printf("管道不存在,創建管道\n");ret = mkfifo("fifo1", 0664);if(ret == -1) {perror("mkfifo");exit(0);}}ret = access("fifo2", F_OK);if(ret == -1) {printf("管道不存在,創建管道\n");ret = mkfifo("fifo2", 0664);if(ret == -1) {perror("mkfifo");exit(0);}}int fd1 = open("fifo1", O_RDONLY);if(fd1 == -1) {perror("open");exit(0);}printf("打開fifo1成功,等待讀取...\n");int fd2 = open("fifo2", O_WRONLY);if(fd2 == -1) {perror("open");exit(0);}printf("打開fifo2成功,等待寫入...\n");char buffer[1024] = {0};while(1) {memset(buffer, 0, 1024);ret = read(fd1, buffer, 1024);if(ret <= 0) {perror("read");exit(0);}printf("chatterA傳來消息: %s\n", buffer);memset(buffer, 0, 1024); fgets(buffer, 1024, stdin);ret = write(fd2, buffer, strlen(buffer));if(ret == -1) {perror("write");exit(0);}}close(fd1);close(fd2);return 0;
}
在兩個終端中分別運行程序:
chatterA:
chatterB:
在運行chatterA的終端中輸入:hello,im chatterA ,回車
chatterB:
輸出了chatterA中傳來的信息,同樣,在運行chatterB的終端中輸入:hello,im chatterB ,回車
chatterA: