歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux管理 >> Linux網絡 >> linux網絡編程之共享內存簡介和mmap函數

linux網絡編程之共享內存簡介和mmap函數

日期:2017/3/3 16:26:21   编辑:Linux網絡

一、共享內存簡介

共享內存區是最快的IPC形式,這些進程間數據傳遞不再涉及到內核,換句話說是進程不再通 過執行進入內核的系統調用來傳遞彼此的數據。

即每個進程地址空間都有一個共享存儲器的映射區,當這塊區域都映射到相同的真正的物理地址空間時,可以通過這塊 區域進行數據交換,例如共享庫就是這麼實現的,很多進程都會使用同一個函數如printf,也許在真正的物理地址空間中只 存在一份printf.o ,然後所有進程都映射到這一份printf.o 就實現了共享。

用管道或者消息隊列傳遞數據:

用共享內存傳遞數據:

即使有共享內存傳遞數據比用消息隊列和管道來說,減少了進入內核的次數,提高了效率。

二、mmap 函數

#include <sys/mman.h>

功能:將文件或者設備空間映射到共享內存區。

原型 void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);

參數

addr: 要映射的起始地址,通常指定為 NULL,讓內核自動選擇

len:映射到進程地址空間的字節數

prot:映射區保護方式

flags:標志

fd:文件描述符

offset:從文件頭開始的偏移量,必須是頁大小的整數倍(在32位體系統結構上通常是4K)

返回值:成功返回映射到 的內存區的起始地址;失敗返回-1

prot 參數取值:

PROT_EXEC 表示映射的這一段可執行,例如映射共享庫

PROT_READ 表示映射的這一段可讀

PROT_WRITE 表示映射的這一段可寫

PROT_NONE 表示映射的這一段 不可訪問

flag參數有很多種取值,這裡只講兩種,其它取值可查看mmap(2)

MAP_SHARED 多個進程對同一個文 件的映射是共享的,一個進程對映射的內存做了修改,另一個進程也會看到這種變化。

MAP_PRIVATE 多個進程對同 一個文件的映射不是共享的,一個進程對映射的內存做了修改,另一個進程並不會看到這種變化,也不會真的寫到文件中去 。

內存映射文件示意圖:

如果mmap成功則返回映射首地址,如果出錯則返回常數MAP_FAILED。當進程終止時,該進程的映射內存會自動解除,也 可以調用munmap解除映射:

功能:取消mmap函數建立的映射

原型 int munmap(void *addr, size_t len);

參 數

addr: 映射的內存起始地址

len:映射到進程地址空間的字節數

返回值:成功返回0;失敗返回-1

下面寫 兩個程序測試一下:

mmap_write.c

#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/mman.h>
    
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)
    
typedef struct stu
{
    char name[4];
    int age;
} STU;
    
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    int fd;
    fd = open(argv[1], O_CREAT | O_RDWR | O_TRUNC, 0666);
    if (fd == -1)
        ERR_EXIT("open");
    
    lseek(fd, sizeof(STU) * 5 - 1, SEEK_SET);
    write(fd, "", 1);
    
    STU *p;
    p = (STU *)mmap(NULL, sizeof(STU) * 5, PROT_READ | PROT_WRITE,
                    MAP_SHARED, fd, 0);
    
    if (p == -1)
        ERR_EXIT("mmap");
    
    char ch = 'a';
    int i;
    for (i = 0; i < 5; i++)
    {
        memcpy((p + i)->name, &ch, 1);
        (p + i)->age = 20 + i;
        ch++;
    }
    
    printf("initialize over\n");
    
    munmap(p, sizeof(STU) * 5);
    printf("exit...\n");
    return 0;
}

mmap_read.c

#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/mman.h>
    
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)
    
typedef struct stu
{
    char name[4];
    int age;
} STU;
    
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    int fd;
    fd = open(argv[1], O_RDWR);
    if (fd == -1)
        ERR_EXIT("open");
    
    
    STU *p;
    p = (STU *)mmap(NULL, sizeof(STU) * 5, PROT_READ | PROT_WRITE,
                    MAP_SHARED, fd, 0);
    
    if (p == -1)
        ERR_EXIT("mmap");
    
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("name = %s age = %d\n", (p + i)->name, (p + i)->age);
    }
    munmap(p, sizeof(STU) * 5);
    printf("exit...\n");
    return 0;
}

先運行mmap_write ,然後用od -c 查看文件內容:

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./mmap_write test

initialize over

exit...

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ od -c test

0000000 a \0 \0 \0 024 \0 \0 \0 b \0 \0 \0 025 \0 \0 \0

0000020 c \0 \0 \0 026 \0 \0 \0 d \0 \0 \0 027 \0 \0 \0

0000040 e \0 \0 \0 030 \0 \0 \0

0000050

注意od -c 輸出的是八進制,024即20,即對內存的操作寫入了文件。

再嘗試運行mmap_read,輸出如下:

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./mmap_read test

name = a age = 20

name = b age = 21

name = c age = 22

name = d age = 23

name = e age = 24

exit...

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$

再次將文件test 映射到內存 ,然後從內存讀取到了文件的內容。

mmap 編程注意點:

1、映射不能改變文件的大小;

2、可用於進程間通信 的有效地址空間不完全受限於被映射文件的大小;

3、文件一旦被映射後,所有對映射區域的訪問實際上是對內存區域的 訪問。映射區域內容寫回文件時,所寫內容不能超過文件的大小;

對於1,3點,將mmap_write.c 中40行以後的代碼 中的5改成10,即映射的內存大於文件的大小,這樣寫入是不會出錯的,因為是向內存寫入,但用od 查看時發現文件還是40 個字節,即只有前5個STU才被真正寫入到了文件。

對於第2點,將mmap_write.c 和 mmap_read.c 都按上面說的更改 成10,然後在mmap_write.c 中munmap 函數之前sleep(10); 先運行mmap_write,再在另一終端運行mmap_read,觀察結果:

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./mmap_read test

name = a age = 20

name = b age = 21

name = c age = 22

name = d age = 23

name = e age = 24

name = f age = 25

name = g age = 26

name = h age = 27

name = i age = 28

name = j age = 29

exit...

即在 mmap_write 對映射內存區域寫入之後尚未取消映射時,mmap_read 也映射了test 文件,兩個虛擬進程地址空間的映射區域 都指向了同一塊物理內存,所以也能讀到write 進程對內存的修改,但進程結束後查看test 文件,還是40個字節而已。內 存的映射是以頁面為單位的,一般為4k,所以才有第2條的說法,其實這才是真正體現共享內存可以進程間通信的所在。

最後一點,與write 類似,將文件映射到內存後對內存進行寫入,不一定會馬上寫回文件,有可能內核也會產生一 個緩沖區,找個適當的時間內核再寫回設備文件,write 之後可以調用fsync 進行同步,同樣地,mmap 可以調用msync 進 行同步。

Copyright © Linux教程網 All Rights Reserved