追踪CPU跑满

最近测试一个应用遇到问题:一旦压力略涨,应用的CPU就顶满。由于是多线程应用,直接就把系统的CPU耗完了。
本来想用gdb來调试的,结果gdb不给力,就在attach那里卡死,半天不动。后来想到了用perf来调试,果然找到了一处性能热点。修复热点以后,CPU顶满的问题缓解了一些,不太容易出现了,但是,多跑一会儿,还是会有。而且现在出现CPU顶满时,不仅gdb不返回,连perf record -a -g都无法用Ctrl+c来停止了,仔细top命令看了一下,原来系统的sys是100%,usr几乎为0%,也就是说,是卡在内核里了,难怪perf不好使。
perf这样的神器都用不了了,还能怎么办?最后@coly提醒我:用 echo t > /proc/sysrq-trigger 把内核栈整个打出来。好办法,我试了一下,dmesg里一堆信息:

Apr 25 22:37:57 v3 kernel: sheep         R  running task        0 19157      1 0x10000080
Apr 25 22:37:57 v3 kernel: ffff8802b41c5418 0000000000000086 ffff88028761ecc0 ffffea0008b0f478
Apr 25 22:37:57 v3 kernel: ffffffff814ef33e ffff88046ff9b588 ffffea0008b0f478 ffffea0008b0f478
Apr 25 22:37:57 v3 kernel: ffff8803ec6ab0f8 ffff8802b41c5fd8 000000000000f4e8 ffff8803ec6ab100
Apr 25 22:37:57 v3 kernel: Call Trace:
Apr 25 22:37:57 v3 kernel: [] ? _spin_lock+0x1e/0x30
Apr 25 22:37:57 v3 kernel: [] ? __lru_cache_add+0x40/0x90
Apr 25 22:37:57 v3 kernel: [] __cond_resched+0x2a/0x40
Apr 25 22:37:57 v3 kernel: [] _cond_resched+0x30/0x40
Apr 25 22:37:57 v3 kernel: [] migrate_pages+0x9d/0x4b0
Apr 25 22:37:57 v3 kernel: [] ? compaction_alloc+0x0/0x3e0
Apr 25 22:37:57 v3 kernel: [] compact_zone+0x4f4/0x770
Apr 25 22:37:57 v3 kernel: [] compact_zone_order+0xa1/0xe0
Apr 25 22:37:57 v3 kernel: [] try_to_compact_pages+0x11c/0x190
Apr 25 22:37:57 v3 kernel: [] ? native_sched_clock+0x13/0x60
Apr 25 22:37:57 v3 kernel: [] __alloc_pages_nodemask+0x5f5/0x940
Apr 25 22:37:57 v3 kernel: [] alloc_pages_vma+0x9a/0x150
Apr 25 22:37:57 v3 kernel: [] do_huge_pmd_anonymous_page+0x145/0x370
Apr 25 22:37:57 v3 kernel: [] handle_mm_fault+0x25a/0x2b0
Apr 25 22:37:57 v3 kernel: [] ? mempool_alloc+0x63/0x140
Apr 25 22:37:57 v3 kernel: [] __get_user_pages+0x12a/0x430
Apr 25 22:37:57 v3 kernel: [] ? rwsem_down_read_failed+0x26/0x30
Apr 25 22:37:57 v3 kernel: [] get_user_pages+0x49/0x50
Apr 25 22:37:57 v3 kernel: [] get_user_pages_fast+0x157/0x1c0
......
Apr 25 22:37:57 v3 kernel: sheep         D ffff88047fc24900     0 19158      1 0x10000080
Apr 25 22:37:57 v3 kernel: ffff880357da9890 0000000000000086 0000000000000000 0000000000000003
Apr 25 22:37:57 v3 kernel: ffff8804700e6ea0 ffff880357da9878 ffffffff812518e2 ffff880357da0000
Apr 25 22:37:57 v3 kernel: ffff8803ec6aa6b8 ffff880357da9fd8 000000000000f4e8 ffff8803ec6aa6b8
Apr 25 22:37:57 v3 kernel: Call Trace:
Apr 25 22:37:57 v3 kernel: [] ? __make_request+0x122/0x5a0
Apr 25 22:37:57 v3 kernel: [] rwsem_down_failed_common+0x95/0x1d0
Apr 25 22:37:57 v3 kernel: [] ? mempool_alloc_slab+0x15/0x20
Apr 25 22:37:57 v3 kernel: [] ? mempool_alloc+0x63/0x140
Apr 25 22:37:57 v3 kernel: [] rwsem_down_read_failed+0x26/0x30
Apr 25 22:37:57 v3 kernel: [] call_rwsem_down_read_failed+0x14/0x30
Apr 25 22:37:57 v3 kernel: [] ? down_read+0x24/0x30
Apr 25 22:37:57 v3 kernel: [] get_user_pages_fast+0x124/0x1c0
......

