一步一步学linux操作系统: 19 内存管理_进程的虚拟内存空间的管理
发布日期:2021-06-29 17:13:49 浏览次数:2 分类:技术文章

本文共 6546 字,大约阅读时间需要 21 分钟。

用户态和内核态的划分

struct mm_struct 结构来管理内存,是struct task_struct中的一个成员变量

\include\linux\sched.h

struct mm_struct    *mm;

在这里插入图片描述

task_sizestruct 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。内核空间和用户空间之间隔着很大的空隙,以此来进行隔离。

图片来自极客时间趣谈linux操作系统

图片来自极客时间趣谈linux操作系统

用户态布局

用户态虚拟空间里面的几类数据

struct mm_struct 里面

\include\linux\mm_types.h

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;

在这里插入图片描述

  • 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操作系统
图片来自极客时间趣谈linux操作系统

虽然 32 位和 64 位的空间相差很大,但是区域的类别和布局是相似的

区域的属性描述

使用 vm_area_struct 结构

\include\linux\mm_types.h

struct 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操作系统

图片来自极客时间趣谈linux操作系统

映射完毕后,什么情况下会修改呢?

  • 第一种情况是函数的调用,涉及函数栈的改变,主要是改变栈顶指针。
  • 第二种情况是通过 malloc 申请一个堆内的空间,当然底层要么执行 brk,要么执行 mmap。

内核态的布局

内核态的虚拟空间和某一个进程没有关系,所有进程通过系统调用进入到内核之后,看到的虚拟地址空间都是一样的。

在内核态,由于 32 位内核态空间太小了,32 位和 64 位的布局差别比较大

32 位的内核态的布局

图片来自极客时间趣谈linux操作系统

图片来自极客时间趣谈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操作系统

图片来自极客时间趣谈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操作系统

图片来自极客时间趣谈linux操作系统

64 位的对应关系

图片来自极客时间趣谈linux操作系统
图片来自极客时间趣谈linux操作系统

参考资料:

趣谈Linux操作系统(极客时间)链接:

欢迎大家来一起交流学习

转载地址:https://blog.csdn.net/leacock1991/article/details/107328814 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:一步一步学linux操作系统: 20 内存管理_NUMA 方式物理内存管理_节点、区域、页以及伙伴系统(Buddy System)
下一篇:一步一步学linux操作系统: 18 内存管理_内存映射与分段、分页机制

发表评论

最新留言

很好
[***.229.124.182]2024年04月15日 18时26分49秒