在 Linux 中,当进程向系统请求一些(虚拟)内存时,它只是在 vma(进程虚拟内存的描述符)中注册,但每个虚拟的物理页面在调用时并未保留。之后,当进程访问该页时,会发生故障(访问会产生缺页中断),PF# handler 将分配物理页并更新进程页表。
有两种情况:读取时出错可能变成写保护的零页(特殊全局预置零页)的链接;写入错误(在零页和刚刚需要但未物理映射的页上)将导致实际的私有物理页分配。
对于 mmap(以及内部 mmap 的 brk/sbrk),此方法是每页的;所有 mmaped 区域都在 vma 中整体注册(它们具有开始和结束地址)。但是堆栈以其他方式处理,因为它只有起始地址(典型平台上较高的地址;增长到较低的地址)。
问题是:
当我在堆栈附近访问新的未分配内存时,它将获得 PF# 并增长。如果我访问的不是堆栈旁边的页面,而是距离堆栈 10 或 100 页的页面,如何处理这种增长?
例如
int main() {
int *a = alloca(100); /* some useful data */
int *b = alloca(50*4096); /* skip 49 pages */
int *c = alloca(100);
a[0]=1;
/* no accesses to b - this is untouched hole of 49 pages */
c[0]=1;
}
这个程序会为堆栈分配 2 或 50 个私有物理页吗?
我认为要求内核在单个页面错误中分配十个物理页面然后逐页分配十个页面错误是有利可图的(1个中断+ 1个上下文切换+简单,对N个页面分配请求的缓存友好循环与N个中断+ N 上下文切换 + N 页面分配,当 mm 代码可能从 Icache 中逐出时)。