看到了"lock"、“mutex”这些字眼,果然是内核里面卡住了,@coly一看到compact_zone()就明确告诉我,是hugepages的问题,把它关掉试试。用

echo never > /sys/kernel/mm/redhat_transparent_hugepage/enabled
echo never > /sys/kernel/mm/redhat_transparent_hugepage/defrag
echo no > /sys/kernel/mm/redhat_transparent_hugepage/khugepaged/defrag

关掉,应用果然一切正常了。原来以前在hadoop集群上也遇到过类似的问题(所以现在alikernel是默认关掉transparent hugepaes的),不过当时只是CPU略涨,不像现在这个,干脆锁死了。
原来除了gdb,valgrind,perf,还有“echo t > /proc/sysrq-trigger"这样更犀利的办法,@coly告诉我,还有更犀利的,就是连sysrq-trigger都不好使了,可以看
/proc/{pid}/wchan,里面是该进程阻塞位置的内核函数名,在所有办法都没戏的时候可以看它。
学习了。

LSF/MM Summit 2014

作者:刘峥

今年LSF/MM会议的内容lwn.net上已经有文章进行详细的介绍了。鉴于我本人分身乏术造成mm track基本没有听,同时会上讨论的有些问题本人理解也十分有限,所以推荐大家还是去lwn.net上看一下上面的文章。这里仅仅是总结我认为比较重要和关注的问题,希望通过这篇文章能让大家了解目前Linux Kernel在文件系统、存储和内存管理方面需要解决的问题。

这次的LSF/MM会议的议题基本可以分为三个大的部分:

  1. Linux Kernel目前存在的问题
  2. 来自业界和社区的分享
  3. 新技术带来的新问题

尽管1中的议题占了会议的大部分时间,但是个人感觉这次LSF/MM最重要的是2、3两部分。而且会对未来一段时间的内核开发带来较大的影响。

1. Linux Kernel目前存在的问题

这一部分包含了如下内容:

  1. Page Cache的相关问题
  2. 测试
  3. blk-mq的相关问题
  4. direct I/O

1) Page Cache的相关问题

这次LSF/MM上关于page cache的讨论有两个,一个是关于32位机器上大空间块设备寻址的历史遗留问题;另一个是支持大于4k内存页的page cache。

32位机器上对于大空间块设备寻址是一个历史遗留问题。这个问题说来很简单,32位机器的内存寻址范围是2^32=4GB,目前Linux Kernel常规内存页大小为4k,因此对于系统page cache来讲,最大可以支持的磁盘设备的容量为4GB x 4KB = 16TB。由于目前嵌入式设备和ARM服务器的广泛使用,出现了32位系统对于超过16TB大容量块设备使用的需求。对于这个问题大家的意见还是就不要解决了,如果用户真的想用,内核会给出信息提示用户这种使用方法的风险。

目前Linux Kernel其实已经有针对Anonymous Page的大页支持了。但是对于page cache,目前Linux Kernel仍然仅支持4k大小。调大page cache的好处是减少内存页管理的开销。目前看,page cache大页支持的关键仍然是防止内存的碎片化,让page cache支持大页并没有想象中那么困难。page的迁移和压缩功能在此前实现Anonymous Page大页时已经基本实现了。目前的问题主要是如何解决那些不能移动的内存页。尽管大家也提出了一些建议,但是似乎问题仍然比解决方法多。

