進程間通信---mmap詳解(與system V ipc通信對照)


目前,進程間通信主要集中在管道和共享內存上使用,共享內存是總所周知的直接對內存映射操作,速度最快的通信方式,缺點,可能就是數據同步沒有提供同步機制

共享存儲映射

存儲映射I/O 

存儲映射I/O (Memory-mapped I/O) 使一個磁盤文件與存儲空間中的一個緩沖區相映射於是當從緩沖區中取數據就相當於讀文件中的相應字節於此類似將數據存入緩沖區則相應的字節就自動寫入文件這樣,就可在不使用readwrite函數的情況下,使用地址(指針)完成I/O操作。

使用這種方法首先應通知內核將一個指定文件映射到存儲區域中這個映射工作可以通過mmap函數來實現

 

 

mmap函數

void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);

返回:成功:返回創建的映射區首地址;失敗:MAP_FAILED

參數

  addr: 建立映射區的首地址,由Linux內核指定。使用時,直接傳遞NULL

  length: 欲創建映射區的大小

  prot 映射區權限PROT_READPROT_WRITEPROT_READ|PROT_WRITE

  flags 標志位參數(常用於設定更新物理區域、設置共享、創建匿名映射區)

       MAP_SHARED:  會將映射區所做的操作反映到物理設備(磁盤)上。

       MAP_PRIVATE: 映射區所做的修改不會反映到物理設備。

     fd 用來建立映射區的文件描述符

     offset 映射文件的偏移(4k的整數倍)

munmap函數

malloc函數申請內存空間類似的,mmap建立的映射區在使用結束后也應調用類似free的函數來釋放

int munmap(void *addr, size_t length); 成功:0; 失敗:-1

 

 

借鑒mallocfree函數原型,嘗試裝自定義函數smalloc,sfree來完成映射區的建立和釋放。思考函數接口該如何設計?

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

void *smalloc(size_t size)
{
    void *p;

    p = mmap(NULL, size, PROT_READ|PROT_WRITE, 
            MAP_SHARED|MAP_ANON, -1, 0);
    if (p == MAP_FAILED) {        
        p = NULL;
    }

    return p;
}

void sfree(void *ptr, size_t size)
{
    munmap(ptr, size);
}

int main(void)
{
    int *p;
    pid_t pid;
    
    p = smalloc(4);

    pid = fork();                //創建子進程
    if (pid == 0) {
        *p = 2000;
        printf("child, *p = %d\n", *p);
    } else {
        sleep(1);
        printf("parent, *p = %d\n", *p);
    }

    sfree(p, 4);

    return 0;
}

 

 

mmap注意事項

mmap.c

 

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

void sys_err(char *str)
{
    perror(str);
    exit(1);
}

int main(void)
{
    char *mem;
    int len = 0;

    int fd = open("hello678", O_RDWR|O_CREAT|O_TRUNC, 0644);
    if (fd < 0)
        sys_err("open error");
    len = lseek(fd, 3, SEEK_SET);   //獲取文件大小,根據文件大小創建映射區
    write(fd, "\0", 1);              //實質性完成文件拓展
    len = lseek(fd, 0, SEEK_END);
    
    printf("The length of file = %d\n", len);

    mem = mmap(NULL, len, PROT_WRITE, MAP_PRIVATE, fd, 0);
    if (mem == MAP_FAILED)            //出錯判斷
        sys_err("mmap err: ");
    close(fd);

    strcpy(mem, "aaa");
    printf("%s\n", mem);

    if (munmap(mem,  len) < 0)
        sys_err("munmap");

    return 0;
}

//思考:
//1. 如果mem++,munmap可否成功?
//2. 如果open時O_RDONLY, mmap時PROT參數指定PROT_READ|PROT_WRITE會怎樣?
//3. 如果文件偏移量為1000會怎樣?
//4. 如果不檢測mmap的返回值,會怎樣?
//5. mmap什么情況下會調用失敗?
//6. 對mem越界操作會怎樣?
//7. 文件描述符先關閉,對mmap映射有沒有影響?
//8. 可以open的時候O_CREAT一個新文件來創建映射區嗎?

 

 

 

