1. 程式人生 > >程序地址空間 get_unmmapped_area()

程序地址空間 get_unmmapped_area()

程序地址空間 get_unmapped_area()

在向資料結構插入新的記憶體區域之前,核心必須確認虛擬地址空間中有足夠的空閒空間,可用於給定長度的區域。該工作由get_unmmaped_area()完成。
在分析get_unmmaped_area()之前,先簡單介紹一下程序地址空間的佈局。
程序地址空間 經典佈局:
這裡寫圖片描述
經典佈局的缺點:在x86_32,虛擬地址空間從0到0xc0000000,每個使用者程序有3GB可用。TASK_UNMAPPED_BASE一般起始於0x4000000(即1GB)。這意味著堆只有1GB的空間可供使用,繼續增長則進入到mmap區域。這時mmap區域是自底向上擴充套件的。
針對這個問題,引入了新的虛擬地址空間:
(./新的佈局.png)![這裡寫圖片描述


與經典佈局不同的是:使用固定值限制棧的最大長度。由於棧是有界的,因此安置記憶體對映的區域可以在棧末端的下方立即開始。這時mmap區是自頂向下擴充套件的。由於堆仍然位於虛擬地址空間中較低的區域並向上增長,因此mmap區域和堆可以相對擴充套件,直至耗盡虛擬地址空間中剩餘的區域。
選擇佈局的工作由arch_pick_mmap_layout完成。其中arch_get_unmapped_area()完成從低地址向高地址建立新的對映,而arch_get_unmapped_area_topdown()完成從高地址向低地址建立新的對映。

include/linux/sched.h

...
#ifdef CONFIG_MMU
extern void arch_pick_mmap_layout(struct mm_struct *mm);
...
#else
static inline void arch_pick_mmap_layout(struct mm_struct *mm) {}
#endif


mm/util.c

...
/* HAVE_ARCH_PICK_MMAP_LAYOUT : 體系結構是否想要在不同mmap區域佈局之間做出選擇 */
#if defined(CONFIG_MMU) && !defined(HAVE_ARCH_PICK_MMAP_LAYOUT)
/* 經典佈局 */
void arch_pick_mmap_layout(struct mm_struct *mm)
{
    mm->mmap_base = TASK_UNMAPPED_BASE;
    mm->get_unmapped_area = arch_get_unmapped_area;
}
#endif

arch/x86/mm/mmap.c

...
/*
 * This function, called very early during the creation of a new
 * process VM image, sets up which VM layout function to use:
 */
void arch_pick_mmap_layout(struct mm_struct *mm)
{
    unsigned long random_factor = 0UL;

    /* 
    * 設定了PF_RANDOMEIZE, 則核心不會為棧和記憶體對映的起點選擇固定
    * 位置,而是在每次新程序啟動時,隨機改變這些值的設定
    */
    if (current->flags & PF_RANDOMIZE)
        random_factor = arch_mmap_rnd();

    mm->mmap_legacy_base = mmap_legacy_base(random_factor);

    if (mmap_is_legacy()) {
        mm->mmap_base = mm->mmap_legacy_base;
        mm->get_unmapped_area = arch_get_unmapped_area;
    } else {
        mm->mmap_base = mmap_base(random_factor);
        mm->get_unmapped_area = arch_get_unmapped_area_topdown;
    }
}

現在我們看看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);

    unsigned long error = arch_mmap_check(addr, len, flags);
    if (error)
        return error;

    /* Careful about overflows.. */
    if (len > TASK_SIZE)
        return -ENOMEM;

    get_area = current->mm->get_unmapped_area;
    /* 根據線性地址區間是否應該用於檔案記憶體對映或匿名記憶體對映 */
    if (file && file->f_op->get_unmapped_area)
        get_area = file->f_op->get_unmapped_area;
    /* 
     * 當不是用於檔案記憶體對映或是匿名記憶體對映,
     * 呼叫current->mm->get_unmapped_area. 
     * 即呼叫arch_get_unmapped_area或arch_get_unmapped_area_topdown
     */
    addr = get_area(file, addr, len, pgoff, flags);
    if (IS_ERR_VALUE(addr))
        return addr;

    if (addr > TASK_SIZE - len)
        return -ENOMEM;
    if (offset_in_page(addr))
        return -EINVAL;

    addr = arch_rebalance_pgtables(addr, len);
    error = security_mmap_addr(addr);
    return error ? error : addr;
}

