linux内核的反复--一切都是过程

来源:互联网 发布:us and them 知乎 编辑:程序博客网 时间:2024/04/20 20:35

1. ZERO_PAGE

2.6.24内核中剔除了ZERO_PAGE这个鸡肋,然而近期又准备添加近来,这样做的原因还是在于当初它为何被当成了鸡肋,当成鸡肋的原因就是当zero页面加入反向映射的时候会更新其page结构体的引用计数,从而造成了固定cacheline的冲刷,仅仅这一点就将zero页面当成了鸡肋未免也太残酷了吧,所以2.6.32内核着手寻找更好的方法,为zero页面平反,也就是尽力去除它作为鸡肋的坏名声。具体实现方式就是更改了vm_normal_page函数,也就是说不再将zero页面当作normal页面,而是将它当作非正常页面,这样的话,在根据虚拟地址得到的page的时候,一旦得知它是非正常页面,比如zero页面,就返回NULL,而vm_normal_page的调用者往往是根据其返回值进行进一步操作的,所以比如要进行更新引用计数之类的操作,一旦查出vm_normal_page返回NULL,那么更新操作就不再进行,因此也就避免了由于page结构体所在的内存被更新而导致的cacheline被冲刷,从而解决了zero页面鸡肋的问题,获得的好处就是zero页面的高效性,不再需要从内存管理器中进行分配和回收,效率是很高的。去除了zero页面鸡肋的名声,那么它也就可以重返内核的mainline了,至于说zero页面的另一个副作用,那就是会导致额外的缺页,毕竟zero页面是写保护的,但是是否会因此导致额外的缺页,那就看用户空间程序了。

2. cpuidle-check

cpu在上面没有任务运行的时候会执行cpu_idle,如果是以前的话直接调用halt就可以了,但是现在就不是那么简单了,由于APM也好,ACPI也罢,都加入了节能机制,不能简单地halt,还要可以做到诸如在不同的级别停掉cpu,因此内核当然需要一整套的机制来实现之,这就是cpuidle机制,框架就是将idle函数(halt的包装)改成了回调函数,然后根据不同的“cpu现状”采取不同的行为,这么做有什么内涵呢?实际上停掉cpu并不总是意味着节能,因为“停止-启动”本身也是需要消耗能源的,特别是如果一个cpu仅仅停掉几个毫秒,这样是很不值得的,因为再次启动它需要的能源足以抵消掉这几个毫秒节省的能源,因此内核必须能够监测到这种情况,但是毕竟内核不是智能的,要预先监测到一个cpu能被停掉多久是不现实的,虽然可以用hrtimer来驱动cpu,比方说将下一个timer到期的时刻定为cpu被启动的时刻,可是多个cpu之间的交互和该cpu停掉之后的系统行为是完全不确定的,比如刚刚停止一个cpu的运行,瞬间一个用户创建了大量的进程,结果就是各个cpu负载情况瞬间改变,可能已经和之前内核预测的大相径庭,这就可能由于预测失误从而导致cpu被瞬间启动,在停止-启动之间消耗掉了大量能源,短短的睡眠时间根本节省不了多少能源而弥补这些浪费,虽然发生上面的情况是可悲的,但是如果内核因此无动于衷的话,岂不更可悲,虽然内核不能智能的预测,但是人可以吗?万能的人也是不行的,人所有的预测都是基于经验的,如果人将这种能力赋予操作系统的话,内河也还是可以根据经验来预测的,也可以说成是所谓的“启发式算法”具体来讲就是根据以往cpu的负载情况来猜测出下面的负载,虽然不能保证绝对准确,但是由于世界事件的局部性原理,有这种预测总比没有强,具体来讲就是如果一个cpu的历史一度负载很大,那么就尽量不要让它进入深度相对较深的睡眠状态(考虑到ACPI支持不同的睡眠深度),实际上的操作就是在idle回调函数中实现一个状态机,不同的状态代表不同的睡眠深度,状态机的next状态由cpu的历史负载确定,历史负载被划分为不同的区间,落入某一个区间的负载指示一个确定的下一个状态,这就是睡眠状态机推进的原理。事实证明,内核在变得复杂,连idle函数都回调化了

3. child-runs-first

