Linux內存管理(一)Linux進程空間管理

Linux內存管理

Linux內存管理(一)Linux進程空間管理

Linux內存管理(二)物理內存管理(上)

Linux內存管理(三)物理內存管理(下)

Linux內存管理(四)用戶態內存映射

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位用戶空間

img

二、用戶態的佈局

用戶虛擬空間有幾類數據,代碼,全局變量,堆,內存映射區,棧。在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; //參數列表和環境變量的位置

img

除了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文件映射到內存中來

最終會形成下面形式

img

映射完後,什麼情況會修改這些信息?

第一種情況是函數調用,涉及函數棧的改變,主要修改棧頂指針

第二種是調用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位的內核態的佈局

img

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位的地址分佈如下

img

下面兩幅圖總結

img

img

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