進程間通信(IPC):操作系統爲用戶提供的幾種進程間通信方式。
管道–用於進程間的數據傳輸
本質:內核中的一塊緩衝區–通過半雙工(可以選擇方向的單向通信)通信實現數據傳輸。
原理:通過讓多個進程都能訪問到同一塊緩衝區,來實現進程間通信。
管道分類:匿名管道\命名管道
匿名管道
概念:這塊內核中的緩衝區沒有標識。
特性:只能用於具有親緣關係的進程間通信。子進程通過複製父進程的方式,獲取到管道的操作句柄進而實現訪問一個管道通信。
創建管道時,操作系統會提供兩個操作句柄(文件描述符),其中一個用於從管道讀取數據,一個向管道寫入數據。每次通信時只能有一個操作。
文件描述符:內核(kernel)利用文件描述符(file descriptor)來訪問文件。文件描述符是非負整數。打開現存文件或新建文件時,內核會返回一個文件描述符。讀寫文件也需要使用文件描述符來指定待讀寫的文件。相當於就是內核給這個文件的一個標記。
int pipe(int pipefd[2]);創建一個匿名管道,向用戶通過參數pipefd返回管道的操作句柄。
pipefd[0]:用於從管道讀取數據
pipefd[1]:用於從管道寫入數據
返回值:0-成功,-1-失敗
特性:若管道中沒有數,則read會阻塞;若管道寫滿了,則write會阻塞;管道自帶同步與互斥。
同步:對臨界資源訪問的合理性。一個進程使用完資源之後,若是還要再次使用,若有其他進程等待使用資源,該進程就必須排隊等待。
互斥:通過保證同一時間只有一個進程能夠訪問臨界資源,保證臨界資源訪問的安全性。對管道進行數據操作的大小不超過PIPE_BUF=4096的時候,則保證操作的原子性。
若管道所有的寫端被關閉(表示當前沒有進程繼續寫入數據了),read讀完管道中的數據之後,就不會再阻塞而是返回0。
若管道所有讀端被關閉(表示沒有進程讀取數據了),繼續write會觸發異常,程序退出。
管道的使用:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 int main(){
6
7 int pipefd[2]={0};
8 int ret=pipe(pipefd);
9 if(ret<0){
10 perror("pipe error");
11 return -1;
12 }
13 pid_t pid=fork();
14 if(pid<0){
15 perror("fork error");
16 return -1;
17 }else if(pid==0){
18 char buf[1024]={0};
19 int ret=read(pipefd[0],buf,1023);
20 printf("buf:[%s]-[%d]\n",buf,ret);
21
22 }else{
23 char *ptr="超級開心";
24 write(pipefd[1],ptr,strlen(ptr));
25 }
26 while(1){
27 printf("---------------------%d\n",getpid());
28
29 }
30
31 return 0;
32 }
命名管道
內核中的緩衝區具有標識符(標識符是一個可見於文件系統的管道文件),其他的進程可以通過這個標識符,找到這塊緩衝區(通過打開同一個管道文件,進而訪問到同一塊緩衝區),進而實現通信。
命令操作:mkfifo filename
int mkfifo(const char* pathname,mode_t mode);—創建命名管道文件
pathname:管道文件名稱
mode:文件權限
創建管道文件:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<sys/stat.h>
5 #include<errno.h>
6
7 int main(){
8
9 umask(0);
10 char* file="./test.fifo";
11 int ret=mkfifo(file,0664);
12 if(ret<0&&errno!=EEXIST){
13 perror("mkfifo error");
14 return -1;
15 }
16 return 0;
17 }
運行結果:
打開特性
若管道文件以只讀的方式打開,則會阻塞,直到這個管道文件以被以寫的方式打開。
若管道以只寫的方式發開,則會阻塞,直到這個管道文件被以讀的方式打開。
若管道以讀寫的方式打開,則不會阻塞。
打開特性:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<sys/stat.h>
5 #include<errno.h>
6 #include<fcntl.h>
7 int main(){
8
9 umask(0);
10 char* file="./test.fifo";
11 int ret=mkfifo(file,0664);
12 if(ret<0&&errno!=EEXIST){
13 perror("mkfifo error");
14 return -1;
15 }
16 printf("create fifo success\n");
17 int fd=open(file,O_RDONLY);
18 if(fd<0){
19 perror("open error");
20 return -1;
21 }
22 printf("open fifo success\n");
23 return 0;
24 }
運行這個程序,程序阻塞在讀取管道這裏
當我們向管道中寫入數據後,程序運行結束。
管道特性
1.管道生命週期隨進程。
2.半雙工特性。
3.自帶同步和互斥。
4.提供字節流服務–有序,鏈接,可靠的字節流傳輸。
共享內存–用於進程間的數據共享
最快的進程間通信方式。爲什麼呢?
因爲共享內存直接通過虛擬地址映射訪問物理內存,而其他方式因爲都是在內核中的緩衝區,因此通信時會涉及到用戶態與內核態之間的兩次數據拷貝。但是共享內存通信方式不會,所以通信速度最快。
共享內存的實現:
1.創建共享內存–在物理內存上開闢一塊內存空間(具有標識符)。
2.將共享內存映射到各個進程的虛擬地址空間。
3.進程就可以通過虛擬地址直接訪問到共享內存–多個進程要是映射同一塊物理內存,就可以通過這塊內存實現數據共享。
4.解除映射關係。
5.刪除共享內存。
查看&刪除進程間通信資源命令:
ipcs:查看進程間通信資源 ipcrm:刪除進程間通信資源
-m:查看共享內存
-q:查看消息隊列
-s:查看信號量
int shmget(key_t key,int size,int flag);–創建共享內存
key:共享內存的標識符,多個進程通過相同的標識符可以打開同一塊共享內存
size:共享內存大小
flag:IPC_CREAT(存在打開,不存在創建)|IPC_EXCL(報錯,不報錯) |權限
返回值:成功返回一個操作句柄,失敗返回-1
void shmat(int shmid,void addr,int flag);–映射共享內存
shmid:共享內存操作句柄
addr:映射到虛擬地址空間的首地址,通常置NULL
flag:通常0-可讀可寫 SHM_RDONLY-只讀
返回值:
成功,返回映射的虛擬空間首地址,通過這個地址對共享內存進行操作
失敗,返回-1
int shmdt(void shmstart);–解除映射*
shmstart:映射到虛擬地址空間的首地址
成功返回0,失敗返回-1
int shmctl(int shmid,int cmd,struct shmid_ds buf);–刪除共享內存*
shmid:共享內存操作句柄
cmd:具體對共享內存要進行的操作—IPC_RMID(刪除共享內存)
成功返回0,失敗返回-1
修改共享內存中的值:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/shm.h>
#define IPC_KEY 0x12345678
int main(){
//創建共享內存
int shmid=shmget(IPC_KEY,32,IPC_CREAT|0664);
if(shmid<0){
perror("create error");
return -1;
}
//映射共享內存
void* shmstart=shmat(shmid,NULL,0);
if(shmstart==(void*)-1){
perror("shmat errror");
return -1;
}
//修改共享內存中的值
int i=0;
while(1){
//格式化數據寫入標準輸出
//覆蓋式寫入
sprintf(shmstart,"%s-%d\n","share memory",i++);
sleep(1);
}
//解除映射
shmdt(shmstart);
//刪除映射
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
讀取共享內存中的值:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/shm.h>
#define IPC_KEY 0x12345678
int main(){
//創建共享內存
int shmid=shmget(IPC_KEY,32,IPC_CREAT|0664);
if(shmid<0){
perror("create error");
return -1;
}
//映射共享內存
void* shmstart=shmat(shmid,NULL,0);
if(shmstart==(void*)-1){
perror("shmat errror");
return -1;
}
//讀取共享內存中的值
int i=0;
while(1){
printf("%s\n",shmstart);
sleep(1);
}
//解除映射
shmdt(shmstart);
//刪除映射
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
運行結果:
當刪除共享內存的時候,共享內存不會立即被刪除(因爲有可能會造成正在訪問的進程崩潰),而是將key修改爲0,表示這塊共享內存將不再繼續接收映射鏈接,當這塊共享內存的映射鏈接數爲0的時候則自動被釋放。
共享內存特性:
1.最快的進程間通信方式。
2.生命週期隨內核。
注意:共享內存的操作時不安全的(並不會自動具備同步與互斥關係,需要操作用戶進行控制)
消息隊列–用於進程間的數據傳輸
本質:內核中的一個隊列,多個進程通過向同一個隊列中添加節點和獲取節點實現通信。傳輸一個有類型(優先級)的數據塊。
特性:
1.自帶同步與互斥。
2.生命週期隨內核。
3.數據傳輸自帶優先級。
信號量–用於實現進程間的控制
作用:用於實現進程間的同步和互斥。
本質:內核中的一個計數器+pcb等待隊列(對資源進行計數)。
互斥的實現:
通過只有0/1的計數器,實現對臨界資源訪問狀態的標記;在訪問臨界資源之前先獲取信號量,計數-1;若計數<0則使進程等待(將進程pcb加入隊列);否則可以對臨界資源進行訪問(並且在訪問期間,已經將臨界資源的狀態置爲不可訪問狀態,因此可以保證其他進程不會再訪問臨界資源),當前進程訪問完畢之後,則對計數器進行+1,並且喚醒一個進程(將一個pcb出隊,置爲運行狀態)。
同步的實現:
信號量是一個對資源的計數,可以通過計數判斷是否能夠獲取一個資源進行處理;若計數<0,則表示不能獲取(並且計數進行-1),則需要等待(加入pcb隊列)。這時候若其他進程產生一個資源,則會對計數進行+1,若計數<=0,則喚醒一個進程。假如說開始有100個停車位,每進來一輛車就會-1,當停車位數爲0的時候,說明停車場已經停滿了車,這時候再進來車時計數器還是會-1,但是車不能進入,只能在外面排隊等着,當停車場有車開走了,計數器+1(此時計數器的數不一定>0),並且喚醒等待隊列中的一個車,告訴他有停車爲了可以去停車了。