此外,Dave Chinner还提到了让文件系统支持大于page size的block size。目前Linux下主流的Ext4、XFS、Btrfs文件系统的block size大小均为4k。Dave的想法是能否借助目前增大page cache的机会来突破这一限制。同时,这样带来的一个副作用就是buffer_head彻底退出历史舞台。在2.6内核中buffer_head作为IO请求的核心作用已经被bio所取代。但是,目前仍然有大量文件系统的代码,特别是文件系统的元数据信息需要使用buffer_head。buffer_head的使用给文件系统开发者带来了很多不必要的麻烦。问题其实都是老生常谈了,但是真想解决确实难度不小,由于buffer_head的使用涉及诸多文件系统,对它的任何修改都不容易。

最终的结论还是那句很简单的“show me the code”。各位可以关注fsdevel@ 和mm@ 邮件列表,最近会有补丁。

2) 测试

继去年xfstests讨论之后,今年Dave Chinner和Dave Jones继续讨论测试的话题。讨论从xfstests开始,说是讨论不如说是抱怨,很多人在抱怨xfstests目前存在的问题。这些大家直接去看lwn.net上的文章就好了。本人表示比较关心的是Dave Chinner对于目前Redhat内部xfstests对于XFS和Ext4的测试覆盖率的说明。目前Redhat内部对于XFS的测试覆盖率约为75%,而Ext4的测试覆盖率仅为65%。本人表示怪不得有那么多Ext4的bug需要我来修呢。。。其实在后面Ext4 Workshop上依然有关于测试相关的话题,Redhat当时简单说明了他们对于文件系统的测试情况。应该说测试的还是很全面的,所以大家放心。

而James Bottomley提出目前其实社区缺少性能回归测试。本人觉得这是切中要害,目前无论是文件系统还是内存管理的开发者,提交补丁前大部分仅做了功能性测试和压力测试,没有一个很好地性能回归测试对修改前后的性能进行比较。而这也造成了Linux Kernel不同版本之间存在较大性能差异的原因。

3) blk-mq的相关问题

blk-mq本身已经合并到upstream内核中了。这次讨论的是前不久Christoph Hellwig关于scsi-mq的补丁。目前计划是在3.16版本的时候开始合并scsi-mq的相关补丁。现在的问题是还存在一些性能退化的问题和驱动的支持问题。

4) direct I/O

direct I/O绝对是一个老生常谈的问题,Kent Overstreet今年继续讨论如何改进direct I/O。其实大家目前对于direct I/O改进的目标是明确的,即重构代码,不使用buffer_head结构,而是直接使用bio来提交IO请求,从而简化代码路径。今年讨论的是如何实现这一目标。Kent的想法是借鉴目前buffered I/O路径的实现,通过回调函数的方法让文件系统对应磁盘块的分配信息,然后由VFS负责刷新page cache。个人觉得direct I/O去掉buffer_head中最复杂的部分是get_block()这个回调函数。因为有buffer_head的存在,目前文件系统的元数据在从磁盘上读取之后是可以保存在内存中的,而换用bio之后,这些元数据信息要么每次从磁盘读取,要么需要单独的结构来进行保存。而目前文件系统的get_block()函数是同步的,这个性能开销是用户完全不能接受的。所以明显需要一个数据结构来保存曾经访问过的文件系统元数据。

2. 来自业界和社区的分享

这一部分包含了如下内容:

  1. 来自PostgreSQL社区的声音
  2. Facebook目前遇到的问题与挑战

1) 来自PostgreSQL社区的声音