思考:

1. 如果mem++munmap可否成功?

2. 如果openO_RDONLY, mmapPROT參數指定PROT_READ|PROT_WRITE會怎樣?

3. 如果文件偏移量為1000會怎樣?

5. mmap什么情況下會調用失敗?

6. mem越界操作會怎樣?

7. 文件描述符先關閉,對mmap映射有沒有影響?

4. 如果不檢測mmap的返回值,會怎樣?

8. 可以open的時候O_CREAT一個新文件來創建映射區嗎?

總結使用mmap時務必注意以下事項

  1. 創建映射區的過程中,隱含着一次對映射文件的讀操作。
  2. MAP_SHARED時,要求:映射區的權限應 <=文件打開的權限(出於對映射區的保護)。而MAP_PRIVATE則無所謂,因為mmap中的權限是對內存的限制。
  3. 映射區的釋放與文件關閉無關。只要映射建立成功,文件可以立即關閉。
  4. 特別注意,當映射文件大小為0時,不能創建映射區。所以:用於映射的文件必須要有實際大小!! mmap使用時常常會出現總線錯誤,通常是由於共享文件存儲空間大小引起的。
  5. munmap傳入的地址一定是mmap的返回地址。堅決杜絕指針++操作。
  6. 如果文件偏移量必須為4K的整數倍
  7. mmap創建映射區出錯概率非常高一定要檢查返回值確保映射區建立成功再進行后續操作

mmap父子進程通信

父子等有血緣關系的進程之間也可以通過mmap建立的映射區來完成數據通信。但相應的要在創建映射區的時候指定對應的標志位參數flags

MAP_PRIVATE:  (私有映射)  父子進程各自獨占映射區;

MAP_SHARED:  (共享映射)  父子進程共享映射區;

  父進程創建映射區,然后fork子進程,子進程修改映射區內容,而后,父進程讀取映射區內容,查驗是否共享。

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.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_SHARED, fd, 0);
    //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    if(p == MAP_FAILED){        //注意:不是p == NULL
        perror("mmap error");
        exit(1);
    }
    close(fd);                    //映射區建立完畢,即可關閉文件

    pid = fork();                //創建子進程
    if(pid == 0){
        *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 (ret == -1) {
            perror("munmap error");
            exit(1);
        }
    }

    return 0;
}

  

結論:父子進程共享:1. 打開的文件  2. mmap建立的映射區(但必須要使用MAP_SHARED)

匿名映射

通過使用我們發現使用映射區來完成文件讀寫操作十分方便父子進程間通信也較容易但缺陷是每次創建映射區一定要依賴一個文件才能實現。通常為了建立映射區要open一個temp文件,創建好了再unlinkclose掉,比較麻煩。 可以直接使用匿名映射來代替。其實Linux系統給我們提供了創建匿名映射區的方法無需依賴一個文件即可創建映射區同樣需要借助標志位參數flags來指定

使用MAP_ANONYMOUS (MAP_ANON), 如:

int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

    "4"隨意舉例,該位置表大小,可依實際需要填寫。

    fork_map_anon_linux.c

 

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

int main(void)
{
    int *p;
    pid_t pid;
    
    int fd = open("/dev/zero", O_RDWR);
    //p = mmap(NULL, 400, PROT_READ|PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
    p = mmap(NULL, 400, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if(p == MAP_FAILED){        //注意:不是p == NULL
        perror("mmap error");
        exit(1);
    }
    close(fd);

    pid = fork();                //創建子進程
    if(pid == 0){
        *p = 2000;
        printf("child, *p = %d\n", *p);
    } else {
        sleep(1);
        printf("parent, *p = %d\n", *p);
    }

    munmap(p, 400);                //釋放映射區

    return 0;
}

 

 

 

需注意的是,MAP_ANONYMOUSMAP_ANON這兩個宏是Linux操作系統特有的宏。在類Unix系統中如無該宏定義,可使用如下兩步來完成匿名映射區的建立。

fd = open("/dev/zero", O_RDWR);

p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);

    fork_map_anon.c

 

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