EXPORT_SYMBOL(get_unmapped_area);

以arch_get_unmapped_area為例。當addr非空,表示指定了一個特定的優先選用地址,核心會檢查該區域是否與現存區域重疊,由find_vma()完成查詢功能。當addr為空或是指定的優先地址不滿足分配條件時,核心必須遍歷程序中可用的區域,設法找到一個大小適當的空閒區域,有vm_unmapped_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;
    struct vm_unmapped_area_info info;

    if (len > TASK_SIZE - mmap_min_addr)
        return -ENOMEM;

    /* MAP_FIXED : 表示對映將在固定地址建立 */
    if (flags & MAP_FIXED)
        return addr;

    if (addr) {
        addr = PAGE_ALIGN(addr);
        /* 
         * find_vma() 尋找第一個滿足 addr < vm_area_struct->vm_end 的vma區
         * vma = NULL 在vma紅黑樹的右子樹,addr 是所存在的所有線性區線性地址最大
         * vma != NULL 一定是tmp == NULL (tmp在find_vma指向當前結點)跳出迴圈的
         */ 
        vma = find_vma(mm, addr);

    /*
     * 以下分別判斷:
     * 1: 請求分配的長度是否小於程序虛擬地址空間大小
     * 2: 新分配的虛擬地址空間的起始地址是否在mmap_min_addr(允許分配虛擬地址空間的最低地址)之上
     * 3: vma是否空
     * 4: vma非空,新分配的虛擬地址空間,是否與相鄰的vma重合
     */
    if (TASK_SIZE - len >= addr && addr >= mmap_min_addr &&
        (!vma || addr + len <= vma->vm_start))
        return addr;
}

    info.flags = 0;
    info.length = len;
    info.low_limit = mm->mmap_base;
    info.high_limit = TASK_SIZE;
    info.align_mask = 0;
    return vm_unmapped_area(&info);
}

/*
* Search for an unmapped address range.
*
* We are looking for a range that:
* - does not intersect with any VMA;
* - is contained within the [low_limit, high_limit) interval;
* - is at least the desired size.
* - satisfies (begin_addr & align_mask) == (align_offset & align_mask)
*/
static inline unsigned long vm_unmapped_area(struct vm_unmapped_area_info *info)
{
    /* arch_get_unmapped_area是低地址到高地址建立對映 所以這時預設呼叫unmapped_area */
    if (info->flags & VM_UNMAPPED_AREA_TOPDOWN)
        return unmapped_area_topdown(info);
    else
        return unmapped_area(info);
}

在分析unmapped_area()之前,我認為有必要搞清楚vm_area_struct結構體中rb_subtree_gap的含義。在http://patchwork.ozlabs.org/patch/197340/ 這樣解釋:
Define vma->rb_subtree_gap as the largest gap between any vma in the subtree rooted at that vma, and their predecessor. Or, for a recursive definition, vma->rb_subtree_gap is the max of:
- vma->vm_start - vma->vm_prev->vm_end
- rb_subtree_gap fields of the vmas pointed by vma->rb.rb_left and
vma->rb.rb_right

