Linux內存管理
Linux內存管理(一)Linux進程空間管理
一、用戶態和內核態的空間劃分
每個進程或者線程都是一個task_struct,它們都有一個mm_struct用於管理內存
struct mm_struct *mm;
在mm_struct中有這樣的一個變量
unsigned long task_size; /* size of task vm space */
前面說過進程的空間被一分爲二,一部分爲用戶空間,一部分爲內核空間,那麼這個分割線在哪呢?就是這個task_size定義的
內核裏有TASK_SIZE的定義
#ifdef CONFIG_X86_32
/*
* User space process size: 3GB (default).
*/
#define TASK_SIZE PAGE_OFFSET
#define TASK_SIZE_MAX TASK_SIZE
/*
config PAGE_OFFSET
hex
default 0xC0000000
depends on X86_32
*/
#else
/*
* User space process size. 47bits minus one guard page.
*/
#define TASK_SIZE_MAX ((1UL << 47) - PAGE_SIZE)
#define TASK_SIZE (test_thread_flag(TIF_ADDR32) ? \
IA32_PAGE_OFFSET : TASK_SIZE_MAX)
......
當執行一個新進程的時候,會做以下操作
current->mm->task_size = TASK_SIZE;
對於32位,高1G位內核空間,低3G位用戶空間
二、用戶態的佈局
用戶虛擬空間有幾類數據,代碼,全局變量,堆,內存映射區,棧。在mm_struct中,有下面這些變量統計着這些信息
unsigned long mmap_base; /* base of mmap area,內存映射區起始地址 */
unsigned long total_vm; /* Total pages mapped,總共映射頁的數目 */
unsigned long locked_vm; /* Pages that have PG_mlocked set,被鎖定不能換出的頁數目 */
unsigned long pinned_vm; /* Refcount permanently increased,不能換出也不能移動的頁數目 */
unsigned long data_vm; /* VM_WRITE & ~VM_SHARED & ~VM_STACK,存放數據的頁數目 */
unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE & ~VM_STACK,存放可運行程序的頁數目 */
unsigned long stack_vm; /* VM_STACK,棧所佔的頁數目 */
unsigned long start_code, end_code, start_data, end_data; //代碼段和數據段的位置
unsigned long start_brk, brk, start_stack; //堆的位置,棧底位置,棧頂存放在棧指針寄存器中
unsigned long arg_start, arg_end, env_start, env_end; //參數列表和環境變量的位置
除了mm_struct之外,還有一個專門的結構體vm_area_struct,來描述這些區域的屬性
struct vm_area_struct *mmap; /* list of VMAs,鏈表 */
struct rb_root mm_rb; /* 紅黑樹樹根 */
struct vm_area_struct {
/* The first cache line has the info for VMA tree walking. */
unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address within vm_mm. */
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next, *vm_prev;
struct rb_node vm_rb;
struct mm_struct *vm_mm; /* The address space we belong to. */
struct list_head anon_vma_chain; /* Serialized by mmap_sem &
* page_table_lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */
/* Function pointers to deal with this struct. */
const struct vm_operations_struct *vm_ops;
struct file * vm_file; /* File we map to (can be NULL). */
void * vm_private_data; /* was vm_pte (shared mem) */
} __randomize_layout;
vm_start和vm_end指定了區域在用戶空間的起始地址和結束地址。vm_next和vm_prev維護一個鏈表
vm_rb是紅黑樹節點,用於放置到紅黑樹中,vm_ops是這個區域的操作
vm_area_struct是如何一上面的區域關聯起來的呢?
這個事情是在load_elf_binary中做的,加載內核是它,啓動第一個用戶態進程init是它,fork完調用exec執行一個程序也是它
當執行exec時,除了解析elf格式之外,還有一件重要的事情就是建立內存映射
static int load_elf_binary(struct linux_binprm *bprm)
{
......
setup_new_exec(bprm);
......
retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
executable_stack);
......
error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
elf_prot, elf_flags, total_size);
......
retval = set_brk(elf_bss, elf_brk, bss_prot);
......
elf_entry = load_elf_interp(&loc->interp_elf_ex,
interpreter,
&interp_map_addr,
load_bias, interp_elf_phdata);
......
current->mm->end_code = end_code;
current->mm->start_code = start_code;
current->mm->start_data = start_data;
current->mm->end_data = end_data;
current->mm->start_stack = bprm->p;
......
}
load_elf_binary會做下面的事
- 調用setup_new_exec設置內存映射區mmap_base
- 調用setup_arg_pages設置棧的vm_area_struct,這裏設置mm->arg_start指向棧底
- elf_map會將elf文件中代碼段映射到內存中
- set_brk設置堆的vm_area_struct,這裏設置mm->start_brk=mm->brk,即堆位空
- load_elf_interp將依賴的so文件映射到內存中來
最終會形成下面形式
映射完後,什麼情況會修改這些信息?
第一種情況是函數調用,涉及函數棧的改變,主要修改棧頂指針
第二種是調用malloc分配內存,底層要麼調用brk,要麼調用mmap,關於mmap後面再講,這裏先討論brk
SYSCALL_DEFINE1(brk, unsigned long, brk)
{
unsigned long retval;
unsigned long newbrk, oldbrk;
struct mm_struct *mm = current->mm;
struct vm_area_struct *next;
......
/* 頁對齊 */
newbrk = PAGE_ALIGN(brk);
oldbrk = PAGE_ALIGN(mm->brk);
/* 如果兩個內存剛好在同一頁內 */
if (oldbrk == newbrk)
goto set_brk;
/* Always allow shrinking brk. */
/* 如果新堆頂小於舊的堆頂,說明要釋放內存 */
if (brk <= mm->brk) {
if (!do_munmap(mm, newbrk, oldbrk-newbrk, &uf))
goto set_brk;
goto out;
}
/* Check against existing mmap mappings. */
/* 在紅黑樹中找到當前當前堆頂處下一個vm_area_struct */
next = find_vma(mm, oldbrk);
/* 如果空閒區域不大於1頁,那麼就退出 */
if (next && newbrk + PAGE_SIZE > vm_start_gap(next))
goto out;
/* Ok, looks good - let it rip.否咋調用do_brk進行內存分配 */
if (do_brk(oldbrk, newbrk-oldbrk, &uf) < 0)
goto out;
/* 設置新的堆頂 */
set_brk:
mm->brk = brk;
......
return brk;
out:
retval = mm->brk;
return retval
static int do_brk(unsigned long addr, unsigned long len, struct list_head *uf)
{
return do_brk_flags(addr, len, 0, uf);
}
static int do_brk_flags(unsigned long addr, unsigned long request, unsigned long flags, struct list_head *uf)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma, *prev;
unsigned long len;
struct rb_node **rb_link, *rb_parent;
pgoff_t pgoff = addr >> PAGE_SHIFT;
int error;
len = PAGE_ALIGN(request);
......
find_vma_links(mm, addr, addr + len, &prev, &rb_link,
&rb_parent);
......
vma = vma_merge(mm, prev, addr, addr + len, flags,
NULL, NULL, pgoff, NULL, NULL_VM_UFFD_CTX);
if (vma)
goto out;
......
vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
INIT_LIST_HEAD(&vma->anon_vma_chain);
vma->vm_mm = mm;
vma->vm_start = addr;
vma->vm_end = addr + len;
vma->vm_pgoff = pgoff;
vma->vm_flags = flags;
vma->vm_page_prot = vm_get_page_prot(flags);
vma_link(mm, vma, prev, rb_link, rb_parent);
out:
perf_event_mmap(vma);
mm->total_vm += len >> PAGE_SHIFT;
mm->data_vm += len >> PAGE_SHIFT;
if (flags & VM_LOCKED)
mm->locked_vm += (len >> PAGE_SHIFT);
vma->vm_flags |= VM_SOFTDIRTY;
return 0;
find_vma_links找到將來的vm_area_struct節點在紅黑樹的位置,找到它的父節點、前序節點。接下來就是調用vma_merge,看新節點是否能夠和現有樹中的節點合併。如果地址是連着的,能夠合併,不需要創建新的vm_area_struct。否則創建新的vm_area_struct,添加到鏈表中,也加到紅黑樹中
三、內核態的佈局
32位的內核態的佈局
32位的內核空間就1G,佔據絕大部分的896G,稱爲直接映射區
所謂直接映射區,就是和物理內存的映射關係非常簡單,它直接映射物理的內存的前896M,就是內存地址減去3G,得到物理地址
內核中有兩個宏
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
#define __pa(x) __phys_addr((unsigned long)(x))
#define __phys_addr(x) __phys_addr_nodebug(x)
#define __phys_addr_nodebug(x) ((x) - PAGE_OFFSET)
即使這裏映射非常簡單,但是依然需要建立頁表
在涉及內核棧的分配,內核的進程管理會講內核棧放到3G-3G+896M地址中,當然存放在物理內存的前896M中,相應的頁表也會被創建
896M這個被定義爲高端內存,高端內存是物理內存的概念,是內核管理模塊看待物理內存的概念
內核空間剩餘的空間可以分爲以下幾類
- VMALLOC_START和VMALLOC_END之間稱爲內核的動態映射區,內核可以也可以通過vmlloac申請內存,這塊區域可以映射物理內存中任何的位置
- PKMAP_BASE到FIXADDR_START的空間稱爲持久內核映射。使用alloc_pages()在物理內存的高端內存分配物理頁,可以使用kmap將其映射到此區域
- FIXADDR_START到FIXADDR_TOP的空間稱爲固定映射區,主要用於滿足特殊需求
64位的地址分佈如下
下面兩幅圖總結