零页面机制在缺页中断中的作用

在2.6的早期内核以及更早的2.4,2.2以及1.X内核中有一个empty_zero_page的数组,它是一个全局的页面数组,它的作用很大,要比现在2.6.2X/3X内核中empty_zero_page的重要性大,empty_zero_page的主要作用就是只要用户引用一个只读的匿名页面并没有进行写操作,缺页中断处理中内核就不会给用户进程分配新的页面。零页面不加入lru链表,因此它不会被换出,也就是说这些页面根本不参与内存管理,它们没有换入换出的必要,它们中没有数据,它们仅仅使一些桩子;零页面仅仅占用了若干个地址,并且很确定,影响的cacheline也很确定,读页面本身并不会影响cacheline,因为这些页面不允许写,唯一使得零页面影响cacheline的是对于其page结构中引用计数的操作,因为page结构本身也在内存当中,而2.6内核新引入了反向映射,而反向映射必然要操作引用计数,即使零页面也不例外。我们可以看一下2.6.1的缺页处理中的匿名页面部分:

static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma, pte_t *page_table, pmd_t *pmd, int write_access, unsigned long addr)

{

pte_t entry;

struct page * page = ZERO_PAGE(addr); //得到零页面,注意所有的进程缺页中都会引用同样的这个零页面

...

entry = pte_wrprotect(mk_pte(ZERO_PAGE(addr), vma->vm_page_prot)); //写保护,只要有写操作就会分配新页面

if (write_access) {

...

page = alloc_page(GFP_HIGHUSER);

if (!page)

goto no_mem;

clear_user_highpage(page, addr); //安全约定,清除遗留数据

...

mm->rss++;

entry = pte_mkwrite(pte_mkdirty(mk_pte(page, vma->vm_page_prot)));

lru_cache_add_active(page); //加入lru,接受内存管理模块的管理

mark_page_accessed(page);

}

set_pte(page_table, entry); //设置页表项

pte_chain = page_add_rmap(page, page_table, pte_chain); //虽然对于零页其实不进行写操作进而也不会破坏既有的cacheline映射,但是对于零页面的page结构本身却还是需要操作的,page可以视为零页面的元数据,零页面可以避开写,但是零页面毕竟确实被选作了用户页面返给了用户,因此需要接受一定的内存管理,接下来的反向映射就是这么一回事,在2.6.1版本中,反向映射忽略了零页面,但是在后来的版本中为了统一管理还是需要修改零页面page结构的一些字段导致了写内存进一步缓存被更新,最终导致了彻底去除了零页面

pte_unmap(page_table);

update_mmu_cache(vma, addr, entry);

...

}

也许有人会问只读页面就一定是零页面吗?有数据的页面也可能是只读的,这当然是对的,但是对于匿名页面来讲,就不对了,首先由于安全原则,新的匿名页面必须清零,但是由于零页面实际上没有什么用处,那么必然有一个进程对其进行写操作,因此它必须是可写的,最起码对于某一个进程是可写的,但是对于文件映射来讲,只读的的并且有数据的页面是存在的,最简单的例子就是elf文件的代码段内存映射。匿名页面一旦被写入了数据,接下来如果该页面被换出,然后重新引用该页面的时候,do_swap_page将会被调用,以后的操作就是从交换空间换页了,匿名页面摇身一变成了交换空间的文件页面。理解了这一点接下来的问题就是既然零页面实际上没有什么用处,那么为何在用户引用只读页面的时候必须分配页面呢?这是操作系统的要求谁也回避不了,我们能做的就是尽可能的让一切更高效,依据懒惰原理,也即是等到不能再拖的时候在行动的原则,用户引用匿名页面并不一定马上就是写数据,而一旦写了数据它就成了交换空间的文件页面了,因此对于第一次引用的只读页面,它实际上不应该有数据的,因为数据必须是写入的,如果你读它,得到的是全部0,因此没有必要为只读页面分配页面,所有的只读页面在第一次引用的时候只需要返回同一个零页面即可,这样就节省了大量的页面,防止一些进程只引用只读页面而不进行写操作而浪费大量的内存最终导致频繁换页。内核只考虑语义合理,具体实现就是怎么高效安全怎么来,对于只读的,匿名的,第一次引用的页面,映射到同一处没有任何问题,因为都是没有数据的零页面,符合安全规则也更加高效,同时符合用户空间编程语义。