rb_subtree_gap是當前結點與其前驅結點之間空隙 和 當前結點其左右子樹中的結點間的最大空隙的最大值。
unmapped_area():先檢查程序虛擬地址空間中可用於對映空間的邊界,不滿足要求返回錯誤代號到上層應用程式。當滿足時,執行以下操作,為了找到最小的空閒的虛擬地址空間滿足這次分配請求,便於兩個相鄰的vma區合併。
步驟如下:
1. 從vma紅黑樹的根開始遍歷
2. 若當前結點有左子樹則遍歷其左子樹,否則指向其右孩子。
3. 當某結點rb_subtree_gap可能是最後一個滿足分配請求的空隙時,遍歷結束。
4. 檢測這個結點,判斷這個結點與其前驅結點之間的空隙是否滿足分配請求。滿足則跳出迴圈。
5. 不滿足分配請求時,指向其右孩子,判斷其右孩子的rb_subtree_gap是否滿足當前請求。
6. 滿足則返回到2。不滿足,回退其父結點,返回到4

unsigned long unmapped_area(struct vm_unmapped_area_info *info)
{
    /*
    * We implement the search by looking for an rbtree node that
    * immediately follows a suitable gap. That is,
    * - gap_start = vma->vm_prev->vm_end <= info->high_limit - length;
    * - gap_end   = vma->vm_start        >= info->low_limit  + length;
    * - gap_end - gap_start >= length
    */

    struct mm_struct *mm = current->mm;
    struct vm_area_struct *vma;
    unsigned long length, low_limit, high_limit, gap_start, gap_end;

    /* Adjust search length to account for worst case alignment overhead */
    length = info->length + info->align_mask;
    if (length < info->length)
        return -ENOMEM;

    /* Adjust search limits by the desired length */
    if (info->high_limit < length)
        return -ENOMEM;
    high_limit = info->high_limit - length;

    if (info->low_limit > high_limit)
        return -ENOMEM;
    low_limit = info->low_limit + length;

    /* Check if rbtree root looks promising */
    if (RB_EMPTY_ROOT(&mm->mm_rb))
        goto check_highest;
    vma = rb_entry(mm->mm_rb.rb_node, struct vm_area_struct, vm_rb);
    if (vma->rb_subtree_gap < length)
        goto check_highest;

    while (true) {
    /* Visit left subtree if it looks promising */
    /* 先從低地址開始查詢 */
        gap_end = vma->vm_start;
        if (gap_end >= low_limit && vma->vm_rb.rb_left) {
            struct vm_area_struct *left =
                rb_entry(vma->vm_rb.rb_left,struct vm_area_struct, vm_rb);
        /*
         * 查詢到最後一個空隙可能滿足這次分配,
         * 說明 addr 從低地址向高地址 分配 。
         * 便於相鄰的兩個vma合併。
         */
            if (left->rb_subtree_gap >= length) {
                vma = left;
                continue;
            }
    }

    /* 當前結點的rb_subtree_gap 已經是最後一個可能滿足這次分配 */
    gap_start = vma->vm_prev ? vma->vm_prev->vm_end : 0;
check_current:
    /* Check if current node has a suitable gap */
    if (gap_start > high_limit)
        return -ENOMEM;
    if (gap_end >= low_limit && gap_end - gap_start >= length)
        goto found;

    /* Visit right subtree if it looks promising */
    /*
    * 當前結點與其前驅的空隙也不能滿足這次請求, 
    * 檢測當前結點的右孩子的 rb_subtree_gap
    */ 
    if (vma->vm_rb.rb_right) {
        struct vm_area_struct *right =
            rb_entry(vma->vm_rb.rb_right,
                 struct vm_area_struct, vm_rb);
        /*
         * 以右孩子為根的樹中 rb_subtree_gap 來滿足這次的請求
         * case 1:若滿足,又從當前結點的右結點的左子樹開始尋找
         * case 2:若不滿足,說明當前結點 左右子樹沒有滿足這次請求的空隙,
         *        所以回退到上個結點
         */
        if (right->rb_subtree_gap >= length) {//case 1
            vma = right;
            continue;
        }
    }

    /* Go back up the rbtree to find next candidate node */
    while (true) {//case 2
        struct rb_node *prev = &vma->vm_rb;
        if (!rb_parent(prev))
            goto check_highest;
        vma = rb_entry(rb_parent(prev),
                   struct vm_area_struct, vm_rb);
        // 當前結點的前驅只可能是其左孩子。因為rb_subtree_gap是當前結點與其前驅的空隙
        if (prev == vma->vm_rb.rb_left) {
            gap_start = vma->vm_prev->vm_end;
            gap_end = vma->vm_start;
            goto check_current;
            }
        }
    }

    check_highest:
        /* Check highest gap, which does not precede any rbtree node */
        gap_start = mm->highest_vm_end;
        gap_end = ULONG_MAX;  /* Only for VM_BUG_ON below */
        if (gap_start > high_limit)
        return -ENOMEM;

found:
    /* We found a suitable gap. Clip it with the original low_limit. */
    if (gap_start < info->low_limit)
        gap_start = info->low_limit;

    /* Adjust gap address to the desired alignment */
    gap_start += (info->align_offset - gap_start) & info->align_mask;

    VM_BUG_ON(gap_start + info->length > info->high_limit);
    VM_BUG_ON(gap_start + info->length > gap_end);
    return gap_start;
}

