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))  --不能寫入一個只允許寫追加的文件
                return -EACCES;
            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))
                return -EACCES;
            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) {
        case MAP_SHARED:
            pgoff = 0;
            vm_flags |= VM_SHARED | VM_MAYSHARE;
            break;
        case MAP_PRIVATE:
            pgoff = addr >> PAGE_SHIFT;
            break;
        default:
            return -EINVAL;
        }
    }
  --上面就是對一些旗標進行檢測,防止出現旗標衝突,比如我欲映射的文件不允許寫,而我映射的旗標卻設定是可寫並可以共享的,這個就衝突了。
    error = security_file_mmap(file, reqprot, prot, flags, addr, 0);   --這個函數就忽略了。
    if (error)
        return error;

    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))
        return addr;

    if (addr > TASK_SIZE - len)
        return -ENOMEM;
    if (addr & ~PAGE_MASK)
        return -EINVAL;

    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)
        return -ENOMEM;

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

    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;
    } else {
            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;
            return addr;
        }
        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 mm_struct *mm = current->mm;
    struct vm_area_struct *vma, *prev;
    struct vm_area_struct *merged_vma;
    int correct_wcount = 0;
    int error;
    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))
            return -ENOMEM;
        goto munmap_back;
    }
    if (!may_expand_vm(mm, len >> PAGE_SHIFT))                   -- 頁數和超過限定值返回 0 ,不超過返回1
        return -ENOMEM;

    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;
            vm_flags |= VM_ACCOUNT;
        }
    }
    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;

    if (file) {
        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
        if (error)
            goto free_vma;
    }
如果建立的是從文件到虛存區間的映射,則:
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);
        if (vm_flags & VM_EXECUTABLE)
            removed_exe_file_vma(mm);
        vma = merged_vma;
    } else {
        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);
out:
    mm->total_vm += len >> PAGE_SHIFT;
    vm_stat_account(mm, vm_flags, file, len >> PAGE_SHIFT);
    if (vm_flags & VM_LOCKED) {
        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:
    if (correct_wcount)
        atomic_inc(&inode->i_writecount);
    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()實現磁盤上文件與共享內存區的內容一致。
void * addr :調用mmap()時返回的地址
size_t len :映射區的大小
int flags :MS_ASYN: 異步寫,MS_SYN : 同步寫,MS_INVALIDAT : 無效的cache 數據。

5、其他

1)進程調用mmap()時,只是在進程空間內新增了一塊相應大小的緩衝區,並設置了相應的訪問標識,但並沒有建立進程空間到物理頁面的映射。因此,第一次訪問該空間時,會引發一個缺頁異常。

2)一個共享內存區域可以看作是特殊文件系統shm中的一個文件,shm的安裝點在交換區上。

3)mmap()系統調用使得進程之間通過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間後,進程可以向訪問普通內存一樣對文件進行訪問,不必再調用read()write()等操作。

4)最終被映射文件的內容的長度不會超過文件本身的初始大小,即映射不能改變文件的大小。文件被映射部分而不是整個文件決定了進程能夠訪問的空間大小,另外,如果指定文件的偏移部分,一定要注意爲頁面大小的整數倍。

 


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