`
totoxian
  • 浏览: 1033567 次
  • 性别: Icon_minigender_2
  • 来自: 西安
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

在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内核开发的灵活和当仁不让!只要有用的谁也去不掉,只要没有用,再花哨的东西终将废止。

分享到:
评论

相关推荐

    页面置换实习报告.docx

    当CPU接收到缺页中断信号,中断处理程序先保存现场,分析中断原因,转入缺页中断处理程序。该程序通过查找页表,得到该页所在外存的物理块号。如果此时内存未满,能容纳新页,则启动磁盘I/O将所缺之页调入内存,...

    第4章 内存管理.ppt

    掌握请求分页的页表机制、缺页中断机构和地址变换机构,掌握页面置换算法。 ◆掌握虚拟存储器的理论基础和定义,熟悉虚拟存储器实现方式和特征。掌握分段、分页和段页式存储管理原理和地址变换机构。

    千万字肝翻Linux内核源码,对底层原理深耕深分析,从入门到入狱

    动态、缺页中断、Kfifo环形缓冲区、开发工具ARM-LInux-gcc安装、网络协议栈、构建嵌入式Lnux系 统、内存性能优化、核心知识CPU、内核编译、UDP收包率、反向映射机制、MMu-gather操作、进程 描述符、虚拟内存机制、...

    综合实验材料

    好东西虚拟存储器管理 基本要求:模拟分页式存储管理中硬件的地址转换 选做内容:加入缺页中断机制(用先进先出页面调度算法处理缺页中断) 输入:指令的页好和单元号(偏移) 输出:指令的绝对地址

    深入分析Linux内核源码

    12.3.1 套接字在网络中的地位和作用 12.3.2 套接字接口的种类 12.3.3 套接字的工作原理 12.3.4 socket 的通信过程 12.3.5 socket为用户提供的系统调用 12.4 套接字缓冲区(sk_buff) 12.4.1 套接字缓冲区的特点 ...

    操作系统-抖动.txt

    抖动(Thrashing)就是指当内存中已无空闲空间而又发生缺页中断时,需要从内存中调出一页程序或 ---数据送磁盘的对换区中,如果算法不适当,刚被换出的页很快被访问,需重新调入,因此需再选一页 ---调出,而此时被换...

    清华大学Linux操作系统原理与应用

    1.1.3 从操作系统在整个计算机系统中所处位置 2 1.1.4 从操作系统设计者的角度 3 1.2 操作系统的发展 4 1.2.1 操作系统的演变 4 1.2.2 硬件的发展轨迹 5 1.2.3 软件的轨迹 6 1.2.4 单内核与微内核操作系统 7 1.3 ...

    ucore实验3实验报告.doc

    LAB3实验报告 实验目的: 了解虚拟内存的Page Fault异常处理实现 了解页替换算法在操作系统中的实现 实验内容: 本次实验是在实验二的基础上,借助于页表机制和实验一中涉及的中断异常处理机制, 完成Page Fault异常...

    《计算机操作系统》期末复习指导

    •要处理页面中断、缺页中断处理等,系统开销较大; •有可能产生“抖动”; •地址变换机构复杂,为提高速度采用硬件实现,增加了机器成本。 4、段式、段页式存储管理 段式、页式存储管理的...

    电科大20秋《计算机操作系统》在线作业2.docx

    A:去查段表 B:去查快 C:发越界中断 D:发缺页中断 答案:C 系统抖动是指()。 A:使用机器时,屏幕闪烁的现象 B:系统盘有问题,至使系统不稳定的现象 C:由于内存分配不当,偶然造成内存不够的现象 D:被调出的页面又...

    内工大 计算机网络 试题2010-2011B

    4.在一请求分页系统中,假如一个作业的页面走向为:4、3、2、1、4、3、5、4、3、2、1、5,当分配给该作业的物理块数为4时(开始时没有装入页面),采用LRU页面淘汰算法将产生( 8 )次缺页中断。 5.信号量被广泛用于...

    FreeBSD操作系统设计与实现

    6.4.7 数据在内核中的转移 6.5 虚拟文件系统的接口 6.5.1 vnode的内容 6.5.2 对vnode的操作 6.5.3 路径名转换 6.5.4 文件系统的导出服务 6.6 与文件系统无关的服务 6.6.1 名字缓存 6.6.2 缓冲区管理 6.6.3 缓冲区...

Global site tag (gtag.js) - Google Analytics