參考原始碼:linux-4.4

相關推薦

程序地址空間 get_unmmapped_area()

程序地址空間 get_unmapped_area() 在向資料結構插入新的記憶體區域之前,核心必須確認虛擬地址空間中有足夠的空閒空間,可用於給定長度的區域。該工作由get_unmmaped_area()完成。 在分析get_unmmaped_area()之前

Linux程序地址空間 程序記憶體佈局

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Linux程序地址空間 && 程序記憶體佈局

轉載自:https://blog.csdn.net/yusiguyuan/article/details/45155035   一 程序空間分佈概述       對於一個程序,其空間分佈如下圖所示:      

程序地址空間佈局

建立一個程序時,作業系統會為該程序分配一個 4GB 大小的程序地址空間,本文具體講的是程序地址空間的各個部分: 棧: 存放程式臨時建立的區域性變數,也就是程式碼塊之內或者函式之內的變數,但不包括sta

Linux系統程式設計——淺談程序地址空間與虛擬儲存空間

早期的記憶體分配機制 在早期的計算機中,要執行一個程式,會把這些程式全都裝入記憶體,程式都是直接執行在記憶體上的,也就是說程式中訪問的記憶體地址都是實際的實體記憶體地址。當計算機同時執行多個程式時,必須保證這些程式用到的記憶體總量要小於計算機實際實體記憶體的大小。 那當程式同時執行

深入理解 Linux 核心---程序地址空間

講述: 程序是怎樣看待動態記憶體的。 程序空間的基本組成。 缺頁異常處理程式在推遲給程序分配頁框中所起的作用。 核心怎樣建立和刪除程序的整個地址空間。 與程序的地址空間管理有關的 API 和系統呼叫。 程序的地址空間 程序的地址空間由允許程序使用的全部線性地址組成。 每個程序看到

15---程序地址空間

什麼是程序地址空間? 系統中每個使用者空間程序所看到的記憶體 程序地址空間有什麼特點? 核心允許程序使用虛擬記憶體 系統中所有程序之間以虛擬方式共享記憶體 對一個程序而言,它好像可以訪問系統中所有的實體記憶體,它擁有的地址空間可以遠大於系統實體記憶體 每個程序擁有

《Linux核心設計與實現》讀書筆記(十五)- 程序地址空間(kernel 2.6.32.60)

程序地址空間也就是每個程序所使用的記憶體,核心對程序地址空間的管理,也就是對使用者態程式的記憶體管理。 主要內容: 地址空間(mm_struct) 虛擬記憶體區域(VMA) 地址空間和頁表 1. 地址空間(mm_struct) 地址空間就是每個程序所能訪問的記憶體地址範圍。 這個地址

淺談程序地址空間與虛擬儲存空間

早期的記憶體分配機制 在早期的計算機中,要執行一個程式,會把這些程式全都裝入記憶體,程式都是直接執行在記憶體上的,也就是說程式中訪問的記憶體地址都是實際的實體記憶體地址。當計算機同時執行多個程式時,必

