目录
4.1 简介
4.2 进程虚拟地址空间
4.2.1 进程地址空间分布
4.2.2 建立布局
第3章讲了两点:物理内存的管理,内核虚拟地址管理。
本章讲:用户进程的虚拟地址空间管理。
4.1 简介
一个进程的整个虚拟地址空间(0-3G)中只有少部分映射到物理页。其余虚拟地址在访问时通过缺页异常处理,分配物理页,并建立页表项。
MMU作用:将虚拟地址转换为物理地址。
内部包含TLB硬件,用于缓存页表,加快转换速度。
4.2 进程虚拟地址空间
32位系统中,一个进程的虚拟地址空间范围:0 - 4G
TASK_SIZE:
含义:用户的虚拟地址空间的大小。
典型值:TASK_SIZE=3G。
PAGE_OFFSET:
作用:划分内核空间和用户空间。
典型值:0xC0000000,即3G用户空间,1G内核空间。
无论当前运行哪个进程,进程虚拟地址空间中内核部分的内容都相同。
解释:A/B两个进程的内核空间0xC0001000处会映射到相同物理内存。
而两个进程的用户空间0xA0001000映射到不同物理内存。
4.2.1 进程地址空间分布
struct mm_struct:管理一个进程的虚拟地址空间。
struct task_struct {
struct mm_struct *mm;
}
虚拟内存区域:简称VMA。
一个进程空间有很多VMA,如:
代码段,数据段,堆,栈,mmap映射区。
内核用struct vm_area_struct表示一个VMA。
struct mm_struct { //部分成员如下
...
unsigned long (*get_unmapped_area) (struct file *filp, addr, len, pgoff, flags);
//在进程地址空间中寻找合适的mmap映射位置
unsigned long mmap_base;
//mmap映射的起始位置
unsigned long task_size;
//进程的虚拟空间长度
//current->mm->task_size = TASK_SIZE;
unsigned long start_code, end_code, start_data, end_data;
//代码段和数据段:开始和结束地址。
//可执行文件映射到地址空间后,这些区域长度不变(只读)。
unsigned long start_brk, brk, start_stack;
//start_brk:堆的起始地址。
//brk:堆的结束地址。该值可变。(因为堆长度可变)
unsigned long arg_start, arg_end, env_start, env_end;
//参数列表和环境变量。位于栈中最高位置。
pgd_t *pgd;
//该进程页全局目录表的起始位置。
};
task_struct中flags成员的PF_RANDOMIZE标志:
作用:每次启动一个进程时,虚拟地址空间中mmap映射起点和栈起点进行随机偏移。
好处:防止攻击,防止通过缓冲溢出而获得栈访问权。
1. mmap映射起点:
mm->mmap_base = TASK_SIZE/3 + 随机值。
2. 栈起点:
mm->start_stack = STACK_STOP - 随机值。
get_unmapped_area函数指针:
作用:mmap函数以mmap_base为起始地址查找合适映射位置。
体系架构可各自实现该函数指针。如ARM中arch_get_unmapped_area
宏CONFIG_STACK_GROWSUP:
作用:可定义栈向上增长(默认向下增长)。
mmap增长方向也可以设置。
如何查找一个进程的虚拟地址address对应的物理地址?
1. current -> task_struct -> mm_struct
2. struct vm_area_struct *vma = find_vma(mm, address);
3. 根据vma的起始地址和address,计算在vma中的偏移量。
offset = address - vma->vm_start;
4. 根据mm_struct的pgd成员和address得到PTE。
三级/四级页表转换。
5. 计算页帧号。
pfn = pte_pfn(*pte);
6. 加上偏移。
物理地址 = pfn << PAGE_SHIFT | offset;
内核Linux 5.x为增加查找vma的cache hit,缓存了最近访问的vma
struct task_struct {
struct vmacache vmacache;
}
struct vmacache {
struct vm_area_struct *vmas[4];
//缓存最近访问的四个VMA
};
内核不能显式的将一个变量放入CPU cache。
访问变量后,其被硬件自动放入CPU cache中。
把VMA按访问时间先后顺序放入数组中,所以可得到最近访问的VMA,而该VMA大概率在CPU cache中。
4.2.2 建立布局
exec()调用load_elf_binary():
作用:装载ELF二进制可执行文件,以创建进程的地址空间。
不同可执行文件有各自实现的struct linux_binfmt,如a.out。
struct linux_binfmt elf_format = {
.load_binary = load_elf_binary, //加载ELF格式的二进制文件
.load_shlib = load_elf_library, //加载ELF格式的共享库
};
ELF文件启动过程:
应用层:fork->exec,执行对应do_execve系统调用。
内核:do_execve->do_execve_common -> search_binary_handler
遍历所有struct linux_binfmt实例,执行load_binary函数指针,即ELF的load_elf_library。
/proc/sys/kernel/randomize_va_space:
作用:设置地址空间随机化。默认开启,随机偏移量最大1M
load_elf_binary实现:
load_elf_binary -> setup_new_exec - > arch_pick_mmap_layout
void arch_pick_mmap_layout(struct mm_struct *mm)
{
if ((current->flags & PF_RANDOMIZE)
random_factor = (get_random_int() % (1 << 8)) << PAGE_SHIFT;
mm->mmap_base = TASK_UNMAPPED_BASE + random_factor;
//设置mmap区域起始位置
mm->get_unmapped_area = arch_get_unmapped_area;
//arch_get_unmapped_area为ARM中的实现
mm->unmap_area = arch_unmap_area;
}
setup_arg_pages:
设置mm->arg_start。
设置mm->stack栈起始位置(加上随机偏移)。
get_unmapped_area什么时候调用?
后续访问地址时,调用mmap来创建不同VMA,此时调用get_unmapped_area为新VMA选择合适位置。
最终调用mm->get_unmapped_area或file->f_op->get_unmapped_area;