这次请PostgreSQL的开发者来LSF/MM的目的就是听取应用开发对于目前内核开发的一些建议。事情的起因是PostgreSQL最近在新内核上出现的性能退化以及长久以来磁盘IO方面存在的问题。首先要说明的是PostgreSQL目前所有的IO操作都是buffered IO(我一直以为是高大上的direct IO)。这样就出现了PostgreSQL中目前解决的不是很好地问题。PostgreSQL目前使用的WAL会确保数据库事物的正确性。正常情况下,磁盘的带宽可以保证WAL写入的低延迟。但是当PostgreSQL开始回写脏数据的时候,由于大量的脏数据从PostgreSQL中拷贝到系统page cache中并sync写到磁盘,造成磁盘带宽短时间内被占满,从而影响PostgreSQL数据库整体的延迟。当然,大家提出的解决方法也有很多:ionice、cgroup等等,但是最终讨论没有结果。Ted希望PostgreSQL开发者能够出一个简单程序来复现问题,以便内核开发者复现问题。

第二个被讨论的问题是关于double buffering的问题。根源依然是buffered IO。由于PostgreSQL使用buffered IO,造成系统中内存的浪费,因此PostgreSQL开发者希望内核能够提供一个接口指明某个区域的page cache不再需要,从而可以减少内存开销。有人说明可以通过fadvise(2)+DONTNEED来完成。但是PostgreSQL的开发说这样会造成这些内存页被再次读回内存(按理说不应该啊)。

最后PostgreSQL讨论的内容是最近新内核上遇到的性能退化问题。主要是透明大页、NUMA等。但是大家讨论并没有什么结论性的东西。

2) Facebook目前遇到的问题与挑战

Chris首先介绍了一下目前Facebook内核部署情况。其中绝大多数是2.6.38内核;少量是3.2 stable + 250 patches;极少量3.8 stable + 60 patches。在backport的补丁中绝大多数是网络和内核调试相关的补丁。

对于目前Facebook遇到的问题主要在如下几个方面:

  1. 数据库(buffered IO延迟、spinlock抢占、细粒度IO优先级)
  2. Web日志(buffered IO限速)
  3. 内存回收和压缩(高阶内存分配的延迟过大、内存回收策略)
  4. 监控(某些时候仅仅靠dmesg不管用)

Chris指出在Facebook内部最常见的两个问题是Stable page和CFQ。提到这两个本人真是感同身受啊,所以目前阿里内核已经将stable page的补丁revert掉了(Google也这么干了^ ^)。

其实Facebook家尽管也是互联网公司,但是与Google各种高大上的分布式系统不同,构筑起Facebook的基石依然是MySQL数据库,所以数据库的性能对Facebook来讲是十分重要的。数据库这里提到的三个问题在阿里也是存在的,我们的数据库也会碰到buffered IO延迟高甚至某些情况下会将内核hang住的情况。spinlock的抢占对于本人更是印象深刻,去年双十一前压力测试就遇到过类似的问题。

对于buffered IO的延迟问题。其实与PostgreSQL遇到的问题类似,依然是回写数据过多造成磁盘带宽的短时间占满。这块的意见是让writeback线程具备ionice的功能,或者至少可以指定IO优先级,避免回写过多。同时Chris提到Josef已经修改了btrfs来限制buffered IO的速度(看来Facebook已经开始全线换用btrfs了)。

高阶内存分配延迟大、内存回收策略造成某些应用性能差这都是Linux Kernel多年来的老问题了。对于一个通用操作系统来讲,内存回收策略的修改很容易使某些应用性能提升的同时造成某些应用程序的性能退化。

可以说,互联网公司遇到的真的是有很多共通的问题。比如buffered IO延迟和回写带宽控制、内存回收策略、内存回收延迟等等。

3. 新技术带来的新问题

这一部分包含了如下内容:

  1. SMR硬盘的相关讨论
  2. Persistent Memory的相关讨论

1) SMR硬盘的相关讨论