Linux程序地址空間 && 程序記憶體佈局

一 程序空間分佈概述     對於一個程序,其空間分佈如下圖所示:                                       程式段(Text):程式程式碼在記憶體中的對映,存放函式體的二進位制程式碼。 初始化過的資料(Data):在程式執

作業系統原理:程序地址空間

Linux程序虛擬儲存 先回憶一下ELF檔案的組織結構,可以看這篇文章:Linux 連結與ELF檔案。程式執行後進程地址空間佈局則和作業系統密切相關。在將應用程式載入到記憶體空間執行時,作業系統負責程式碼段與資料段的載入,並在記憶體中為這些段分配空間。Linu

程序地址空間與虛擬儲存空間區別

在進入正題前先來談談作業系統記憶體管理機制的發展歷程,瞭解這些有利於我們更好的理解目前作業系統的記憶體管理機制。 一 早期的記憶體分配機制 在 早期的計算機中,要執行一個程式,會把這些程式全都裝入記憶體,程式都是直接執行在記憶體上的,也就是說程式中訪問的記憶體地址都是實際的

如何獲得當前執行模組在程序地址空間的位置

(w)WinMain的hInstanceExe引數實際值是一個記憶體基地址;系統將可執行檔案的映像載入到程序地址空間中的這個位置。例如,系統開啟可執行檔案,並將它載入到地址0x00400000,則(w)WinMain的hInstanceExe引數值為0x00400000.

Linux程序地址空間的理解

對於Linux的虛擬記憶體的理解,這個例子算是一個很好的引導了,原文連結:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26683523&id=3201345 《Linux核心設計與實現》15

程序地址空間分佈

轉載請註明出處:http://blog.csdn.net/wangxiaolong_china 對於一個程序,其空間分佈如下圖所示: C程式一般分為: 1.程式段:程式段為程式程式碼在記憶體中的對映.一個程式可以在記憶體中多有個副本. 2.初始化過的資料:

Windows運用AVL樹對程序地址空間的管理

32位Windows系統中,程序在使用者態可用的地址空間範圍是低2G(x64下是低8192G)。隨著程序不斷的申請和釋放記憶體,這個2G的地址空間,有的地址範圍是保留狀態(reserved),有的地址範圍是提交狀態(對映到了物理頁面,committed),有的地址範圍是空閒

Linux程序地址空間 程序記憶體佈局

                一 程序空間分佈概述    對於一個程序,其空間分佈如下圖所示:                                     程式段(Text):程式程式碼在記憶體中的對映,存放函式體的二進位制程式碼。初始化過的資料(Data):在程式執行初已經對變數進行初始化的資

程序地址空間與虛擬儲存空間的理解

在進入正題前先來談談作業系統記憶體管理機制的發展歷程,瞭解這些有利於我們更好的理解目前作業系統的記憶體管理機制。 一 早期的記憶體分配機制 在 早期的計算機中,要執行一個程式,會把這些程式全都裝入記憶體,程式都是直接執行在記憶體上的,也就是說程式中訪問的記憶體地址都是實際的

程序地址空間的一點認識

在進入正題前先來談談作業系統記憶體管理機制的發展歷程,瞭解這些有利於我們更好了解目前作業系統的記憶體管理機制。 一 早期的記憶體分配機制 在早期的計算機中,要執行一個程式,會把這些程式全都裝入記憶體,程式都是直接執行在記憶體上的,也就是說程式中訪問的記憶體地址都是實際的實體

虛擬核心和程序地址空間

地址空間和記憶體         地址空間和記憶體是不同概念,地址空間是CPU所能管理記憶體的範圍,4GB地址空間不表示4GB實體記憶體。但4G大小虛擬地址空間的資料肯定會對應4G大小實體記憶體的資料。按32位Windows來說,虛擬地址空間是4GB,虛擬記憶體地址的最大值是4GB-1,並不是一開始就有4GB