一、進程間通信
(一)系統介紹進程間通信
進程間通信(IPC)介紹
小編插入的這篇文章詳細介紹了進程間通信的一些內容,大家可以一起學習。
(二)進程間通信的方法
1、管道
2、信號量
3、共享內存
4、消息隊列
5、套接字
? ? ? ? 接下來的幾篇文章,小編就會按照順序介紹這幾種方法(加粗的為重點內容),今天首先要學到的是管道。
二、管道
? ? ? ? 關于管道這個詞,其實我們在之前有過一點點了解,不知道大家還是否記得。在Linux(四)基礎命令2中,| 代表管道,當時是和grep(過濾)搭配使用。今天,我們就正式接觸管道這個內容了。
? ? ? ? 首先是管道的分類,分為有名管道(命名管道)和無名管道。它們的區別是有名管道在任意兩個進程間通信,無名管道在父子進程之間通信。
(一)有名管道
1、創建有名管道? ?mkfifo
2、打開管道? ?open()
3、關閉管道? ?close()
4、讀數據? ?read()
5、寫入數據? ?write()
? ? ? ? 在學習完上面的操作后,我們來思考一個問題:如果進程a要從鍵盤獲取數據傳遞給另一個進程b,不使用管道操作應該如何完成?
? ? ? ? 其實在C語言中,我們可以通過文件操作完成,但是通過文件進行進程間通信存在兩個問題:(1)速度慢(2)讀數據時不知道a什么時候會寫入。
? ? ? ? 下面,大家就和小編一起通過有名管道來演示進程間通信。
? ? ? ? 管道創建之后,它會在內存上分配一塊空間(也就是通過管道的數據存在了內存中),而管道本身在磁盤里,所以管道的大小永遠為0。
? ? ? ? 管道有兩端(大家可以想象一下它想一個水管有兩頭),一端為讀端,一端為寫端(就像水管一遍流入一遍流出)。即管道一個是讀打開,一個是寫打開。
? ? ? ? 讓我們將下面的代碼在終端中寫入,進行演示。
//a.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<fcntl.h>
#include<string.h>int main(){int fd = open("fifo",O_WRONLY);//當前路徑可以省略,絕對路徑要寫全assert(fd!=-1);printf("fd = %d\n",fd);write(fd,"hello",5);close(fd);
}
//b.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<fcntl.h>int main(){int fd = open("fifo",O_RDONLY);assert(fd!=-1);printf("fd = %d\n",fd);char buf[128] = {0};read(fd,buf,127);printf("read:%s\n",buf);close(fd);exit(0);
}
我們打開兩個終端界面。
? ? ? ? 下面我們將上面的代碼進行修改,使a循環寫入(從鍵盤寫入),b循環讀取。
//a.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<fcntl.h>
#include<string.h>int main(){int fd = open("fifo",O_WRONLY);//當前路徑可以省略,絕對路徑要寫全assert(fd!=-1);printf("fd = %d\n",fd);while(1){ printf("input:\n");char buff[128] = {0};fgets(buff,128,stdin);if(strncmp(buff,"end",3)==0){break;}write(fd,buff,strlen(buff));} close(fd);
}//b.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<fcntl.h>int main(){int fd = open("fifo",O_RDONLY);assert(fd!=-1);printf("fd = %d\n",fd);while(1){char buf[128] = {0};if(read(fd,buf,127)==0)//用于驗證管道寫端關閉,讀read返回值為0(第3個特點){break;}printf("read:%s\n",buf);} close(fd);exit(0);
}
通過上面這兩個個例子,我們來總結一下管道的特點。
? ? ? ? (1)管道必須讀,寫進程同時open,否則會阻塞;
????????(2)如果管道沒有數據,那么read會阻塞;????
????????(3)管道的寫端關閉,讀read返回值為0;
? ? ? ? (4)管道打開的時候只有只讀和只寫兩種方式,讀寫方式打開是未定義的。? ? ? ? ? ?
(二)無名管道
? ? ? ? 有名管道之所以不限制進程,就是因為它有名字可以被找到;而無名管道不可以,如果不限制進程,它又沒有名字,我們就沒有辦法找到它了。因此,無名管道只能用于父子進程間通信。
? ? ? ? 創建無名管道? ?pipe
還是同樣的思路,學習一個新的命令要使用幫助手冊 man pipe。
//fi.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<fcntl.h>
#include<string.h>int main(){int fd[2];assert(pipe(fd)!=-1);pid_t pid = fork();assert(pid!=-1);//先open 后fork 共享文件描述符if(pid==0){ close(fd[1]);char buff[128] = {0};read(fd[0],buff,127);printf("child read:%s\n",buff);close(fd[0]);} else{close(fd[0]);write(fd[1],"hello",5);close(fd[1]);}exit(0);
}
?通過上面兩個例子,下面小編帶領大家總結一下管道的特點:
? ? ? ? (1)管道必須讀,寫進程同時open,否則會阻塞;
? ? ? ? (2)如果管道沒有數據,那么read會阻塞;
? ? ? ? (3)管道的寫端關閉,讀read返回值為0;
? ? ? ? (4)管道打開的時候只有只讀和只寫兩種方式,讀寫方式打開是未定義的;
? ? ? ? (5)無論有名還是無名,寫入管道的數據都在內存中(管道的大小永遠為0,面試的重點)
? ? ? ? (6)管道是一種半雙工通信方式(通信方式有單工,半雙工,全雙工)
? ? ? ? (7)有名管道和無名管道的區別:有名管道可以在任意進程間使用,無名管道主要在父子進程
間通信
? ? ? ? (8)管道的讀端關閉,寫會產生異常(發送信號SIGPIPE)
小編通過改變a.c的代碼來進行驗證,代碼如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<fcntl.h>
#include<string.h>
#include<signal.h>
void sig_fun(int sig){printf("sig = %d\n",sig);
}
int main(){signal(SIGPIPE,sig_fun);int fd = open("fifo",O_WRONLY);//當前路徑可以省略,絕對路徑要寫全assert(fd!=-1);printf("fd = %d\n",fd);while(1){ printf("input:\n");char buff[128] = {0};fgets(buff,128,stdin);if(strncmp(buff,"end",3)==0){break;}write(fd,buff,strlen(buff));} //write(fd,"hello",5);close(fd);
}
(三)管道的實現
通過上面的圖片,我們可以直到管道其實就是一個循環隊列。
如果管道是空的,讀操作會堵塞;如果管道是滿的,寫操作會堵塞。