Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3520747
  • 博文数量: 1805
  • 博客积分: 135
  • 博客等级: 入伍新兵
  • 技术积分: 3345
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-19 20:01
文章分类

全部博文(1805)

文章存档

2017年(19)

2016年(80)

2015年(341)

2014年(438)

2013年(349)

2012年(332)

2011年(248)

分类: LINUX

2013-12-14 00:41:36


Optimizing preemption

内核的基础操作每秒会被调用数千次,通常它们的性能已经被压榨得很好了,但随着代码的修改还是会引入些新的性能开销,而这些损失一般也只有在开发者仔细调查某个子系统时才能察觉到。最近关于内核抢占的优化就是一例:

User-space access and voluntary preemption

事情源自Andi Kleen决定对用户空间数据访问系列例程做些优化,他在结果补丁中讲到,这些功能已经在x86系统上优化得很好了,但它们调用的might_sleep()和might_fault()拖累了性能。开始时,这些函数只用于调试目的,所以在“生产内核”配置上它们不会生成实际代码。但自2004年之后,内核核心系统的开发者开始着手做低延迟优化,其中一个优化点就是如果有高优先级进程需要处理器,内核代码也可以被抢占。但内核代码并不是处处都是抢占安全的,于是Ingo Molnar和Arjan van de Ven提出,调用了might_sleep()的地方其实就是抢占安全的,因为这些调用点都是“可休眠”的,基于这些观察最后形成了主动抢占(voluntary preemption)功能,它为内核增加了一个受限的内核抢占模式,并且一直使用到现在。

Andi Kleen注意到,这个设计的问题是might_sleep()和might_fault()的功能已经深入到调度系统里,所以一定会生成代码,据测试,Andi说它们会使每次系统调用时间增加2.5?s。Andi的补丁包括一些小优化:某些函数(例如, might_sleep()所调用的should_resched()被标记为__always_inline以去掉函数调用开销;增加一个新函数might_fault_debug_only()实现了might_fault()的最初动机,等等。Linus并没有提出什么异议,但他还是抛出了一些疑问,其中一个观点:主动抢占的设计动机是可以在内核线程执行有昂贵开销的操作可以被切换出去,而copy_from_user()这些函数的开销并不大:如果复制成功性能也很快,如果发生缺页异常,那么抢占也有机会发生,所以这里是没有必要调用might_sleep()之类的检查函数的。

The problem with full preemption

到目前为上,讨论还只集中在“主动抢占模式”上,但内核已经支持了“完全抢占模式(full preemption)”,而且经过多年演化,这个功能已经相当成熟可靠了。H. Peter Anvin干脆地问,我们是不是应该废弃了“主动抢占模式”呢?但Mike Galbraith回答说:"PREEMPT munches throughput.",是啊,经常有人说抢占会损伤吞吐量。但从原理上没有什么直接原因会让完全抢占模块对吞吐量造成负面影响,而且在吞吐量敏感的场合抢占也应该是很罕见的情形。一定是某种副作用,其中一个线索,Linus认为是对preemption count的检查操作 — 在完全抢占内核中,这个检查会非常频繁,同时条件语句也使编译器生成更慢的代码。

Optimizing full preemption

问题的根源在于一个叫作"preemption count"的变量,它保存位于内核栈底部的thread_info结构中。它并不是一个简单计数器,它的32位被分成了几个字段:

  • 抢占计数,即内核关闭抢占的计数,行为上非常类似于“递归锁”的概念。(8位)
  • 软中断嵌套数量。(8位)
  • 硬是断嵌套数量。 (多数体系结构上10位)
  • PREEMPT_ACTIVE标志位,表示当前已经被抢占,或者刚刚被抢占。

只要以上任何一个字段非零,内核抢占都会被禁用掉。虽然可以使用一些汇编技巧降低这个测试的开销,但更麻烦的是这些数据保存在了thread_info结构里,而这个结构必须根据内核栈指针推算出来,这使得整个操作更加昂贵了。

一个重要的观察是这些计数器几乎都是与特定线程无关的:非运行状态的线程,这些计数器都为0,并且这些计数器非零时也根本不会发生内核抢占。所以,与其说它们是是任务的一种属性,还不如说是CPU的。这就是Peter Zijlstra的将它们变成per-CPU变量的补丁的“理论基础”,但PREEMPT_ACTIVE标志是个例外,这个标志的语义是单个任务级别的。Peter的第一个补丁并不能对问题缓解多少,原因是内核在检查一个任务能否被抢占时还需要检查thread_info中的另一个标志位TIF_NEED_RESCHED。除了preemption count为零时会检查这个标志,还有许多其它情形也如此。只要这个需要检查这个标志,完全抢占模式的额外运行开销就仍然存在。

Linus大神出招说,这个标志也是可以移动到per-CPU变量里的,可以会保存在per-CPU preemption count的高位里。但这个地方有个微妙的问题:这个标志位可能被其它处理器访问,这可能会破坏preemption count的其它字段。但Linus提供了一种方案避免了潜在问题:用原子操作访问need rescheduling标志位,而其它计数器字段则可以无锁更新。虽然这种方法可以防止对其它字段的破坏,但访问其它计数器字段仍可能破坏need rescheduling标志位,这就有些背离做这些优化的初衷了——可能导致一些抢占唤醒的丢失,造成较长的延迟。最后,Peter给出了一种目前看来最棒的方案:目前,如果内核设置了一个任务的TIF_NEED_RESCHED就会向有关CPU发送一个IPI中断,而Peter的补丁是在IPI处理例程中将thread_info的TIF_NEED_RESCHED标志复制到per-CPU preemption count中,因为这个标志的复制操作发生在拥有preemption count的处理器上,所以不会出现任何race conditions。

以上所有这些优化都还需要大量的review和性能测试,恐怕3.12之前是不在进入到upstream的,也许到那时,主动抢占就真的可以淘汰了。

阅读(491) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~