那么这种方式带来的另一个效果是什么呢?试想如果每次都分配新页面,那么每次有很大可能分到不同的页面,这样操作这些页面的元数据的结果就是造成重置大量的cacheline,非常影响效率,零页面位置固定,影响cacheline的位置也固定并且很有限,因此效率可以提高。在只读的匿名页面缺页之后,内核只会给用户一个零页面,并且该零页面是以写保护方式给用户提供的,然后一旦有进程对该页面进行写操作,那么内核会以写保护违规的方式触发缺页中断,在中断处理中会重新分配一个新的页面给用户,这就是所谓的懒惰的方式,直到用户进程最终写页面的时候才会分配页面给用户,对于零页面,其实就是将新分配的页面清零,这在同一进程中常见,进程往往先引用一个页面,然后就行写操作或者什么也不做,对于非零页面就是将老页面的数据拷贝到新的页面,这在fork时比较常见。在2.6.1的代码,调用了一个copy_cow_page来拷贝页面数据,过程就是无论如何先分配一个页面然后再考虑怎么初始化其数据:

static inline void copy_cow_page(struct page * from, struct page * to, unsigned long address)

{

if (from == ZERO_PAGE(address)) {

clear_user_highpage(to, address);

return;

}

copy_user_highpage(to, from, address);

}

2.6.17的代码中去除了copy_cow_page函数,将分配页面的动作放到了判断页面类型之后,因为2.6.17的内核中分配页面更加细化了,这些事情还是自己看代码的好:

if (old_page == ZERO_PAGE(address)) {

new_page = alloc_zeroed_user_highpage(vma, address);

if (!new_page)

goto oom;

} else {

new_page = alloc_page_vma(GFP_HIGHUSER, vma, address);

if (!new_page)

goto oom;

cow_user_page(new_page, old_page, address);

}

但是等等,事情发生了,不知道是好事还是坏事,我觉得不怎么好,零页面虽然有很奇妙的功效,但是在最近的内核中被去除了,在缺页中断中无论如何都不会考虑零页面了,而是无论如何都分配新的页面,难道零页面不好吗?Nick觉得不好,正是由于每引用一次零页面就要修改page中的某些字段,而page存于内存,这势必会冲刷cacheline,在机器中,很多的地址将共用一个cacheline,因此即便零页面再好(它比每次重新分配一个页面好在可以控制cacheline的冲刷),它还是会将很多cacheline冲刷,正如一个罪犯随机杀了10个人,另一个罪犯杀了确定的10个人,他们谁的罪更轻些?显然这个问题很可笑,只可惜linux内核的早期版本正是被这种笑话冲昏了头脑,实际上零页面节省的这种cacheline就是类似的一个笑话。虽然固定的冲刷cacheline比随机的冲刷带来的损失可能更小,但是能小多少呢?可以确定的是对于第一次的匿名只读页面分配零页面导致的多了将近一倍的缺页,我们需要做的额外工作是将页面清零,这虽然是个代价,但是却比大量冲刷缓冲要便宜的多。为何呢?注意,每次在缺页中断中引用零页面就意味着一次cacheline的绑定,因为要修改page数据结构的字段,由于cacheling是一个cpu所有进程共用的,在进程分时调度下cacheline不断被冲刷,每次缺页将导致一次cacheline冲刷,虽然是固定地址但是谁也保证不了缺页之前这个cacheline存放的是哪个进程的数据。于是零页面机制在缺页处理中正式下课。

纵观零页面的历史,显示出的是linux内核开发的灵活和当仁不让!只要有用的谁也去不掉,只要没有用,再花哨的东西终将废止。

