APUE之mmap機制

mmap(memery-mapped I/O)可以將一個文件或其他對象(比如一段匿名內存)映射到進程的虛擬地址空間。
mmap技術的用途主要有以下兩個方面:
    [1]. 將一個文件映射到進程的虛擬地址空間後,進程就可以直接訪問這段虛擬地址來進行文件的I/O操作,而不再需要使用read、write等系統調用
    [2]. 多個進程可以通過映射同一個文件實現共享內存,來作爲進程間的一種IPC方式

mmap接口格式:
    void *mmap(void *addr,size_t len,int prot,int flag,int fd,off_t off);
    @addr   - 用於指定映射區域的起始地址。通常設置爲0,表示由系統爲該映射區選擇一個合適的起始地址
              需要注意的是,addr的值最好確保是內存頁長的整數倍
    @len    - 映射區域的長度
    @prot   - 映射區域的訪問權限,支持的權限類型如下:
                    PROT_NONE   映射區不可訪問
                    PROT_READ   映射區可讀
                    PROT_WRITE  映射區可寫
                    PROT_EXEC   映射區可執行
              需要注意的是,這裏設置的訪問權限必須確保是文件open時設置的訪問權限的子集
    @flag   - 用於設置映射區域的屬性,Linux中支持的主要屬性類型如下:
                    MAP_SHARED      共享進程的本次映射操作.
                                    這意味着後續本進程對映射區域的任何修改都對其他映射了相同區域的進程可見,並且這些修改會同步到原文件中.
                                    本標誌和MAP_PRIVATE標誌中必須指定一個
                    MAP_PRIVATE     創建一個使用COW機制的私有映射.
                                    這意味着後續本進程對映射區域的任何修改對其他進程不進程可見,並且這些修改不會同步到原文件中.
                                    本標誌和MAP_SHARED標誌中必須指定一個
                    MAP_ANONYMOUS   映射一段匿名內存,映射成功後這段內存會用0初始化.
                                    使用本標誌時,fd參數會被忽略(但最好設置爲-1),off參數必須設置爲0
                    MAP_FIXED       強制使用addr作爲實際的映射區域起始地址,不建議使用該標誌
    @fd     - 要被映射文件的描述符(顯然只有在映射對象是文件的情況下有意義).
              文件被映射前,必須先打開該文件,映射完成後該文件即使立即關閉也不會解除映射
    @off    - 要映射字節在文件中的起始偏移量(映射對象不是文件時off必須設置爲0)
              需要注意的是,off的值最好確保是內存頁長的整數倍

mmap映射文件進行I/O操作時,沒有對原文件進行追加的能力,只能在原文件當前長度範圍內操作,也就是說,mmap只有在文件被映射部分中的修改才能最終寫入文件.
mmap創建的映射區域可以通過fork繼承,這是因爲fork得到的子進程會繼承父進程的虛擬地址空間,顯然其中就包含了映射區域.
mmap創建的映射區域不能通過exec繼承,這是因爲exec會使用新程序來覆蓋原程序的地址空間.

對於設置爲MAP_SHARED對象爲文件的映射區域,修改並不會立即回寫到原文件中,何時回寫髒頁由內核決定,也可以手動回寫,接口如下:
    int msync(void *addr,size_t len,int flags);
    @addr   - 需要回寫的映射區域地址
    @len    - 需要回寫的映射區域長度
    @flags  - 用於對本次回寫操作進行控制,可控制的選項如下:
                    MS_ASYNC    簡單安排要回寫的頁
                    MS_SYNC     等待寫操作完成後返回
                    以上兩個標誌必選其一。

解除映射的方法有以下兩種:
    [1]. 進程結束時會自動解除映射
    [2]. 調用munmap函數
需要注意的是,解除映射操作並不會使映射區的內容回寫到原文件

 

例子:

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

int main(int argc,char *argv[])
{
    void *src;
    int fd;

    int page_size = getpagesize();

    fd= open("./testfile",O_RDWR | O_CREAT | O_TRUNC,S_IRUSR | S_IWUSR | S_IROTH);
    if (fd< 0) {
        perror("open");
        exit(1);
    }
    if (ftruncate(fd,5)) {
        perror("ftruncate");
        exit(1);
    }
#if TEST_SIGSEGV1
    src = mmap(0,page_size * 2,PROT_READ,MAP_SHARED,fd,0);
    if (!src) {
        perror("mmap");
        exit(1);
    }

    strcpy(src,"ha");
#else
    src = mmap(0,page_size * 2,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if (!src) {
        perror("mmap");
        exit(1);
    }

#if TEST_OVERWRITE
    strcpy(src,"hahahaha");
#elif TEST_SIGBUS
    strcpy(src + page_size,"ha");
#elif TEST_SIGSEGV2
    strcpy(src + page_size * 5,"ha");
#endif
#endif

    return 0;
}

$ gcc -Wall main.c -o mmap -DTEST_OVERWRITE
$ ./mmap
$ cat testfile
        hahah
$ gcc -Wall main.c -o mmap -DTEST_SIGBUS
$ ./mmap
        總線錯誤
$ gcc -Wall main.c -o mmap -DTEST_SIGSEGV1
$ ./mmap
        段錯誤
$ gcc -Wall main.c -o mmap -DTEST_SIGSEGV2
$ ./mmap
        段錯誤

以上結果用於印證以下幾點:
    [1]. 如果映射區域被mmap指定爲只讀,則進程試圖修改這片映射區中的數據時,就會觸發SIGSEGV信號
    [2]. 進程能夠訪問的有效地址空間取決於文件被映射部分所佔用的內存頁數量.
         如果超出這個有效地址空間,但未超出映射空間,進程訪問這部分時就會觸發SIGBUS信號;
         如果超出映射空間,進程訪問這部分時就會觸發SIGSEGV信號
    [3]. 只有在文件被映射部分中的修改才能最終寫入文件

 

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