SMR硬盘是未来传统机械硬盘发展的新方向。目前SMR硬盘的产品市面上已经可见。这种硬盘由于内部物理结构的改变,相比传统磁盘,SMR硬盘的随机写性能更差(传统硬盘已经很渣了)。所以为了规避SMR硬盘在随机写操作性能上的问题,目前由WD主推了一种新的SCSI协议ZBC。即通过主机和应用程序来主动控制数据写入的位置,从而避免因随机写引起的性能开销。关于SMR硬盘的具体介绍,请看此处(https://www.cs.cmu.edu/~garth/papers/05_feldman_022-030_final.pdf)。

LSF/MM上有两个议题是关于SMR硬盘的。其中一个是讨论目前的ZBC协议。这个其实没有什么特别需要说明的,只是协议各种细节的讨论...。另外一个议题则值得详细说明。即Ted Ts'o和Dave Chinner主持的关于SMR硬盘在文件系统上优化的议题。Dave Chinner给了一个突破常规的想法。既然目前所有文件系统都需要针对SMR硬盘进行优化,那么是否可以考虑将目前的文件系统进行分层。即分为namespace layer和block allocation layer。在namespace layer中,每个文件系统有自己的磁盘格式。然后通过统一的接口向block allocation layer请求分配磁盘空间。而block allocation layer则可以实现为不同的dm设备。针对不同的设备,实现不同的磁盘块分配算法,实现对不同设备的优化。比如dm-smr、dm-disk、dm-flash、dm-thinp等等。此想法确实新颖,但是问题也不少,首先就是开销问题。特别是对于互联网应用。所有应用程序都希望文件系统尽可能的薄,不要引入不必要的开销。而Dave的想法平白引入了一层,开销必然要增大。其次实现起来也过于复杂,需要为不同设备实现一个算法。最后,此前每个文件系统已经有自己的磁盘块分配算法了,而且可以针对每个文件系统的特点进行有针对性的优化。分开之后是否还能够有非常好的优化效果,目前还难以评估。因此Ted其实对这个想法的可行性持怀疑态度。Dave Chinner的想法可能预示着未来一段时间文件系统开发的一个新的方向。

2) Persistent Memory的相关讨论

对于Persistent Memory的支持本身社区已经有补丁了,并且最近就会被接收。这次主要讨论目前对于Persistent Memory支持中存在的一些问题。Intel的Matthew Wilcox提到了下面的一些问题。首先是msync。目前如果应用程序调用msync,尽管内存地址和长度都会作为参数传递进Linux Kernel。但是内核目前是根本没有处理的,sync的时候直接调用vfs_sync()把整个文件一刷完事。这个修改本身当然并不复杂,但是由于修改了已有API的行为,所以用户程序能否接受才是核心问题。第二个问题是关于mmap(2)的MAP_FIXED。这个标记会要求mmap在指定位置进行映射,但是副作用是映射区域已有的映射会被释放掉。于是Matthew想定义一个新的标记MAP_WEAK来避免这个副作用。当然还有其他一些问题。总之比SMR硬盘遇到的问题小,而且目前看开发进度也很好。

tpps: 一个可做cgroup资源隔离的高效IO调度器

(本文里说的“资源隔离”主要是指cgroup根据blkio.weight的值来按比例调配io的带宽和IOPS,不包括io-throttle即blkio.throttle.xxx的一系列配置,因为linux的io-throttle机制不依赖于IO调度器)

