我正在读一本有关虚拟内存的教科书,上面写道:
任何 Linux 程序都可以通过调用执行以下操作的函数来调用加载器execve
:
删除当前进程虚拟地址的用户部分中现有的区域结构。
为新程序的代码、数据、bss 和堆栈区域创建新的区域结构。所有这些新领域都是私有写时复制。代码和数据区域映射到 a.out 文件的 .text 和 .data 部分
我只是在想如果该程序是一个单进程程序(不使用fork()
),当这个单个进程从一个虚拟地址开始时,比如说.data
的一部分a.out
,它将触发一个保护错误,然后错误处理程序注意到保护异常是由进程尝试写入私有写时复制区域中的页面引起的,它会在物理内存中创建该页面的新副本。
我在这里看到两个问题:
即使没有第二个进程可以共享物理内存中的同一页,私有区域的写时复制机制仍然会创建一个新页,这根本没有用,因为只有一个进程,没有其他进程会创建一个新页。写入这个页面,进程每次要修改一个页面都会触发异常进行处理,效率相当低下?
在物理内存中创建了该页面的新副本,那么不再被进程引用的原始页面怎么办?如果旧页驻留在物理内存中,不是浪费内存吗?
答案1
您缺少一条关键信息:当内核处理与写时复制场景相对应的页面错误时,如果目标页面由单个进程使用,它会使该页面可写,而不是复制它。
内核为每个页面保留一个计数器,mapcount
在struct page
;如果单个进程映射该页面,则该计数器为 0,并且每次新进程映射它时该计数器都会递增,并且在未映射时递减(例如因为它是写时复制并且一个进程试图写入它)。