int main(void)
{
    int *p;
    pid_t pid;
    
    int fd;
    fd = open("/dev/zero", O_RDWR);

    p = mmap(NULL, 400, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);

    if(p == MAP_FAILED){        //注意:不是p == NULL
        perror("mmap error");
        exit(1);
    }

    pid = fork();                //創建子進程
    if(pid == 0){
        *p = 2000;
        printf("child, *p = %d\n", *p);
    } else {
        sleep(1);
        printf("parent, *p = %d\n", *p);
    }

    munmap(p, 4);                //釋放映射區

    return 0;
}

 

結論:

MAP_PRIVATE:在父子進程間通信,映射的區域不能互相訪問,或者不能讀取父或子寫入映射內存地址空間的數據

 

mmap無血緣關系進程間通信

實質上mmap是內核借助文件幫我們創建了一個映射區,多個進程之間利用該映射區完成數據傳遞。由於內核空間多進程共享,因此無血緣關系的進程間也可以使用mmap來完成通信。只要設置相應的標志位參數flags即可。若想實現共享,當然應該使用MAP_SHARED了。

例:

mmap_r.c

 

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

struct STU {
    int id;
    char name[20];
    char sex;
};

void sys_err(char *str)
{
    perror(str);
    exit(-1);
}

int main(int argc, char *argv[])
{
    int fd;
    struct STU student;
    struct STU *mm;

    if (argc < 2) {
        printf("./a.out file_shared\n");
        exit(-1);
    }

    fd = open(argv[1], O_RDONLY);
    if (fd == -1)
        sys_err("open error");

    mm = mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED, fd, 0);
    if (mm == MAP_FAILED)
        sys_err("mmap error");
    
    close(fd);

    while (1) {
        printf("id=%d\tname=%s\t%c\n", mm->id, mm->name, mm->sex);
        usleep(10000);
    }

    munmap(mm, sizeof(student));

    return 0;
}

 

mmap_w.c

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

struct STU {
    int id;
    char name[20];
    char sex;
};

void sys_err(char *str)
{
    perror(str);
    exit(1);
}

int main(int argc, char *argv[])
{
    int fd;
    struct STU student = {10, "xiaoming", 'm'};
    char *mm;

    if (argc < 2) {
        printf("./a.out file_shared\n");
        exit(-1);
    }

    fd = open(argv[1], O_RDWR | O_CREAT, 0664);
    ftruncate(fd, sizeof(student));

    mm = mmap(NULL, sizeof(student), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (mm == MAP_FAILED)
        sys_err("mmap");

    close(fd);

    while (1) {
        memcpy(mm, &student, sizeof(student));
        student.id++;
        sleep(1);
    }

    munmap(mm, sizeof(student));

    return 0;
}

 

 

以上總結了mmap的用法,現在和ipc system V的共享內存比較一下

shm圖示例:

 

   

 

(1)通過int shmget(key_t key, size_t size, int shmflg);在物理內存創建一個共享內存,返回共享內存的編號。
(2)通過void *shmat(int shmid, constvoid shmaddr,int shmflg);連接成功后把共享內存區對象映射到調用進程的地址空間
(3)通過void *shmdt(constvoid* shmaddr);斷開用戶級頁表到共享內存的那根箭頭。
(4)通過int shmctl(int shmid, int cmd, struct shmid_ds* buf);釋放物理內存中的那塊共享內存。

總結mmap和shm:
1、mmap是在磁盤上建立一個文件,每個進程地址空間中開辟出一塊空間進行映射。
而對於shm而言,shm每個進程最終會映射到同一塊物理內存。shm保存在物理內存,這樣讀寫的速度要比磁盤要快,但是存儲量不是特別大。
2、相對於shm來說,mmap更加簡單,調用更加方便,所以這也是大家都喜歡用的原因。
3、另外mmap有一個好處是當機器重啟,因為mmap把文件保存在磁盤上,這個文件還保存了操作系統同步的映像,所以mmap不會丟失,但是shmget就會丟失。

 

具體的話請看這篇總結:

https://www.cnblogs.com/stevensfollower/p/4897711.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM