本文共 6546 字,大约阅读时间需要 21 分钟。
用户态和内核态的划分
struct mm_struct 结构来管理内存,是struct task_struct中的一个成员变量
\include\linux\sched.hstruct mm_struct *mm;task_size 是struct mm_struct 里面的一个成员变量
\include\linux\mm_types.h
unsigned long task_size; /* size of task vm space */
整个虚拟内存空间要一分为二,一部分是用户态地址空间,一部分是内核态地址空间,那这两部分的分界线就要是用 task_size 来定义
TASK_SIZE 定义
\arch\x86\include\asm\processor.h
#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 位的系统
- 最大能够寻址 2^32=4G,其中用户态虚拟地址空间是 3G,内核态是 1G。
64 位系统
虚拟地址只使用了 48 位- 1 左移了 47 位,就相当于 48 位地址空间一半的位置,0x0000800000000000
#define TASK_SIZE_MAX ((1UL << 47) - PAGE_SIZE)
- 然后减去一个页,就是 0x00007FFFFFFFF000,共 128T。
- 内核空间也是 128T。内核空间和用户空间之间隔着很大的空隙,以此来进行隔离。
用户态布局
用户态虚拟空间里面的几类数据
struct mm_struct 里面
\include\linux\mm_types.hunsigned 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;
- total_vm 总共映射的页的数目
- locked_vm 就是被锁定不能换出
- pinned_vm 不能换出,也不能移动
- data_vm 存放数据的页的数目
- exec_vm 存放可执行文件的页的数目
- stack_vm 栈所占的页的数目
- start_code 可执行代码的开始位置
- end_code 可执行代码的结束位置
- start_data 已初始化数据的开始位置
- end_data 已初始化数据的结束位置
- start_brk 堆的起始位置
- brk 堆当前的结束位置,malloc 申请一小块内存的话,就是通过改变 brk 位置实现的
- start_stack 栈的起始位置,栈的结束位置在寄存器的栈顶指针中
- arg_start 和 arg_end 参数列表的位置
- env_start 和 env_end 环境变量的位置
- mmap_base 虚拟地址空间中用于内存映射的起始地址
布局图
图片来自极客时间趣谈linux操作系统虽然 32 位和 64 位的空间相差很大,但是区域的类别和布局是相似的
区域的属性描述
使用 vm_area_struct 结构
\include\linux\mm_types.hstruct vm_area_struct *mmap; /* list of VMAs */struct rb_root mm_rb;vm_area_struct 定义
\include\linux\mm_types.h
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 里面是对这个内存区域可以做的操作的定义
- anon_vma 中anoy 是 anonymous,匿名的意思,匿名映射
- vm_file 指定被映射的文件
vm_area_struct 是如何和内存区域几类数据关联
虚拟内存区域可以映射到物理内存,也可以映射到文件,映射到物理内存的时候称为匿名映射,anon_vma 中,anoy 就是 anonymous,匿名的意思,映射到文件就需要有 vm_file 指定被映射的文件。
映射是在load_elf_binary里面实现的,加载内核的是它,启动第一个用户态进程 init 的是它,fork 完了以后,调用 exec 运行一个二进制程序的也是它,exec 运行一个二进制程序的时候,除了解析 ELF 的格式之外,另外一个重要的事情就是建立内存映射。
load_elf_binary 函数
\fs\binfmt_elf.c
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 是指向栈底的,current->mm->start_stack 就是栈底;
- elf_map 会将 ELF 文件中的代码部分映射到内存中来;
- set_brk 设置了堆的 vm_area_struct,这里面设置了 current->mm->start_brk = current->mm->brk,也即堆里面还是空的;
- load_elf_interp 将依赖的 so 映射到内存中的内存映射区域。
形成下面这个内存映射图
图片来自极客时间趣谈linux操作系统映射完毕后,什么情况下会修改呢?
- 第一种情况是函数的调用,涉及函数栈的改变,主要是改变栈顶指针。
- 第二种情况是通过 malloc 申请一个堆内的空间,当然底层要么执行 brk,要么执行 mmap。
内核态的布局
内核态的虚拟空间和某一个进程没有关系,所有进程通过系统调用进入到内核之后,看到的虚拟地址空间都是一样的。
在内核态,由于 32 位内核态空间太小了,32 位和 64 位的布局差别比较大
32 位的内核态的布局
图片来自极客时间趣谈linux操作系统32 位的内核态虚拟地址空间一共就 1G,占绝大部分的前 896M,称为直接映射区
直接映射区
所谓的直接映射区,就是这一块空间是连续的,和物理内存是非常简单的映射关系,其实就是虚拟内存地址减去 3G,就得到物理内存的位置。(虚拟地址 - 3G = 物理地址)
直接映射区也需要建立页表, 通过虚拟地址访问(除了内存管理模块)
直接映射区组成: 1MB 启动时占用; 然后是内核代码/全局变量/BSS等,即 内核 ELF文件内容; 进程task_struct 即内核栈也在其中
896M 这个值在内核中被定义为 high_memory,在此之上常称为“高端内存”
两个宏
__pa(vaddr) 返回与虚拟地址 vaddr 相关的物理地址;
__va(paddr) 则计算出对应于物理地址 paddr 的虚拟地址。#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)
\arch\x86\include\asm\page.h
\arch\x86\include\asm\page_32.h剩余虚拟空间组成
- 8MB 空余 在 896M 到 VMALLOC_START 之间有 8M 的空间
- 内核动态映射空间 VMALLOC_START 到 VMALLOC_END 之间称为内核动态映射空间,动态分配内存, 映射放在内核页表中
- 持久内存映射 PKMAP_BASE 到 FIXADDR_START 的空间称为持久内核映射。储存物理页信息
- 固定内存映射 FIXADDR_START 到 FIXADDR_TOP(0xFFFF F000) 的空间,称为固定映射区域,主要用于满足特殊需求。
- 临时内存映射 在最后一个区域可以通过 kmap_atomic 实现临时内核映射,例如为进程映射文件时使用
64 位的内核布局
图片来自极客时间趣谈linux操作系统- 8T 空余 从 0xffff800000000000 开始就是内核的部分,不过一开始有 8T 的空档区域
- 64T直接映射区域 从__PAGE_OFFSET_BASE(0xffff880000000000) 开始的 64T 的虚拟地址空间是直接映射区域,减去 PAGE_OFFSET 就是物理地址。大部分情况下还是会通过建立页表的方式进行映射
- 32T动态映射 从 VMALLOC_START(0xffffc90000000000)开始到 VMALLOC_END(0xffffe90000000000)的 - 32T 的空间
- 1T物理页描述结构 struct page 从 VMEMMAP_START(0xffffea0000000000)开始的 1T 空间用于存放物理页面的描述结构 struct page 的
- 512MB存放内核代码段、全局变量、BSS 等 从 __START_KERNEL_map(0xffffffff80000000)开始的 512M。减去 __START_KERNEL_map 就能得到物理内存的地址。
总结
内存结构
- 用户态:
- 代码段、全局变量、BSS
- 函数栈
- 堆
- 内存映射区
- 内核态:
- 内核的代码、全局变量、BSS
- 内核数据结构例如 task_struct
- 内核栈
- 内核中动态分配的内存
32 位的对应关系
图片来自极客时间趣谈linux操作系统
64 位的对应关系
图片来自极客时间趣谈linux操作系统参考资料:
趣谈Linux操作系统(极客时间)链接:
欢迎大家来一起交流学习转载地址:https://blog.csdn.net/leacock1991/article/details/107328814 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!