由于现在的SSD越来越快也越来越便宜,如何高效的利用SSD就变得十分重要。高效利用SSD有两个办法,一个是加大应用程序发出的io深度(比如用aio),对于无法加大io深度的一些重要应用(比如mysql数据库),则可以用第二个办法:在一个高速块设备上跑多个应用实例。
现在问题来了,既然跑多个实例,就要避免它们互相干扰,比如一个mysql由于压力大把io带宽都占了,这对另一个mysql实例就不太公平。现在常用的办法当然是用cgroup来做io资源的隔离(参考这篇),但是,有个尴尬的事情,目前只有cfq这一个io调度器是支持cgroup的,而cfq调度器在高速设备上表现却不尽人意。
deadline调度器准备了两个队列,一个用来处理写请求,一个用来处理读请求,然后根据io快要到期的时间(即deadline)来选择哪个io该发出去了,这个选择同时也要考虑“读比写优先”、“尽可能连续发射io”等约束,但是不管怎么说,既然把io的响应时间做为了该调度算法的第一要素,那deadline调度器的io延时性就是相对最好的(即response time很短),它也是SSD设备的推荐调度器。问题在于,“两个队列”,这结构太简单了,自然也无法支持cgroup这样复杂的特性,所以,虽然deadline调度器很快,RT很短,但是做不了资源隔离。
再来看cfq,cfq最早就是基于磁盘来做优化的,它的算法尽可能保证io的连续性,而不保证io的及时响应(所以,在对磁盘做高io压测的时候,cfq调度器有时会制造出长达几秒的io响应来),比如,对于单个io,它会做一个小小的等待,看有没有和这个io连上的下一个io到来,如果有,可以一起发出去,以形成连续的io流,但是,这个”小小的等待“,就大大延长了io响应时间。cfq调度器为cgroup中的每个group单独创建一个"struct cfq_queue“实例,然后以各group的weight所占的权重来决定改发哪个group的io,所以cfq是支持资源隔离的。我讲的这些还没有碰到cfq的皮毛,它四千多行的代码有很多复杂和技巧性的算法和数据结构,我估计只有fusionio的李少华能把它说清楚,我这里就不班门弄斧了。
介绍IO调度器的文章,可参考这篇

总的来说,deadline适合SSD设备,但是它不支持资源隔离;cfq支持资源隔离,但是在SSD上跑延时又太差,且代码复杂,不易改造。(noop铁定不支持资源隔离,而anticipator跟deadline代码相差不大,这两个都出局了)最终,经过我、高阳伯瑜三个人的讨论,得出决定:如果要方便应用方的使用,最好是做一个新的、代码非常简单的新IO调度器。这样,应用方如果是用硬盘,那就选择cfq;如果是用SSD,就选择deadline;如果既用了高速设备,又想做资源隔离,那就选这个新调度器。既方便应用方灵活选择,又方便我们自己维护。(如果把新调度器做进deadline,后期的维护和backport就会越来越吃力)。新调度器的原理很简单:通过类似cfq的xxx_queue结构来记录各group的信息,针对每个group都创建一个list,存放从该group到来的io,然后在dispatch io时,用该设备的io request容量(即nr_request)减去已经发出但还没有处理完成的io数(即rq_in_driver),得出的就是该设备还可处理的io数(即下面代码中的quota),然后根据这个“可处理io数”和各group的权重,算出各group的list上可以dispatch的io数,最后,就是按照这些数去list上取io,把它们发出去了。

