IPC(進程間通信)詳解

Linux環境下,進程地址空間相互獨立,每個進程各自有不同的用戶地址空間。任何一個進程的全局變量在另一個進程中都看不到,所以進程和進程之間不能相互訪問,要交換數據bi必須通過內核,在內核中開闢一塊緩衝區,進程1把數據從用戶空間放至內核緩衝區,進程2再從內核緩衝區把數據讀走,內核提供的這種機制稱爲進程間通信(IPC InterProcess Communication)
在這裏插入圖片描述
在進程間完成數據傳遞需要藉助操作系統提供的特殊方法,如:文件(因爲兩個進程共享打開的文件描述符,即虛擬地址空間內核態中的PCB映射到物理地址空間中的同一塊地方)、管道、信號、共享內存、消息隊列、套接字、命名管道等,有些方法已經被淘汰,現在常用的進程間通信方法有:
**
1.管道(使用最簡單)
2.信號(開銷最小)
3.共享映射區MMAP(無血緣關係的IPC)
4.本地套接字(最穩定)
**

管道

作用於有血緣關係的進程間通信,調用pipe函數即可創建一個管道,並且打開了讀端和寫端的文件描述符,並且通過函數的傳入參數返回打開的文件描述符。

#include <unistd.h>
int pipe(int pipefd[2])

// 成功返回0,失敗返回-1

管道的特質:
a)本質是一個僞文件(Linux中7中文件類型之一,實質爲內核緩衝區)
b)由兩個文件描述符引用,一個表示讀端,一個表示寫端。
c)規定數據從管道的寫端流入,讀端流出
管道的原理:管道實質爲內核使用環形隊列機制,藉助內核緩衝區(4KB,可以用ulimit -a命令查看,結果是 pipe size 512 bytes 8 ,其中,512代表一個扇區的大小,8表示用了8個扇區,所以是4KB,大小可調節)實現。
管道的侷限性:
a)數據能自己讀不能自己寫
b)數據一旦被讀走,便不存在於管道中
c)由於管道採用雙向半雙工通信方式(數據的流動方向是單向的稱之爲半雙工,雙向半雙工是指數可以從A端讀B端寫也可以從B端讀A端寫,雙向全雙工,即兩端都可讀可寫,比如電話通話,只讀不寫或者只寫不讀稱爲單工通信,如BB機),因此,數據只能在一個方向上流動
d)只能在有公共祖先的進程間使用pipe
對於無血緣關係的進程,可以使用FIFO實現通信,也是管道的一種
代碼實現:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(void){
    int fd[2];//作爲傳入參數,接收文件描述符
    pid_t pid;
    
    int ret = pipe(fd);
    if(-1 == ret){
        perror("pipe error:");
        exit(1);
    }
    pid = fork();
    if(-1 == pid){
        perror("fork error:");
        exit(1);
    }else if(0 == pid){//子進程 讀數據
        //由於此時子進程掌握着管道的讀寫兩端,因此需要關閉寫端保障單>向通信
        //認爲規定,fd[0]爲讀端,fd[1]爲寫端
        close(fd[1]);
        char buf[1024];
        ret = read(fd[0],buf,sizeof(buf));
        if(0 == ret){
            printf("waitting write-----\n");
        }
        write(STDOUT_FILENO,buf,ret);
    }else{
        close(fd[0]);
        write(fd[1],"hello pipe\n",strlen("hello pipe\n"));
        sleep(1);
    }
    return 0;
}

共享內存

mmap函數,用於父子進程有血緣關係的IPC,作用是把磁盤上的文件映射到內存中,完成內存和磁盤文件間的一致性,也可用於父子進程間通信。
在這裏插入圖片描述
參數:
addr:建立映射區的首地址,有Linux內核指定,一般爲NULL
length:欲創建映射區的大小
port:映射區權限PROT_READ PROT_WRITE PROT_READ|PORT_WRITE
flags:標誌位參數,值爲MAP_SHARED時,會將映射區所做的操作反映到物理設備(磁盤)上,值爲MAP_PRIVATE時,不會將映射區所做的操作反映到物理設備(磁盤)上
fd:用來建立映射區的文件描述符
offset:映射文件的偏移(4K的整數倍,即會將映射區所做的操作反映到物理設備(磁盤)上)
返回值:成功返回映射區首地址,失敗返回MAP_FAILD宏

mmap創建映射區實例

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <fcntl.h>

int main(void){
    int len,ret;
    int fd = open("mytest.txt",O_CREAT|O_RDWR,0644);
    char *p = NULL;
    if(fd < 0){
        perror("open error:");
        exit(1);
    }
    if(-1 == len){
        perror("ftruncate error:");
    }
    p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(p == MAP_FAILED){
        perror("mmap error:");
        exit(1);
    }
    strcpy(p,"abc");//向共享內存中寫數據
    close(fd);
    ret = munmap(p,4);
    if(-1 == ret){
        perror("munmap error:");
    }
    return 0;
}

mmap實現父子進程共享實例

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>

int var = 100;

int main(void){
    int *p;
    pid_t pid;
    int fd;

    fd = open("temp",O_RDWR|O_CREAT|O_TRUNC,0644);
    if(fd < 0){
        perror("open error:");
        exit(1);
    }
    unlink("temp"); //刪除臨時文件的目錄項,使之具備被釋放的條件
    ftruncate(fd,4);

    p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
    if(p == MAP_FAILED){
        perror("mmap error");
        exit(1);
    }
    close(fd); //映射區建立完畢,即可關閉文件

    pid = fork();
    if(pid < 0){
        perror("fork error");
        exit(1);
    }else if(0 == pid){
        *p = 2000;
        var = 1000;
        printf("child,*p = %d, var = %d\n",*p, var);
    }else{
        sleep(1);
        printf("parent,*p = %d,var = %d\n",*p, var);
        wait(NULL);

        int ret = munmap(p,4); //釋放映射區
        if(-1 == ret){
            perror("munmap error:");
            exit(1);
        }
    }
    return 0;
}

在這裏插入圖片描述
p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);語句中,將倒數第三個參數設置爲MAP_PRIVATE,因此父子進程獨佔各自的映射區,子進程將映射區的值改爲2000,父進程的p值不變。如果把倒數第二個參數改爲MAP_SHARED,才能完成父子進程間通信,結果如下
在這裏插入圖片描述
其中,兩個結果的父進程var值爲100,子進程var值爲1000,因爲父子進程共享文件描述符和mmap映射區,0到3G的虛擬地址中的用戶態爲讀時共享寫時複製。因此全局變量var在父子進程中各自不影響。
可以發現上面代碼中的temp文件很雞肋,只是用作創建映射區的時候用一下,然後立刻就釋放了,爲了解決這一問題,便出現了匿名映射

匿名映射

解決了創建映射區必須依賴一個文件纔可以實現的雞肋問題。
int *p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
4可以根據實際大小改變

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>

int var = 100;

int main(void){
    int *p;
    pid_t pid;

    p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
    if(p == MAP_FAILED){
        perror("mmap error");
        exit(1);
    }

    pid = fork();
    if(pid < 0){
        perror("fork error");
        exit(1);
    }else if(0 == pid){
        *p = 2000;
        var = 1000;
        printf("child,*p = %d, var = %d\n",*p, var);
    }else{
        sleep(1);
        printf("parent,*p = %d,var = %d\n",*p, var);
        wait(NULL);

        int ret = munmap(p,4); //釋放映射區
        if(-1 == ret){
            perror("munmap error:");
            exit(1);
        }
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章