曾经,我发了一个内核patch,就是要确保在单核cpu上CFS调度器下实现承诺的子进程优先执行,当初想到的是为了避免额外的不必要的写时复制,鉴于子进程一般都会瞬间调用exec函数系列,然而那已经是历史了,考虑到这个避免cow的同时,没有考虑到的是对cacheline的影响,问题是,让本来就处于运行态的父进程继续运行下去呢还是切换至新创建的子进程,如果切换的话,那么必然导致相关的cacheline被刷新,这也会影响效率,问题于是转化为到底一次cow对系统效率影响大还是cacheline被刷新对系统效率影响大,内核开发者的回答是后者,毕竟现在很多都是多核处理器,在创建新的进程的时候根据负载均衡策略要有多处理器负载均衡的相关操作,这样的话子进程就不一定和父进程在相同的处理器上运行,如果为了减少额外的cow硬要子进程优先运行的话,内核要做的工作就会很多很复杂,这样做有点得不偿失,事实上,子进程由于浅复制父进程的内存,如果在同一个处理器上运行的话会受益于cacheline,然而总会有更复杂的或者更重要的因素导致子进程被分配到别的处理器上,如此一来的话,就没有必要必须实现child-runs-first这个承诺了。新的内核为了充分利用父进程的hot cacheline,于是干脆干掉了child-runs-first,当然也不是完全干掉了,只是将这个内核选项默认设置成了false,如果你要将它再搬回去,也是可以的。

4. child不再继承父进程的实时优先级

这个论题是从一篇类似战斗檄文的文章中引出来的,文章的标题是《RealtimeKit and the audio problem》

这篇文章在抱怨为何linux没有更好地支持实时的音频,为了得到更好的音频质量不得不采取的行为是耦合其它的内核模块,比如利用LSM来支持更好的音频播放,但是这样的话就会引入不安全因素,当然可以使用实时优先级,比如RR和FIFO,可是如果一个进程可以随时简单的得到实时优先级的话,那么恶意的进程也应该可以做到,为了支持高质量音频播放而给与它实时优先级,但是如果一个进程伪装成音频播放进程的话,那么它瞬间就可以down掉系统,如此一来,文章中充满激情的问道,难道内核开发人员就不能用一种更和谐的方式应对音频播放的实时性了吗?其实音频播放仅仅是一个例子,很多别的需求也需要响应的实时性,另一方面安全是最重要的,它又不能被恶意利用,因此必然要采取一些措施,众所周知,UNIX系列系统的所有进程是一个树状模型,init是树根,其它所有的进程都是init的子孙,当然也继承了init的一些特性,优先级就是其中之一,子进程或多或少的继承父进程的优先级,如果一个恶意的进程得到了实时优先级,那么它的子进程就会得到实时优先级,如果该恶意进程拼命fork子进程的话,一个DOS攻击就完成了,这当然是需要避免的,于是新的内核补丁中就有人提出增加一个进程标识,就是reset标识,一个进程一旦有了这个标识,不管它是什么优先级执行什么调度策略,它所fork出来的子进程的优先级一律回归为平均优先级,调度策略一律不能是实时调度,这就避免了恶意进程使用fork炸弹。这个标识的加入并没有切断父子的关系,相反的,联系更加多了,父亲不再拥有将一切给与儿子的权力,儿子必要的时候必须为其父亲不被信任而负责,另一些时候,父亲必须支付一些遗产税。引入的这个机制避免了一些漏洞被利用,我认为这个机制很有必要,这样可以保证一些实时性要求高的应用可以放心使用RT调度策略而不再为安全问题担心。

5.杂

首先看看cfs调度器抢占粒度的微调对系统的影响(cfs没有时间片概念,虚拟时钟的时间片是随着系统中进程数量的变化动态调整的,而不像以前O(n)或者O(1)那样是静态不变的),仅举一例,如果你只运行很少几个服务器,那么可以增大这个粒度,这样一轮调度周期就会很长,进程切换相对不频繁,开销小,但是如果你运行桌面和多媒体应用,那就要调小这个粒度,进程切换频繁,交互性增强,同时开销也变大;再看一下用户空间驱动,其实并不像很多人想象的那样,用户空间驱动没有内核空间的效率高,事实是,用户空间的程序唯一的劣势就是系统调用开销比较大,至于别的和内核是一样的,要知道linux是完全按照进程来调度的,当然也会有中断这样的不速之客。用户空间驱动为了效率需要做两点:第一就是应用实时优先级;第二就是用mlock将内存锁入存储器,消掉页面置换的开销。

原创粉丝点击