進程間通信(IPC)


進程間通信(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),並且喚醒等待隊列中的一個車,告訴他有停車爲了可以去停車了。

發佈了84 篇原創文章 · 獲贊 72 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章