### 回答1: 分页管理的地址转换是指将逻辑地址转换为物理地址的过程。在分页管理,逻辑地址由页号和页内偏移量组成,而物理地址由页框号和页内偏移量组成。地址转换的过程需要使用页表来实现,通过查找页表对应的页号,可以得到该页所在的页框号,然后将页内偏移量加上页框号得到物理地址。 缺页中断处理是指当程序访问的页面不在内存时,操作系统需要将该页面从磁盘读入内存,然后再进行地址转换。这个过程需要使用缺页中断机制来实现,当程序访问的页面不在内存时,CPU会产生一个缺页中断,操作系统会根据页表的信息将该页面从磁盘读入内存,并更新页表的信息,然后再重新执行访问该页面的指令。 ### 回答2: 请求分页管理是一种虚拟内存管理技术,将物理内存分成若干等大小的区域(页面),并将进程的逻辑空间也划分成等大小的区域(页)。将进程的页调入物理内存,使进程访问逻辑地址时自动转换为物理地址,从而实现了虚拟内存的抽象。 地址转换是请求分页管理的核心操作之一,它主要是将进程的虚拟地址映射到物理地址上。地址转换部分通常位于央处理器(CPU)的硬件,其操作链路如下:当CPU访问虚拟地址时,处理器会将该地址发送给地址转换机构。地址转换机构先通过地址转换表得到与该虚拟地址对应的物理页框号,然后再加上虚拟地址的页内偏移量,最终得到物理地址。这个过程是透明的,对进程来说,它看到的仍然是逻辑地址。 缺页中断处理是请求分页管理的另一个重要操作,在内存不足时,操作系统会将进程暂时挂起,将进程未能获取的页面调入内存,然后再让进程继续运行。这个过程,当进程访问一个还未被调入内存的页面时,请求分页管理会发出缺页中断信号,从而通知操作系统重新为该进程映射虚拟地址到物理地址的过程,并确定该页面的位置,将其调入内存。 总之,地址转换和缺页中断处理是请求分页管理的核心操作,这些操作必须快速有效、可靠性强,从而确保操作系统能够高效地管理虚拟内存,满足进程的空间需求。 ### 回答3: 分页管理是操作系统一种重要的内存管理技术,它采用虚拟内存和物理内存分离的方式,使得进程可以在逻辑上拥有比实际物理内存更大的内存空间。地址转换及缺页中断处理是分页管理的两个关键技术,以下分别进行详细说明: 地址转换:当进程访问一个虚拟地址时,操作系统需要将该地址转换为实际物理地址,以访问对应的物理存储空间。地址转换过程主要包括两个步骤:页表查找和页内偏移计算。操作系统维护了每个进程的页表,该表记录了虚拟地址和实际物理地址之间的映射关系,当进程访问一个虚拟地址时,首先通过页表进行查找,获取到对应的物理页框号。然后,结合物理页框大小和地址在所在页内的偏移量,计算出实际物理地址。这样,进程就可以通过实际物理地址直接访问物理内存,完成内存读写操作。 缺页中断处理:当进程访问一个虚拟地址,但其对应的物理页框不存在于内存时,就会发生缺页中断,操作系统需要进行一系列的处理操作,以将缺失的物理页框从存储器读取到内存,使得进程可以正常访问。缺页中断处理主要包括以下几个步骤:首先,操作系统需要通过页表获取到缺失的虚拟地址所在页的页号,然后查找硬盘或其他外部存储介质,将页面读取到内存的空闲物理页框,并修改页表的映射关系。最后,重新执行导致缺页中断的指令,从而完成进程对缺失的物理页框的访问。如果内存没有足够的物理页框,操作系统需要通过页面置换算法,将一些暂时不会被使用的页替换掉,以腾出足够的物理内存空间。 以上是关于分页管理地址转换和缺页中断处理的详细说明,这两个技术是确保分页管理正常运行的关键所在,也是操作系统内存管理不可或缺的组成部分。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值