目录
虚拟地址空间划分(用户空间)
32位系统虚拟地址空间分配
64位系统虚拟地址空间分配
内存管理
内核布局虚拟地址空间
虚拟内存区域在内核中组织
内存访问权限
调用malloc 申请内存,
虚拟地址空间布局(内核)
直接映射区:范围地址3G-3G+896M
高端内存896M以上,ZONE_HIGHMEM,
虚拟内存 vmalloc 动态映射区
虚拟内存 永久映射区
虚拟内存 固定映射区
临时映射区:
1:代码段
存放二进制文件机器码的虚拟内存
2:数据段
在代码中被我们制定了初始值的全局变量和静态变量在虚拟内存中的存储区
没有指定初始值的全局变量和静态变量在虚拟内存空间中的存储区域叫做BSS段;这些未初始化的全局变量被加载进内存知乎会被初始化为0.
上面介绍的全局变量和静态变量都是在编译期间确定的;
3:堆
程序运行时申请的内存,所以在虚拟内存中需要一块区域存放动态申请的内存,叫做堆。
4:文件映射与匿名映射区
动态链接库.so glibc 这些动态链接库也有自己对应的代码段,数据段BSS段 也需要被加载内存
文件映射mmap 映射的内存
5、栈
调用函数以及过程中的局部变量和函数参数也需要一块区域保存。
寻址范围2^32 ,虚拟内存空间4GB 0x0000 0000 - 0xFFFF FFFF
其中用户态虚拟地址空间为3GB,虚拟内存范围0x0000 0000 - 0xC000 000
内核态地址空间1GB ,虚拟内存范围0XC000 000 -0xFFFF FFFF
用户态地址不是从0x0000 0000 开始 而是从0x08048000地址开始,因为大多数操作系统中数值比较小被认为是一个不合法的地址,不允许访问小地址。
内核使用start_brk标识堆的起始地址,brk标识当前堆的结束位置。当堆申请新的内存空间时,只需要将brk指针增加对应的大小,回收地址时减少对应的大小即可。如malloc申请内存就是通过改变brk位置实现。
文件映射区域的地址增长方向 从高地址向低地址增长;
栈空间的地址增长方向从高地址向低地址增长,每次进行申请新的栈地址时,其地址在减少。Start_stack标识起始地址,RSP 寄存器保存栈顶指针stack pointer,RBP寄存器中保存栈基地址。
2^64 虚拟内存空间为16EB
内核空间与用户空间有空洞 叫做 canonical address空洞
内核的结构体 task_struct -> mm_struct
在do_fork中 copy_mm 完成子进程虚拟内存空间mm_struct结构的创建和初始化
dup_mm函数将父进程的虚拟内存空间以及相关页表拷贝到子进程的mm_struct中,再赋值给task_struct
如果设置CLONE_VM,将父进程的虚拟内存空间以及相关页表直接赋值给子进程,父子进程共享
内核线程和用户线程区别,内核线程没有mm_struct,内核线程之间调度不涉及地址空间切换。
内核和用户地址空间划分 分界线
TASK_SIZE 大小计算 :task_size_max 1 左移47位 减 PAGE_SIZE (默认为4k) 就是
0x00007FFFFFFFF000,共 128T
内核空间的起始地址0xFFFF 8000 0000 0000
struct mm_struct {
unsigned long task_size; /* size of task vm space */
...
}
task_size 定义了用户态地址空间与内核态地址空间之间的分界线
#define TASK_SIZE __PAGE_OFFSET
64 位系统中用户地址空间和内核地址空间的分界线在 0x0000 7FFF FFFF F000 地址处,task_size 为0x0000 7FFF FFFF F000
/arch/x86/include/asm/page_64_types.h
#define TASK_SIZE (test_thread_flag(TIF_ADDR32) ? \
IA32_PAGE_OFFSET : TASK_SIZE_MAX)
#define TASK_SIZE_MAX task_size_max()
#define task_size_max() ((_AC(1,UL) << __VIRTUAL_MASK_SHIFT) - PAGE_SIZE)
#define __VIRTUAL_MASK_SHIFT 47
计算 TASK_SIZ与页大小有关,在 task_size_max() 的计算逻辑中 1 左移 47 位得到的地址是 0x0000800000000000,然后减去一个 PAGE_SIZE (默认为 4K),就是 0x00007FFFFFFFF000,共 128T。
所以在 64 位系统中的 TASK_SIZE 为 0x00007FFFFFFFF000
PAGE_SIZE 定义在
/arch/x86/include/asm/page_types.h
文件中
/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT 12
#define PAGE_SIZE (_AC(1,UL) << PAGE_SHIFT)
内核布局虚拟地址空间
task_struct->mm_struct
struct mm_struct {
unsigned long task_size; /* size of task vm space */
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;
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 */
..............
}
struct vm_area_struct, 每个结构体对应于虚拟内存空间中的唯一虚拟内存区域VMA [vm_start,vm_end)
vm_flags | 访问权限 |
---|---|
VM_READ | 可读 |
VM_WRITE | 可写 |
VM_EXEC | 可执行 |
VM_SHARD | 可多进程之间共享 |
VM_IO | 可映射至设备 IO 空间 |
VM_RESERVED | 内存区域不可被换出 |
VM_SEQ_READ | 内存区域可能被顺序访问 |
VM_RAND_READ | 内存区域可能被随机访问 |
我们可以通过 posix_fadvise,madvise 系统调用来暗示内核是否对相关内存区域进行顺序读取或者随机读取。
虚拟内存映射可以映射到文件上,也可以映射到物理内存上。
映射到物理内存上称之为 匿名映射,映射到文件中 称之为 文件映射。
如果申请的是小块内存(低于128k),则会调用do_brk(),通过调整堆中的brk指针大小来增加或者回收堆内存;
如果申请的是大块内存(大于128k),调用mmap,在文件映射与匿名映射区域中创建一块VMA内存区域。这块映射区域用 struct anon_vma 表示。
当调用 mmap 进行文件映射时,vm_file 属性就用来关联被映射的文件。这样一来虚拟内存区域就与映射文件关联了起来。vm_pgoff 则表示映射进虚拟内存中的文件内容,在文件中的偏移。
32位体系结构,1G虚拟内存空间
这块连续的虚拟内存地址会映射到物理地址0~896M这块连续的物理内存上。
物理内存直接映射区的前16M,ZONE_DMA:用于为DMA分配内存;
16M-896M,ZONE_NORMAL
32位4G 高端内存区域4G-896M = 3200M,这块区域如何映射到内核虚拟内存中?
内核虚拟内存空间 1G - 896M = -128M,两者大小不一致不能通过直接映射
需要采用动态映射
VMALLOC_START到VMALLOC_END之间的这块区域成为动态映射区,动态方式映射到物理内存中的高端内存。
用户使用malloc 申请内存,在内核中使用vmalloc 进行内存分配。vmalloc分配的内存在虚拟地址上是连续的,但是在物理内存上是不连续的。
PKMAP_BASE到PKMAP_START之间的这段空间成为永久映射区。允许这段虚拟内存映射到物理高端内存长期映射关系。 如通过alloc_pages函数在物理内存高端区域申请到物理页,这些物理内存通过调用kmap映射到永久映射区中。
页数限制 PKMAP_BASE
范围:FIXADDR_START 到 FIXMAP_TOP
此处虚拟地址可以自由映射到物理内存的高端地址上,虚拟地址是固定的,而被映射的物理地址是可以改变的。
作用:在内核启动过程中,有些模块需要使用虚拟内存并映射到指定的物理地址上,而且这些模块也没有办法等待完整的内存管理模块初始化后再进行地址映射。因此,内核固定分配了一些虚拟地址,这些地址有固定用途,使用该地址的模块在初始化的时候,将这些固定分配的虚拟地址映射到指定的物理地址上去。
数据拷贝过程,kmap_atomic 创建映射;kunmap_atomic 解除映射