天天看點

linux mmap 詳解【轉】

一.前言

mmap的具體實作以前在學習核心時學習過,但是對于其中的很多函數是一知半解的,有些隻能根據其函數名來猜測其具體的功能,在本文中,一起來重新深入了解其

具體的實作。

二.mmap的使用者層應用

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize); 

具體參數含義

start :  指向欲映射的記憶體起始位址,通常設為 NULL,代表讓系統自動標明位址,映射成功後傳回該位址。

length:  代表将檔案中多大的部分映射到記憶體。

prot  :  映射區域的保護方式。可以為以下幾種方式的組合:

                    PROT_EXEC 映射區域可被執行

                    PROT_READ 映射區域可被讀取

                    PROT_WRITE 映射區域可被寫入

                    PROT_NONE 映射區域不能存取

flags :  影響映射區域的各種特性。在調用mmap()時必須要指定MAP_SHARED 或MAP_PRIVATE。

                    MAP_FIXED 如果參數start所指的位址無法成功建立映射時,則放棄映射,不對位址做修正。通常不鼓勵用此旗标。

                    MAP_SHARED 對映射區域的寫入資料會複制回檔案内,而且允許其他映射該檔案的程序共享。

                    MAP_PRIVATE 對映射區域的寫入操作會産生一個映射檔案的複制,即私人的“寫入時複制”(copy on write)對此區域作的任何修改都不會寫回原來的檔案内容。

                    MAP_ANONYMOUS建立匿名映射。此時會忽略參數fd,不涉及檔案,而且映射區域無法和其他程序共享。

                    MAP_DENYWRITE隻允許對映射區域的寫入操作,其他對檔案直接寫入的操作将會被拒絕。

                    MAP_LOCKED 将映射區域鎖定住,這表示該區域不會被置換(swap)。

fd    :  要映射到記憶體中的檔案描述符。如果使用匿名記憶體映射時,即flags中設定了MAP_ANONYMOUS,fd設為-1。有些系統不支援匿名記憶體映射,則可以使用fopen打開/dev/zero檔案,

          然後對該檔案進行映射,可以同樣達到匿名記憶體映射的效果。

offset:檔案映射的偏移量,通常設定為0,代表從檔案最前方開始對應,offset必須是PAGE_SIZE的整數倍。

傳回值:

      若映射成功則傳回映射區的記憶體起始位址,否則傳回MAP_FAILED(-1),錯誤原因存于errno 中。

錯誤代碼:

            EBADF  參數fd 不是有效的檔案描述詞

            EACCES 存取權限有誤。如果是MAP_PRIVATE 情況下檔案必須可讀,使用MAP_SHARED則要有PROT_WRITE以及該檔案要能寫入。

            EINVAL 參數start、length 或offset有一個不合法。

            EAGAIN 檔案被鎖住,或是有太多記憶體被鎖住。

            ENOMEM 記憶體不足。

使用者層的調用很簡單,其具體功能就是直接将實體記憶體直接映射到使用者虛拟記憶體,使使用者空間可以直接對實體空間操作。但是對于核心層而言,其具體實作比較複雜。

三.mmap的核心實作

對于mmap的核心有了解的都會知道使用者層的mmap到核心層的mmap其中多了一個參數vma_struct這個結構體,在開始時對于這個參數很疑惑就是這個參數的值是哪兒來的,

在這裡我們會一一來講述。

mmap() ---> sys_mmap_pgoff() 核心系統調用函數

munmap() --->sys_munmap() 核心系統調用函數,其最終調用unmap_region()來解除映射關系,不需要對應的file_operation有unmap操作項.

還是從do_mmap開始吧。

3.1 do_mmap

參數說明:

file  :就是使用者層想要映射的file

addr  :欲映射的起始位址,即使用者層的start

prot  :使用者層傳入的port

flag  :同上

offset:同上

從這裡可以知道,這裡面的參數幾乎均是使用者層傳入的參數。

