趣谈Linux操作系统学习笔记-内存管理(22讲)
用户态和内核态的划分
内存管理信息: task_struct 的 mm_struct 中
整个虚拟内存空间:
1)是用户态地址空间
2)是内核态地址空间
那这两部分的分界线在哪里呢?这就要 task_size 来定义
1 #ifdef CONFIG_X86_32 2 /* 3 * User space process size: 3GB (default). 4 */ 5 #define TASK_SIZE PAGE_OFFSET 6 #define TASK_SIZE_MAX TASK_SIZE 7 /* 8 config PAGE_OFFSET 9 hex 10 default 0xC0000000 11 depends on X86_32 12 */ 13 #else 14 /* 15 * User space process size. 47bits minus one guard page. 16 */ 17 #define TASK_SIZE_MAX ((1UL << 47) - PAGE_SIZE) 18 #define TASK_SIZE (test_thread_flag(TIF_ADDR32) ? \ 19 IA32_PAGE_OFFSET : TASK_SIZE_MAX) 20 ......
对于 32 位系统: 最大能够寻址 2^32=4G,其中用户态虚拟地址空间是 3G,内核态是 1G
对于 64 位系统:
虚拟地址只使用了 48 位。就像 代码里面:写的一样,1 左移了 47 位,就相当于 48 位地址空间一半的位置,0x0000800000000000,然后减去一个页,就是 0x00007FFFFFFFF000,共 128T。同样,内核空间也是 128T。内核空间和用户空间之间隔着很大的空隙,以此来进行隔离
用户态布局
用户态虚拟空间的布局
1 unsigned long mmap_base; /* base of mmap area */ 2 unsigned long total_vm; /* Total pages mapped */ 3 unsigned long locked_vm; /* Pages that have PG_mlocked set */ 4 unsigned long pinned_vm; /* Refcount permanently increased */ 5 unsigned long data_vm; /* VM_WRITE & ~VM_SHARED & ~VM_STACK */ 6 unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE & ~VM_STACK */ 7 unsigned long stack_vm; /* VM_STACK */ 8 unsigned long start_code, end_code, start_data, end_data; 9 unsigned long start_brk, brk, start_stack; 10 unsigned long arg_start, arg_end, env_start, env_end;
1 ) mmap_base: 表示虚拟地址空间中用于内存映射的起始地址。一般情况下,这个空间是从高地址到低地址增长的。前面咱们讲 malloc 申请一大块内存的时候,就是通过 mmap 在这里映射一块区域到物理内存。咱们加载动态链接库 so 文件,也是在这个区域里面,映射一块区域到 so 文件
2 ) total_vm: 是总共映射的页的数目。我们知道,这么大的虚拟地址空间,不可能都有真实内存对应,所以这里是映射的数目。当内存吃紧的时候,有些页可以换出到硬盘上,有的页因为比较重要,不能换出。
3 ) locked_vm: 是被锁定不能换出
4 ) pinned_vm 是不能换出,也不能移动
5 ) data_vm: 是存放数据的页的数目,exec_vm 是存放可执行文件的页的数目,stack_vm 是栈所占的页的数目
6 ) start_code 和 end_code 表示可执行代码的开始和结束位置,start_data 和 end_data 表示已初始化数据的开始位置和结束位置
7 ) start_brk 是堆的起始位置,brk 是堆当前的结束位置。前面咱们讲过 malloc 申请一小块内存的话,就是通过改变 brk 位置实现的
8 ) start_stack 是栈的起始位置,栈的结束位置在寄存器的栈顶指针中
9 ) arg_start 和 arg_end 是参数列表的位置, env_start 和 env_end 是环境变量的位置。它们都位于栈中最高地址的地方
示图:
load_elf_binary 实现vm_area_struct 和上面的内存区域关联
1 static int load_elf_binary(struct linux_binprm *bprm) 2 { 3 ...... 4 setup_new_exec(bprm); 5 ...... 6 retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP), 7 executable_stack); 8 ...... 9 error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt, 10 elf_prot, elf_flags, total_size); 11 ...... 12 retval = set_brk(elf_bss, elf_brk, bss_prot); 13 ...... 14 elf_entry = load_elf_interp(&loc->interp_elf_ex, 15 interpreter, 16 &interp_map_addr, 17 load_bias, interp_elf_phdata); 18 ...... 19 current->mm->end_code = end_code; 20 current->mm->start_code = start_code; 21 current->mm->start_data = start_data; 22 current->mm->end_data = end_data; 23 current->mm->start_stack = bprm->p; 24 ...... 25 }
1)load_elf_binary 会完成以下的事情:
2)调用 setup_new_exec,设置内存映射区 mmap_base;
3)调用 setup_arg_pages,设置栈的 vm_area_struct,这里面设置了 mm->arg_start 是指向栈底的,current->mm->start_stack 就是栈底;
4)elf_map 会将 ELF 文件中的代码部分映射到内存中来;
5)set_brk 设置了堆的 vm_area_struct,这里面设置了 current->mm->start_brk = current->mm->brk,也即堆里面还是空的;
6)load_elf_interp 将依赖的 so 映射到内存中的内存映射区域
映射完毕后,什么情况下会修改呢?
第一种情况是函数的调用,涉及函数栈的改变,主要是改变栈顶指针。
第二种情况是通过 malloc 申请一个堆内的空间,当然底层要么执行 brk,要么执行 mmap
内核态的布局
32 位的内核态的布局
32 位的内核态虚拟地址空间一共就 1G,占绝大部分的前 896M,我们称为直接映射区
直接映射区:就是这一块空间是连续的,和物理内存是非常简单的映射关系,其实就是虚拟内存地址减去 3G,就得到物理内存的位置。
64 位的内存布局
64 位的内核主要包含以下几个部分:
从 0xffff800000000000 开始就是内核的部分,只不过一开始有 8T 的空档区域。
从 __PAGE_OFFSET_BASE(0xffff880000000000) 开始的 64T 的虚拟地址空间是直接映射区域,也就是减去 PAGE_OFFSET 就是物理地址。虚拟地址和物理地址之间的映射在大部分情况下还是会通过建立页表的方式进行映射。
从 VMALLOC_START(0xffffc90000000000)开始到 VMALLOC_END(0xffffe90000000000)的 32T 的空间是给 vmalloc 的。
从 VMEMMAP_START(0xffffea0000000000)开始的 1T 空间用于存放物理页面的描述结构 struct page 的。
从 __START_KERNEL_map(0xffffffff80000000)开始的 512M 用于存放内核代码段、全局变量、BSS 等。这里对应到物理内存开始的位置,减去 __START_KERNEL_map 就能得到物理内存的地址。这里和直接映射区有点像,但是不矛盾,因为直接映射区之前有 8T 的空当区域,早就过了内核代码在物理内存中加载的位置
总结时刻
进程运行状态在 32 位下对应关系
64 位的对应关系