static int tpps_dispatch_requests(struct request_queue *q, int force)
{
        struct tpps_data *tppd = q->elevator->elevator_data;
        struct tpps_group *tppg, *group_n;
        struct tpps_queue *tppq;
        struct list_head *next;
        int count = 0, total = 0, ret;
        int quota, grp_quota;

        if (unlikely(force))
                return tpps_forced_dispatch(tppd);

        if (!tppd->total_weight)
                return 0;

        quota = q->nr_requests - tppd->rq_in_driver;
        if (quota < MIN_DISPATCH_RQ)
                return 0;

        list_for_each_entry_safe(tppg, group_n, &tppd->group_list, tppd_node) {
                if (!tppg->nr_tppq)
                        continue;
                tpps_update_group_weight(tppg);
                grp_quota = (quota * tppg->weight / tppd->total_weight) -
                                tppg->rq_in_driver;
......

这样,就是按cgroup的权重比例来发出io了,而且,不像cfq一个队列一个队列的处理,这个新调度器是拿到quota后从每个group list里都取一定量的io,所以也还保证了一定的“公平性”,同时,quota是按照设备的处理能力算出来的,所以也能保证尽可能把设备打满。简单、按比例并发处理(准确的说,是公平处理),所以我们给这个新io调度器起名字叫“Tiny Parallel Proportion Scheduler",简称tpps。代码已经合并到了ali_kernel,patch链接是1,2,3,4,5,6

另外申明一下,这个调度器并不复杂,也不高端,讲求的是简单好用,所以如果有哪位内核高手看到这个调度器后发表不满:“这么简单也好意思拿出来说?!”,那大可不必。我们就是做出来解决问题的,问题解决就ok,不必做得那么高深。我写这篇文章也只是方便有兴趣的DBA或者SA来试用和提建议,不是要说明这个调度器有多牛逼。

想要使用tpps调度器需要使用ali_kernel:

git clone git@github.com:alibaba/ali_kernel.git
git checkout dev
#编译并重启内核

机器重启后

modprobe tpps-iosched
#再用 cat /sys/block/sdX/queue/scheduler 可以看到多个io调度器,其中有tpps
echo tpps > /sys/block/sdX/queue/scheduler

我用一个快速设备测试了一下cfq和tpps对cgroup的支持,fio测试脚本如下:

[global]
direct=1
ioengine=libaio
runtime=20
bs=4k
rw=randwrite
iodepth=1024
filename=/dev/sdX
numjobs=4

[test1]
cgroup=test1
cgroup_weight=1000

[test2]
cgroup=test2
cgroup_weight=800

[test3]
cgroup=test3
cgroup_weight=600

[test4]
cgroup=test4
cgroup_weight=400

测试结果:

各个cgroup的IOPS

io调度器 test1 test2 test3 test4
cfq 6311 5334 3983 2569
tpps 8592 8141 7152 5767

各个cgroup的平均RT(Response Time)单位:毫秒

io调度器 test1 test2 test3 test4
cfq 161 221 241 398
tpps 114 127 141 177

各个cgroup的最大RT(Response Time)单位:毫秒

io调度器 test1 test2 test3 test4
cfq 312 387 445 741
tpps 173 262 260 322

从测试看来,tpps既能实现资源隔离,同样的设备和io压力下性能也略高于cfq。
重要提示:对tpps来说,只有当设备被压满时才会有不同cgroup的不同iops比例出现,所以,测试时压力一定要大。

linux默认kernel.pid_max值

今早石祤同学发现了一个问题:同样的两台服务器,相同的OS版本、内核版本、CPU型号、CPU核数,只是厂家不同,但是机器启动后sysctl里的kernel.pid_max值,一台是128k,一台是32k。看了一下/etc/sysctl.conf,两台都没在配置文件里做更改,应该是内核自己选定的默认值。那内核到底是怎样选定这个默认值的呢?为何两个厂家的服务器默认值就不同?怎么让它们一致?

看了一下内核代码,决定kernel.pid_max的值是在pidmap_init()函数里:

int pid_max = PID_MAX_DEFAULT;
......
void __init pidmap_init(void)
{       
        /* bump default and minimum pid_max based on number of cpus */
        pid_max = min(pid_max_max, max_t(int, pid_max, 
                                PIDS_PER_CPU_DEFAULT * num_possible_cpus()));
......

其中,PIDS_PER_CPU_DEFAULT值为1024,也就是,内核基本上认为一个CPU核差不多最大跑1024个task,至于num_possible_cpus(),是通过计算cpu_possible_mask

const struct cpumask *const cpu_possible_mask

这个结构里被置为1的bit数来确定possible cpus的,即可用的最高CPU核数。possible cpu这个概念是为热插拔CPU准备的,比如,一台机器一共可以插24个CPU核,但是目前只插了12个,那么possible cpu核数应该是24,pid_max应该为“没插的cpu核“做预备,应该是24k。但是pid_max的默认值PID_MAX_DEFAULT是32k,比24k大,所以按代码,应该是选32k为值。

那是谁设置了cpu_possible_mask里的这些bit呢?再看看内核启动的函数:

asmlinkage void __init start_kernel(void)                                     
{                                     
        char * command_line;
        extern struct kernel_param __start___param[], __stop___param[];
......
        setup_arch(&command_line);
......
        pidmap_init();
......
}

里面是在setup_arch()里做了一些bit设置的事情:

setup_arch() --> prefill_possible_map()

__init void prefill_possible_map(void)                       
{      
        int i, possible;
                                      
        /* no processor from mptable or madt */
        if (!num_processors)
                num_processors = 1;
                                                
        printk(KERN_INFO "num_processors: %u, disabled_cpus: %u",
                          num_processors, disabled_cpus);
        if (setup_possible_cpus == -1)
                possible = num_processors + disabled_cpus;       
        else                                             
                possible = setup_possible_cpus;
                                                          
        total_cpus = max_t(int, possible, num_processors + disabled_cpus);
                                               
        /* nr_cpu_ids could be reduced via nr_cpus= */
        if (possible > nr_cpu_ids) {                                      
                printk(KERN_WARNING
                        "%d Processors exceeds NR_CPUS limit of %d\n",
                        possible, nr_cpu_ids);
                possible = nr_cpu_ids;
        }                                                             
                                              
        printk(KERN_INFO "SMP: Allowing %d CPUs, %d hotplug CPUs\n",
                possible, max_t(int, possible - num_processors, 0));

        for (i = 0; i < possible; i++)                              
                set_cpu_possible(i, true);                          

        nr_cpu_ids = possible;        
}                                         

setup_possible_cpus的值默认是-1,所以是根据ACPI驱动返回的num_processors和disabled_cpus的和来确定possible cpu数的。不同厂商的ACPI返回的disabled_cpus是不同的,所以possible cpu核数不同,自然kernel.pid_max值也不同。在系统启动的日志里可以看disabled cpu的不同:

bash# dmesg|grep Allowing
[    0.000000] SMP: Allowing 24 CPUs, 8 hotplug CPUs

说明当前CPU核数为16(24减去8),disabled的CPU核数为8。
那,怎么解决这个问题呢?怎样统一所有机型的默认kernel.pid_max值呢?还是得注意setup_possible_cpus这个值,这个值是可以通过grub来改的,只要在grub命令里kernel那一行的后面加上 possible_cpus=128 就可以把possible cpu数都统一成128了,参考这篇文档。当然,其实最方便的还是改/etc/sysctl.conf。

感谢石祤同学的细心,我又学到了很多。

旧文件干扰

昨天直接从git源上update到最新upstream内核版本,编译的内核却无法启动。一开始我以为是.config文件不对,于是试了各种方法,make localmodconfig,甚至make allmodconfig,居然还是起不来。于是在grub里加了个" vga=771 ",看看启动失败的具体报错信息,发现里面有句:

kernel tool old

神奇,这3.12的内核还老啊,在google上找了一下,还真有人遇到同样的报错,文章, 不过他是在arm上编译,gcc版本和内核版本差异很大,我这是rhel6,不应该有这么严重的问题,如果rhel6上都启动不了upstream内核,那应该很多人会发现的。
折腾了一圈,还是仔细看看失败的报错信息吧,这次才看到,除了“kernel too old”,居然还有

Not tained 2.6.19-rc79

莫名奇妙,明明是3.12内核,怎么说是2.6.19?这应该就是失败的原因了,于是沿着dump_stack()函数一路找回去,看到了这个

struct uts_namespace init_uts_ns = {
        .kref = {
                .refcount       = ATOMIC_INIT(2),
        },     
        .name = {
                .sysname        = UTS_SYSNAME,
                .nodename       = UTS_NODENAME,
                .release        = UTS_RELEASE,
                .version        = UTS_VERSION,
                .machine        = UTS_MACHINE,
                .domainname     = UTS_DOMAINNAME,
        },
        .user_ns = &init_user_ns,
        .proc_inum = PROC_UTS_INIT_INO,
};
EXPORT_SYMBOL_GPL(init_uts_ns);

原来打印的2.6.19-rc79是从UTS_RELEASE和UTS_VERSION宏来的,一找UTS_RELEASE在哪里定义的,发现居然有三个头文件,其中一个include/linux/version.h里是2.6.19,但是git一看,这个文件根本不在git库里,应该是某次我checkout到旧版本残留下来的......就是它干扰了启动过程。看来编译内核前,除了make clean以外,还要把不在git里的文件删掉,免得干扰正常编译。

1 2 3 7