static inline unsigned long do_mmap(struct file *file, unsigned long addr,unsigned long len, unsigned long prot,

                                    unsigned long flag, unsigned long offset)

{

    unsigned long ret = -EINVAL;

    if ((offset + PAGE_ALIGN(len)) < offset)  --頁對齊len,檢測傳入參數是否有誤。

        goto out;

    if (!(offset & ~PAGE_MASK))           --檢測offset是否頁對齊。映射時隻能映射頁對齊的長度。

        ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);  

out:

    return ret;

}

3.2 do_mmap_pgoff

這個函數是巨大的。

unsigned long do_mmap_pgoff(struct file * file, unsigned long addr,unsigned long len, unsigned long prot,unsigned long flags, unsigned long pgoff)

    struct mm_struct * mm = current->mm;      --目前使用者程序的mm

    struct inode *inode; 

    unsigned int vm_flags;

    int error;

    int accountable = 1;

    unsigned long reqprot = prot;

    if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))   --是否隐藏了可執行屬性。

        if (!(file && (file->f_path.mnt->mnt_flags & MNT_NOEXEC)))

            prot |= PROT_EXEC;

    if (!len)         

        return -EINVAL;

    if (!(flags & MAP_FIXED))              -  

        addr = round_hint_to_min(addr);    --判斷輸入的欲映射的起始位址是否小于最小映射位址,如果小于,将addr修改為最小位址,不過前提是MAP_FIXED旗标沒有設定。

    error = arch_mmap_check(addr, len, flags);   --不同平台對于mmap參數的不同檢測。這裡之間傳回0

    if (error)

        return error;

    len = PAGE_ALIGN(len);        --檢測len是否越界,len的範圍在0~TASK_SIZE之間。

    if (!len || len > TASK_SIZE)

        return -ENOMEM;             --錯誤值為nomem

    if ((pgoff + (len >> PAGE_SHIFT)) < pgoff)  --再次檢測是否越界。我們這裡不得不小心哪個暈頭了傳入一個莫名其妙的值

    return -EOVERFLOW;

    if (mm->map_count > sysctl_max_map_count)   --在一個程序中對于mmap個數是有限制的。超出了還是nomem的錯誤。

        return -ENOMEM;

    addr = get_unmapped_area(file, addr, len, pgoff, flags);  --擷取沒有映射的位址,這個是查詢mm中空閑的記憶體位址,這個在下面了解。

    if (addr & ~PAGE_MASK)

        return addr;

    vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) | mm->def_flags |

               VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;      --設定vm_flags,根據傳入的port和flags以及mm本身自有的旗标來設定。

    if (flags & MAP_LOCKED) {       

        if (!can_do_mlock())         

            return -EPERM;

        vm_flags |= VM_LOCKED;

    }

    if (vm_flags & VM_LOCKED) {

        unsigned long locked, lock_limit;

        locked = len >> PAGE_SHIFT;

        locked += mm->locked_vm;

        lock_limit = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur;

        lock_limit >>= PAGE_SHIFT;

        if (locked > lock_limit && !capable(CAP_IPC_LOCK))

            return -EAGAIN;

  --關于鎖定的記憶體區在以後學習中再看,這裡就不細看。

    inode = file ? file->f_path.dentry->d_inode : NULL;  --判斷是否匿名映射,如果不是則指派inode

    if (file) {

        switch (flags & MAP_TYPE) {   --MAP_TYPE = 0x0F type的掩碼

        case MAP_SHARED:

            if ((prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE))   --file應該被打開并允許寫入。

                return -EACCES;

            if (IS_APPEND(inode) && (file->f_mode & FMODE_WRITE))  --不能寫入一個隻允許寫追加的檔案

            if (locks_verify_locked(inode))      --確定檔案沒有被強制鎖定。

                return -EAGAIN;

            vm_flags |= VM_SHARED | VM_MAYSHARE;  --嘗試允許其他程序共享。

            if (!(file->f_mode & FMODE_WRITE))    --如果file不允許寫就算了,共享也沒有用啊,因為file就一直固定死了,共享也沒有意義。

                vm_flags &= ~(VM_MAYWRITE | VM_SHARED);

        case MAP_PRIVATE:

            if (!(file->f_mode & FMODE_READ))

            if (file->f_path.mnt->mnt_flags & MNT_NOEXEC) {

                if (vm_flags & VM_EXEC)

                    return -EPERM;

                vm_flags &= ~VM_MAYEXEC;

            }

            if (is_file_hugepages(file))

                accountable = 0;

            if (!file->f_op || !file->f_op->mmap)

                return -ENODEV;

            break;

        default:

            return -EINVAL;

        }

    } else {

        switch (flags & MAP_TYPE) {

            pgoff = 0;

            vm_flags |= VM_SHARED | VM_MAYSHARE;

            pgoff = addr >> PAGE_SHIFT;

  --上面就是對一些旗标進行檢測,防止出現旗标沖突,比如我欲映射的檔案不允許寫,而我映射的旗标卻設定是可寫并可以共享的,這個就沖突了。

    error = security_file_mmap(file, reqprot, prot, flags, addr, 0);   --這個函數就忽略了。

    return mmap_region(file, addr, len, flags, vm_flags, pgoff,accountable);  --最後一個參數為是否為大頁,如果是的就為0.其餘的參數都好了解。

3.3 get_unmapped_area

這個是擷取沒有被映射的記憶體區

unsigned long get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,unsigned long pgoff, unsigned long flags)

    unsigned long (*get_area)(struct file *, unsigned long,unsigned long, unsigned long, unsigned long);

    get_area = current->mm->get_unmapped_area;

    if (file && file->f_op && file->f_op->get_unmapped_area)

        get_area = file->f_op->get_unmapped_area;

    addr = get_area(file, addr, len, pgoff, flags);

    if (IS_ERR_VALUE(addr))

    if (addr > TASK_SIZE - len)

    return arch_rebalance_pgtables(addr, len);

對于get_area函數我們以arch_get_unmapped_area為例來看如何查找一個空閑的mmap area

unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr,unsigned long len, unsigned long pgoff, unsigned long flags)

    struct mm_struct *mm = current->mm;

    struct vm_area_struct *vma;

    unsigned long start_addr;

    if (len > TASK_SIZE)

    if (flags & MAP_FIXED)    --還記否這個MAP_FIXED是什麼含義不?

    if (addr) {

        addr = PAGE_ALIGN(addr);

        vma = find_vma(mm, addr); --vma為NULL即addr的位址不在任一個VMA(vma->vm_start~vma->vm_end) addr的位址沒有被映射,

                                    而且空洞足夠我們這次的映射,那麼傳回addr以準備這次的映射

        if (TASK_SIZE - len >= addr &&(!vma || addr + len <= vma->vm_start))

            return addr;

    if (len > mm->cached_hole_size) { --如果所需的長度大于目前vma之間的空洞長度

            start_addr = addr = mm->free_area_cache;

            start_addr = addr = TASK_UNMAPPED_BASE;  --需要的長度小于目前空洞,為了不至于時間浪費,那麼從0開始搜尋,

                                                       這裡的搜尋基位址TASK_UNMAPPED_BASE很重要,使用者mmap的位址的基位址必須在TASK_UNMAPPED_BASE之上,

                                                       但是一定這樣嚴格 嗎?看上面的if (addr)判斷,如果使用者給了一個位址在TASK_UNMAPPED_BASE之下,

                                                       映射實際上還是會發生的。

            mm->cached_hole_size = 0;

full_search:

    for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {

        if (TASK_SIZE - len < addr) {

            if (start_addr != TASK_UNMAPPED_BASE) {

                addr = TASK_UNMAPPED_BASE;

              start_addr = addr;

                mm->cached_hole_size = 0;

                goto full_search;

            return -ENOMEM;

        if (!vma || addr + len <= vma->vm_start) {        --如果第一次find_vma傳回值即為NULL ,vma沒有被映射并且空洞足夠映射

                                                        !vma的條件隻有可能在循環的第一次滿足,在其後不可能滿足,在其後的判斷條件即為

                                                         vma->vma_end~vma->vma_next->vma_start之間的空洞大小大于所需要映射的長度即可,

                                                         下面判斷條件中的addr為vma->vma_end,而vma->vm_start為 vma->vma_next->vma_start

            mm->free_area_cache = addr + len;

        if (addr + mm->cached_hole_size < vma->vm_start)  --在循環的第一次如果vma不為NULL,不會滿足下面的條件,在以後循環中mm->cached_hole_size 

                                                            則為該次vma->vm_start 與上一次的vma->vm_end之間的內插補點

                mm->cached_hole_size = vma->vm_start - addr;

        addr = vma->vm_end;

還記否以前看的紅黑樹,這裡就現實的用了紅黑樹的算法。關于這個我們就不看了。

struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr)

    struct vm_area_struct *vma = NULL;

    if (mm) {

        vma = mm->mmap_cache;

        if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {

            struct rb_node * rb_node;

            rb_node = mm->mm_rb.rb_node;

            vma = NULL;

            while (rb_node) {

                struct vm_area_struct * vma_tmp;

                vma_tmp = rb_entry(rb_node,struct vm_area_struct, vm_rb);

                if (vma_tmp->vm_end > addr) {

                    vma = vma_tmp;

                    if (vma_tmp->vm_start <= addr)

                        break;

                    rb_node = rb_node->rb_left;

                } else

                    rb_node = rb_node->rb_right;

            if (vma)

                mm->mmap_cache = vma;

    return vma;

3.4 mmap_region

unsigned long mmap_region(struct file *file, unsigned long addr,unsigned long len, unsigned long flags,

                                unsigned int vm_flags, unsigned long pgoff,int accountable)

    struct vm_area_struct *vma, *prev;

    struct vm_area_struct *merged_vma;

    int correct_wcount = 0;

    struct rb_node **rb_link, *rb_parent;

    unsigned long charged = 0;

    struct inode *inode =  file ? file->f_path.dentry->d_inode : NULL;

    /* Clear old maps */

    error = -ENOMEM;

munmap_back:

    vma = find_vma_prepare(mm, addr, &prev, &rb_link, &rb_parent); --函數find_vma_prepare()與find_vma()基本相同,它掃描目前程序位址空間的vm_area_struct

                                                                     結構所形成的紅黑樹,試圖找到結束位址高于addr的第一個區間;如果找到了一個虛拟區,

                                                                     說明addr所在的虛拟區已經在使用,也就是已經有映射存在,是以要調用do_munmap()

                                                                     把這個老的虛拟區從程序位址空間中撤銷,如果撤銷不成功,就傳回一個負數;

                                                                     如果撤銷成功,就繼續查找,直到在紅黑樹中找不到addr所在的虛拟區

    if (vma && vma->vm_start < addr + len) {

        if (do_munmap(mm, addr, len))

        goto munmap_back;

    if (!may_expand_vm(mm, len >> PAGE_SHIFT))                   -- 頁數和超過限定值傳回 0 ,不超過傳回1

    if (flags & MAP_NORESERVE)                             -- 如果flags參數中沒有設定MAP_NORESERVE标志,新的虛拟區含有私有的可寫頁,空閑頁面數小于要映射的虛拟區

                                                              的大小;則函數終止并傳回一個負數;其中函數security_vm_enough_memory()用來檢查一個

                                                              程序的位址空間中是否有足夠的記憶體來進行一個新的映射

        vm_flags |= VM_NORESERVE;

    if (accountable && (!(flags & MAP_NORESERVE) ||

                sysctl_overcommit_memory == OVERCOMMIT_NEVER)) {

        if (vm_flags & VM_SHARED) {

            /* Check memory availability in shmem_file_setup? */

            vm_flags |= VM_ACCOUNT;

        } else if (vm_flags & VM_WRITE) {

            charged = len >> PAGE_SHIFT;

            if (security_vm_enough_memory(charged))

                return -ENOMEM;

    if (!file && !(vm_flags & VM_SHARED)) { --如果是匿名映射(file為空),并且這個虛拟區是非共享的,則可以把這個虛拟區和與它緊挨的前一個虛拟區進行合并;

                                              虛拟區的合并是由vma_merge()函數實作的。如果合并成功,則轉out處,請看後面out處的代碼。

        vma = vma_merge(mm, prev, addr, addr + len, vm_flags,

                    NULL, NULL, pgoff, NULL);

        if (vma)

            goto out;

    vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);

    if (!vma) {

        error = -ENOMEM;

        goto unacct_error;

    vma->vm_mm = mm;

    vma->vm_start = addr;

    vma->vm_end = addr + len;

    vma->vm_flags = vm_flags;

    vma->vm_page_prot = vm_get_page_prot(vm_flags);

    vma->vm_pgoff = pgoff;

        error = -EINVAL;

        if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))

            goto free_vma;

        if (vm_flags & VM_DENYWRITE) {

            error = deny_write_access(file);

            if (error)

                goto free_vma;

            correct_wcount = 1;

        vma->vm_file = file;

        get_file(file);

        error = file->f_op->mmap(file, vma);    -- (⊙o⊙)哦 ,終于可以調用裝置檔案中真正的mmap

        if (error)

            goto unmap_and_free_vma;

        if (vm_flags & VM_EXECUTABLE)

            added_exe_file_vma(mm);

    } else if (vm_flags & VM_SHARED) {

        error = shmem_zero_setup(vma);// it will call shmem_file_setup(), the same way as called in ashmem.c

如果建立的是從檔案到虛存區間的映射,則:

1.當參數flags中的VM_GROWSDOWN或VM_GROWSUP标志位為1時,說明這個區間可以向低位址或高位址擴充,但從檔案映射的區間不能進行擴充,是以轉到free_vma,釋放給vm_area_struct配置設定的Slab,并傳回一個錯誤;

2.當flags中的VM_DENYWRITE标志位為1時,就表示不允許通過正常的檔案操作通路該檔案,是以要調用deny_write_access()排斥正常的檔案操作(參見第八章)。

3.get_file()函數的主要作用是遞增file結構中的共享計數;

4.每個檔案系統都有個fiel_operation資料結構,其中的函數指針mmap提供了用來建立從該類檔案到虛存區間進行映射的操作,這是最具有實質意義的函數;對于大部分檔案系統,這個函數為generic_file_mmap( )函數實作的,該函數執行以下操作:

        (1)初始化vm_area_struct結構中的vm_ops域。如果VM_SHARED标志為1,就把該域設定成file_shared_mmap, 否則就把該域設定成file_private_mmap。從某種意義上說,這個步驟所做的事情類似于打開一個檔案并初始化檔案對象的方法。

        (2)從索引節點的i_mode域(參見第八章)檢查要映射的檔案是否是一個正常檔案。如果是其他類型的檔案(例如目錄或套接字),就傳回一個錯誤代碼。

        (3)從索引節點的i_op域中檢查是否定義了readpage( )的索引節點操作。如果沒有定義,就傳回一個錯誤代碼。

        (4)調用update_atime( )函數把目前時間存放在該檔案索引節點的i_atime域中,并将這個索引節點标記成髒。

5.如果flags參數中的MAP_SHARED标志位為1,則調用shmem_zero_setup()進行共享記憶體的映射。

    if ((vm_flags & (VM_SHARED|VM_ACCOUNT)) == (VM_SHARED|VM_ACCOUNT))

        vma->vm_flags &= ~VM_ACCOUNT;

    addr = vma->vm_start;

    pgoff = vma->vm_pgoff;

    vm_flags = vma->vm_flags;

    if (vma_wants_writenotify(vma))

        vma->vm_page_prot = vm_get_page_prot(vm_flags & ~VM_SHARED);

    merged_vma = NULL;

    if (file)

        merged_vma = vma_merge(mm, prev, addr, vma->vm_end,

            vma->vm_flags, NULL, file, pgoff, vma_policy(vma));

    if (merged_vma) {

        mpol_put(vma_policy(vma));

        kmem_cache_free(vm_area_cachep, vma);

        fput(file);

            removed_exe_file_vma(mm);

        vma = merged_vma;

        vma_link(mm, vma, prev, rb_link, rb_parent);

        file = vma->vm_file;

此時,把建立的虛拟區插入到程序的位址空間,這是由函數vma_link()完成的,該函數具有三方面的功能:

(1)把vma 插入到虛拟區連結清單中

(2)把vma插入到虛拟區形成的紅黑樹中

(3)把vam插入到索引節點(inode)共享連結清單中

函數atomic_inc(x)給*x加1,這是一個原子操作。在核心代碼中,有很多地方調用了以atomic為字首的函數。原子操作,在操作過程中不會被中斷。

    if (correct_wcount)

        atomic_inc(&inode->i_writecount);

    mm->total_vm += len >> PAGE_SHIFT;

    vm_stat_account(mm, vm_flags, file, len >> PAGE_SHIFT);

        long nr_pages = mlock_vma_pages_range(vma, addr, addr + len);

        if (nr_pages < 0)

            return nr_pages;    /* vma gone! */

        mm->locked_vm += (len >> PAGE_SHIFT) - nr_pages;

    } else if ((flags & MAP_POPULATE) && !(flags & MAP_NONBLOCK))

        make_pages_present(addr, addr + len);

    return addr;

unmap_and_free_vma:

    vma->vm_file = NULL;

    fput(file);

    unmap_region(mm, vma, prev, vma->vm_start, vma->vm_end);

    charged = 0;

free_vma:

    kmem_cache_free(vm_area_cachep, vma);

unacct_error:

    if (charged)

        vm_unacct_memory(charged);

    return error;

ok!到此mmap的核心核心就可以了,關于具體的mmap的實作,以後再看。

四.總結

mmap的實質是什麼,其實就是從每一個程序中的使用者空間配置設定一段空間用于映射。 這裡面的機關重重,需要好好了解,不過謹記一點,程序的vma_struct是采用了紅黑樹來管理的。對于每一段的記憶體區都會有一個vma_struct 來描述,比如資料區,code區等等,以及mmap所需要的一段記憶體區。

五.其它

1、特點:

① 程序相關的

② 與XSI共享記憶體一樣,需要與同步原語一起使用

③ 隻能是有共同祖先的程序才能使用

2、使用

系統調用mmap()用于共享記憶體的兩種方式:

(1)使用普通檔案提供的記憶體映射:

    适用于任何程序之間。此時,需要打開或建立一個檔案,然後再調用mmap()

典型調用代碼如下:

fd=open(name, flag, mode); if(fd<0) ...

ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);

     通過mmap()實作共享記憶體的通信方式有許多特點和要注意的地方,可以參看UNIX網絡程式設計第二卷。【3】

(2)使用特殊檔案提供匿名記憶體映射:

    适用于具有親緣關系的程序之間。由于父子程序特殊的親緣關系,在父程序中先調用mmap(),然後調用fork()。那麼在調用fork()之後,子程序 繼承父程序匿名映射後的位址空間,同樣也繼承mmap()傳回的位址,這樣,父子程序就可以通過映射區域進行通信了。一般來說,子程序單獨維護從父程序繼 承下來的一些變量。而mmap()傳回的位址,卻由父子程序共同維護。對于具有親緣關系的程序實作共享記憶體最好的方式應該是采用匿名記憶體映射的方式。此時,不必指定具體的檔案,隻要設定相應的标志即可。

3、說明

(1)void *mmap(void *addr, size_t len, int prot, int flag, int fd, off_t offset );

把檔案或裝置映射或解除映射到記憶體中

0)flag:必須有MAP_SHARED 标志

MAP_SHARED對映射區域的寫入資料會複制回檔案内,而且允許其他映射該檔案的程序共享。

MAP_PRIVATE 對映射區域的寫入操作會産生一個映射檔案的複制,即私人的“寫入時複制”(copy on write)對此區域作的任何修改都不會寫回原來的檔案内容。

MAP_ANONYMOUS建立匿名共享。此時會忽略參數fd(fd可以指定為-1),不涉及檔案,而且映射區域無法和其他程序共享(隻能用于具有親緣關系的程序間通信)。

    映射/dev/zero可為調用程式提供零填充的虛拟記憶體塊。

1)start:指向欲映射的記憶體起始位址,通常設為 NULL,代表讓系統自動標明位址,映射成功後傳回該位址。

2)length:代表将檔案中多大的部分映射到記憶體。

3)offset 必須是頁面大小的整數倍。頁面大小由 getpagesize(2)得到。

4)被映射的檔案大小應是頁面大小的整數倍。如一個檔案大小不是頁面大小的整數倍,映射時多出來的區域将被賦為0,對這些區域的寫不會被寫回到檔案中。

5)munmap()系統調用将删除指定位址範圍内的映射區域。随後對這個範圍内區域的引用将産生非法的記憶體引用。當這個程序終止後,這個區域也會被删除。另一方面,關閉檔案描述符并不會删除映射區域。

6)fd:要映射到記憶體中的檔案描述符。如果使用匿名記憶體映射時,即flags中設定了MAP_ANONYMOUS,fd設為-1。有些系統不支援匿名記憶體映射,則可以使用fopen打開/dev/zero檔案,然後對該檔案進行映射,可以同樣達到匿名記憶體映射的效果。

7)若映射成功則傳回映射區的記憶體起始位址,否則傳回MAP_FAILED(-1)。

(2) munmap

int munmap( void * addr, size_t len )

    在程序位址空間中解除一個映射關系,當映射關系解除後,對原來映射位址的通路将導緻段錯誤發生。

void * addr :調用mmap()時傳回的位址

size_t len :映射區的大小

(3)int msync ( void * addr , size_t len, int flags)

    一般說來,程序在映射空間的對共享内容的改變并不直接寫回到磁盤檔案中,往往在調用munmap()後才執行該操作。可以調用msync()實作磁盤上檔案與共享記憶體區的内容一緻。

int flags :MS_ASYN: 異步寫,MS_SYN : 同步寫,MS_INVALIDAT : 無效的cache 資料。

5、其他

1)程序調用mmap()時,隻是在程序空間内新增了一塊相應大小的緩沖區,并設定了相應的通路辨別,但并沒有建立程序空間到實體頁面的映射。是以,第一次通路該空間時,會引發一個缺頁異常。

2)一個共享記憶體區域可以看作是特殊檔案系統shm中的一個檔案,shm的安裝點在交換區上。

3)mmap()系統調用使得程序之間通過映射同一個普通檔案實作共享記憶體。普通檔案被映射到程序位址空間後,程序可以向通路普通記憶體一樣對檔案進行通路,不必再調用read(),write()等操作。

4)最終被映射檔案的内容的長度不會超過檔案本身的初始大小,即映射不能改變檔案的大小。檔案被映射部分而不是整個檔案決定了程序能夠通路的空間大小,另外,如果指定檔案的偏移部分,一定要注意為頁面大小的整數倍。

linux mmap 詳解【轉】

【新浪微網誌】 張昺華--sky

【twitter】 @sky2030_

【facebook】 張昺華 zhangbinghua

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.

繼續閱讀