今年的linux内核开发大会上,google的开发人员也上台做了名为“how google use linux”的演讲。我斗胆翻译注解一番――括号内为注解,欢迎读者斧正。
原文链接参见:
(前面几段讲google对linux kernel代码的管理及跟进,偏细碎,不翻译了)
在google为linux加入的代码中,3/4是对内核核心的改动,设备驱动代码只是其中相对较小的一部分。
(linux发展到现在这个阶段,需要加入的新的设备驱动已经越来越少了)
如果google要与linux社区的合作开发,那将面临一系列问题。跟上linux代码的主干太难――它的代码更新的太快了。在一个大型项目里,开发者对补丁的提交、重改确实是个问题。Alan Cox对此的回答十分简单:人总是贪得无厌的,但有时候就应该简单的对他们说”不“。
(Alan Cox是linux kernel的二号功臣,现已加入Intel公司。我觉得Intel这样的CPU公司很适合内核开发者)
在CPU调度上,google发现想改用新的cfs(“完全公平调度”,由Con Kolivasy在2.6.23中加入内核)非常麻烦。由于太麻烦,google不得不倒回去把O(1) sheduler(2.6.23前内核使用的调度算法)移植到2.6.26上,一切才能运转起来。内核对sched_yield()语义的更改也造成了麻烦,尤其当google使用用户态锁时。高优先级的线程会对服务器的负载均衡(这里的负载均衡指的是一台服务器上对个CPU对多任务的分配处理,不是指分布式)造成影响,哪怕这些线程只是运行很短的时间。而负载均衡很重要:google通常在16-32核的服务器上跑5000个线程(好诡异的用法!)。
在内存管理上,新的linux内核改变了对脏数据的管理,导致出现了大量主动的写回操作(脏数据要写回硬盘)。系统很容易出现这种局面:kswaped(swap进程)会产生大量小的I/O操作,塞满块设备的请求队列,结果造成别的写回无法完成(写回“饥饿”);这个问题已经通过 “per-DBI写回机制”补丁在2.6.32内核中解决了。
(per-DBI的主要原理是块设备不再只有一个等待队列,而是多个,每个“硬盘轴”一个队列,因为硬盘轴是一个硬件上的真正的工作单位。这样,对装配多个硬盘的服务器会有很好的I/O性能。不过我个人猜测,如果能把kswaped的小请求合并,是否也能提高性能呢?)
如上所述,google在系统中启动很多现成――不寻常的用法。他们发现如果向一个大的线程组发信号,会造成运行队列锁的大量竞争。google 还发现mmap_sem信号量(是内核结构 struct mm_struct中用来保护mmap空间的内核信号量)有竞争问题;一个睡眠的读者会阻塞写者,而这个写者又阻塞了其他读者,最后造成系统死锁。内核应该修改,拿到信号量以后不要再等待I/O。
(google所说的信号对线程组造成的问题估计是“惊群效应”,就是很多任务睡在一个队列上,一个唤醒操作会造成他们都突然醒来,结果必然是资源拥挤。我个人认为这不是linux的问题,这是google使用linux的方法太奇特了,所以内核开发者没有注意到)
google大量使用了OOM killer来减轻高负载服务器的负担。这样做有一定的麻烦,当拥有锁的进程被OOM杀掉时(锁并不会释放,结果就阻塞了别的任务)。Mike(演讲人)很想知道为什么kernel费那么大劲搞出OOM killer来,而不是简单的在分配内存失败后返回一个错误。
(不光Mike,大家都有这样的疑问,估计答案只能在内核邮件组里找了。而google所说的那个“进程被杀锁却没释放、造成阻塞”的问题,yahoo在freebsd-4.11的时代就已经解决了,用了很巧很轻量级的办法。大家都觉得google的技术最牛,其实公正的说,牛公司牛人很多,只是大家没他那么高调而已。但对国内来说,能通过改进内核来提高服务器的公司,也真是凤毛麟角了。)
(此外略去一段google对内核开发工作的分类,看不太懂)
google增加了一种SCHED_GIDLE的调度类,是真正的空闲类;如果没有CPU供使用,属于此类的任务就彻底不运行(甚至不参与对 CPU的抢夺)。为了避免“优先级反转”问题,SCHED_GIDLE类的进程在睡眠时(此处指内核睡眠,不是系统调用sleep造成的睡眠)会临时提高优先级。网络由HTB排队规则管理,配有一组流量控制逻辑。对硬盘来说,它们按linux的I/O调度来工作。
(假设三个进程A、B、C,优先级为A>B>C,假设C先运行,占了一个重要的共享资源,A也想要这个资源,所以A等待C完成,但由于B的优先级比C高,结果C还没完成就调度到B运行了,这样总的来看,B的运行先于A,尽管A的优先级比B高。这就是“优先级反转”问题。通常的解决方法是:谁占了重要的共享资源,谁就临时提升自己的优先级,比如C占了资源后优先级临时升到和A一样高,释放资源后再把优先级降回来。说白了,占用资源的一伙人,他们最好有相同的优先级,不然会有麻烦)
除了这些,google还有很多代码用于监控。监控所有的硬盘和网络流量,记录之,用于后期对运维的分析。google在内核加了很多钩子,这样他们就能把所有的硬盘I/O情况返回给应用程序――包括异步的写回I/O。当Mike被问到他们是否使用跟踪点时,回答是“是的”,但是,自然的,google使用的是自己的一套跟踪方法。
google内核改进在2010年还有很多重要的目标:
google对CPU限制功能很兴奋,通过此功能,就能给“低延时任务”较高的优先级,而不必担心这些任务把霸占整个系统。
基于RPC的CPU任务调度,这包括监控RPC入口流量,以决定唤醒哪一个进程。(这很有分布式OS的味道)
延迟调度。对很多任务来说,延迟并不是什么大不了的事。但当RPC消息到来时,内核却尝试去运行所有这些任务;这些消息不会分布到不同的CPU上(意思就是处理这些请求的服务进程可能就在某几个CPU上运转),这造成了系统负载在CPU间分配的问题。所以任务应该能被标为“延迟调度”;当被唤醒后,这些任务并不会被直接放到运行队列上,而是等待,知道全局的CPU负载调度完成。
插入空闲周期。高级电源管理使google能够把服务器用到接近烧毁的边缘――但是不超过这个边缘。
更好的内存管理已经列入计划,包括统计内核的内存使用。
“离线内存”。Mike强调想买到便宜好用的内存是越来越难。所以google需要能够把坏内存标出的功能。HWPOISON兴许可以帮到他们。
在网络方面,google希望改进对“接收端缩放”的支持――即把输入流量导到指定的队列。google还需要统计软件中断次数然后转给指定的任务――网络进程通常包含大量的软中断。google已经在拥塞控制上做了很多工作;他们开发了“网络不安全”的拥塞算法,该算法在google的数据中心运转良好。名为“TCP pacing”的算法放慢了服务器的输出流量,避免了交换机过载。
(自己管理数据中心的公司就是不一样,网络优化做得很细)
在存储方面,google花了大量精力降低块设备层的瓶颈,这样就能更好的使用高速flash。在块设备层通过flash来提高存储效率已经列入了开发计划。google考虑在内核里增加flash转换层,但大家建议google还是把操作flash的逻辑直接放入文件系统层更好一些。
Mike最后总结了几个“感兴趣的问题”。其中一个是google希望把文件系统的元数据固定在内存里。目的是减少I/O请求的时间。从硬盘读取一个块的时间是已知的,但是如果元数据不在内存中,就会有不只一个I/O操作被执行(会有新的I/O操作用来读元数据)。这样就减慢了读文件的速度。google 目前对此问题的解决方案是直接从原始块设备读取数据到用户空间(估计是用O_DIRECT,然后自己在用户态管理元数据缓存,这样就不会使用系统的 cache),但以后不想再这么做了。
(不知道google所说的filesystem metadata到底指哪些数据,因为不同的文件系统,metadata也很不同,既然google说要把这些数据固定在内存里,估计应该不大,那自己缓存有什么不好?希望有机会可以问问Mike)
还有一个问题,使用fadvise会降低系统调用的性能。目前还不清楚问题的细节。
google的这个演讲很成功,linux社区也从自己最大的客户那里学到了不少东西。如果google更加面向linux社区的计划能够付诸行动,那linux将会拥有一个更好的kernel。
(注:google可算是IT公司中使用linux最多的,也很可能是使用得最深的,30个人的内核开发队伍,非常可观。看看国内,很少公司、很少人为开源做过贡献,唉,说起来惭愧,在下也是之一啊。)