memory.usage_in_bytes内存只增不减

三月 12, 2015
cgroup, filesystem
No Comments »

今天发现了一个问题,似乎memory.usage_in_bytes的内存总是在不停的增加,但很少减少,并不能说不减,只是很少。即使这个cgroup里的进程全kill了,usage_in_bytes依然纹丝不动。

经过分析之后,原因如下,与memory.usage_in_bytes的统计方式有关:

  1. 内核在统计的时候,usage_in_bytes包含进程的命名页 + 匿名页,匿名页通常就是file cache
  2. file cache是谁第一次使用算到谁的头上。
  3. file cache只有在完完全全被释放掉之后才会从usage_in_bytes中减掉,即使第一次使用它的进程早已死亡。

简单来说,就是我(某进程)读文件产生的file cache你(内核)算到我的头上,我退出了你也不清空我的计数,万一这个文件正好又被别的cgroup的进程依赖了,这笔帐还是赖在我的头上。。(内核虽然这么做,但其实是不合理的)

1、charge(计入)

file cache的charge函数是mem_cgroup_cache_charge(),这个函数通常是在文件页第一次被读上来时,内核将其加到file cache中时被调用,贴一个常见的调用栈:

  [<ffffffff8116019d>] ? mem_cgroup_cache_charge+0xed/0x130
  [<ffffffff8110bdba>] ? add_to_page_cache_locked+0xea/0x160
  [<ffffffff8119c232>] mpage_readpages+0x102/0x150
  [<ffffffff8120157d>] ext4_readpages+0x1d/0x20
  [<ffffffff8110c521>] do_generic_file_read.clone.23+0x271/0x450
  [<ffffffff8110d1ba>] generic_file_aio_read+0x1ca/0x240
  [<ffffffff81164c82>] do_sync_read+0xd2/0x110
  [<ffffffff81165463>] vfs_read+0xc3/0x180
  [<ffffffff81165571>] sys_read+0x51/0x90

page cache的维护是独立于进程的,所以即使cgroup里的进程退出后,其所使用的page cache不会马上被释放。

2、uncharge(计出)

file cache的计出函数是 mem_cgroup_uncharge_cache_page(),uncharge主要有三种情况:

  1. 手动触发内核回收file cache时被调用
  2. 文件被删除
  3. 内核主动回收file cache时被调用。

手动触发回收file cache,通过sysctl修改vm.drop_caches的值来实现,调用栈如下:

[<ffffffff81114bae>] __mem_cgroup_uncharge_common+0x1ab/0x1f6
[<ffffffff81114c09>] mem_cgroup_uncharge_cache_page+0x10/0x12
[<ffffffff810e0ad0>] __remove_mapping+0xd0/0xf4
[<ffffffff810e0b0a>] remove_mapping+0x16/0x2f
[<ffffffff810dfb75>] invalidate_inode_page+0x84/0x8d
[<ffffffff810dfc0c>] invalidate_mapping_pages+0x8e/0x114
[<ffffffff81138853>] drop_pagecache_sb+0x7f/0xd4
[<ffffffff811387d4>] ? drop_pagecache_sb+0x0/0xd4
[<ffffffff8111c253>] iterate_supers+0x77/0xc0
[<ffffffff811387ac>] drop_caches_sysctl_handler+0x30/0x58
[<ffffffff8116aa9c>] proc_sys_call_handler+0x90/0xb6
[<ffffffff8116aad6>] proc_sys_write+0x14/0x16
[<ffffffff8111ae72>] vfs_write+0xb0/0x10a
[<ffffffff8111af9a>] sys_write+0x4c/0x75

另一种情况就是内核主动回收file cache,通常就是内存资源紧张的时候进行。这个改天单独有空整理一下。

cpu带宽控制

三月 11, 2015
cgroup
No Comments »

一些资料

  1. https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt
  2. CPU bandwidth control for CFS

cpu带宽控制的原理是在给定周期内(cpu.cfs_period_us)内限制进程组的运行时间(cpu.cfs_quota_us),这两个参数值通过cpu子系统来设置。

cfs_rq相关的两个成员是runtime_remainingruntime_expires,前者是当前进程组的剩余时间片,后者是时间片的到期时间。

内核的做法是:

  1. 每次进程切换的时候更新当前进程的运行时间,检查进程时钟带宽是否超过阈值。
  2. 通过一个定时器,每隔固定的cpu.cfs_period_us周期,更新进程组的时钟带宽

Read the rest of this entry »

扩展net_cls子系统实现端口隔离

二月 28, 2015
cgroup
No Comments »

端口隔离的目的是要严格隔离不同容器所使用的端口段。我们通过扩展内核net_cls模块来达到此目的

  1. bind端口隔离:限制bind使用的端口范围,通过net_cls.bind_port_range配置文件设置
  2. connect端口隔离:限制进程能connect的端口范围,通过net_cls.connect_port_range配置文件设置

其中bind_port_range和connect_port_range配置文件的接口定义如下:

static struct cftype ss_files[] = {
  {
    .name = "bind_port_range",
    .read_seq_string = port_range_read,
    .write_string = port_range_write,
    .max_write_len = 20U,
    .private = FILE_BIND,
  },
  {
    .name = "connect_port_range",
    .read_seq_string = port_range_read,
    .write_string = port_range_write,
    .max_write_len = 20U,
    .private = FILE_CONNECT,
  },
  //...
}

通过port_range_read/port_range_write函数来实现配置参数的读写。我们重点关注cgrp_local_port_range()函数,这个函数取得当前进程所在的cgroup能使用的端口范围段,区分于是否是bind/connect:

/**
 * cgrp_local_port_range  -  get available local port range
 *
 * @connect:  select port range
 */
int cgrp_local_port_range(int *low, int *high, int connect)
{
  struct port_range_st *pr;

  rcu_read_lock();
  pr = &task_cls_state(current)->port_range;
  rcu_read_unlock();

  if (connect) {
    *low = pr->conn_low_port;
    *high = pr->conn_high_port;
  } else {
    *low = pr->bind_low_port;
    *high = pr->bind_high_port;
  }

  return 0;
}

Read the rest of this entry »

编写可读代码的艺术

二月 24, 2015
Programming practices
No Comments »

可读性的基本定理:代码的写法应当使别人理解它所需的时间最小化

Read the rest of this entry »

cgroup内核实现

二月 20, 2015
cgroup
No Comments »

cgroup模块里比较重要的数据结构是:struct cgroup, struct css_set, struct cg_cgroup_link,搞清楚这三个数据结构之间的关系,基本就可以了解内核是怎么管理cgroup的了。关于cgroup的一些资料,可以参考redhat写的介绍:

>> Relationships Between Subsystems, Hierarchies, Control Groups and Tasks

Read the rest of this entry »

hotfix内核热更新技术

二月 2, 2015
Kernel
No Comments »

最近研究了一下内核热升级技术,顺便和组内的同学做了一个分享。

https://github.com/pipul/coutune/blob/master/papers/kpatch.pdf

参考资料如下:

  1. 阿里虚拟化技术 http://pan.baidu.com/s/1eQcSPTw
  2. Safe and Automatic Live Update for Operating Systems  https://www.cs.vu.nl/~giuffrida/papers/asplos-2013.pdf
  3. LUCOS http://pan.baidu.com/s/1kTmSd4F
  4. ksplice http://www.ksplice.com/doc/ksplice.pdf
  5. 淘宝内核月报 http://kernel.taobao.org/index.php/%E5%86%85%E6%A0%B8%E6%9C%88%E6%8A%A52014-05-06
  6. kgraft http://lwn.net/Articles/596776/
  7. kpatch https://github.com/dynup/kpatch

memory allocator and coredump analysis

十二月 22, 2014
Performance Analysis and Tools
No Comments »

这几天彻底被一个线上BUG搞跨了,我们准备上线一个新的模块,灰度的时候发现系统极其不稳定,几乎每过几十分钟就core一次,而且coredump的地方比较有规律。现在来回顾一下这个core的分析过程

coredump现象

我们上线的流程是 稳定性测试 + 小流量灰度 + 全量上线
稳定性测试的机器环境是centos 6,线上自己的环境是 centos 5.4,稳定性测试的时候从线上拷贝流量,启动所有模块,持续跑了两天没有发现问题,然后接着小流量灰度。(后来的事实说明,环境也是导致coredump触发的一个很重要因素)

小流量灰度的时候问题出现了,时不时的core一下,core的地方比较稳定,每次都差不多都是那几个地方。既然core的比较稳定,说明应该是附近的代码出现了问题。

信息收集

从coredump的栈帧信息来看,现在能确定的是:

  • core在stl内部,说明不是明显的NULL引用等引发的coredump
  • 用户态极少使用malloc/free等来分配释放内存
  • coredump比较稳定,附近代码有问题的可能性比较大

静态代码分析

由于初步分析coredump处附近

  • 分析coredump代码附近有可疑的地方,例如数组越界,多线程race condition,不正确的malloc/free等等,没有发现问题。
  • 分析新上线的代码,例如有可能引入或者触发coredump的地方,没有发现问题。
  • 检查线程安全,race condition的地方,例如多线程读写map等

排查

初步分析没有发现明显的问题后,我们开始一步一步的排查和定位问题。通过配置线上可运行的模块,定位到其中某个模块可能有问题。由于静态代码分析的
时候看过这部分代码,很难定位到出问题的地方。加上在排查过程中即使卸载可疑的模块,仍然没法彻底解决问题,只是降低了coredump之间的时间间隔

coredump很可能不是在crash的地方附近,而是在之前别的地方就已经被破坏掉了。

Valgrind

为了证实这个问题,我们对所有模块都做了一次长时间的Valgrind测试,最终发现一个已经长时间运行的模块存在一个BUG,这个BUG非常难捕捉到,主要是由于一个Json的不正确使用导致的,走了一条很少有的执行路径,触发了此BUG

这个问题为什么之前没有爆发出来呢?double
free这种错误会由于内存管理器的实现不用而表现出不稳定性,原因是假设你轻微地破坏了一块内存,而且被破坏的地方不影响别的内存块,那么如果这块内存
释放后并没有被重复利用,这个BUG就难以复现出来。所以我们看到coredump的地方未必是问题发现的地方。

笛卡尔 - 谈谈方法

十月 7, 2014
Life
No Comments »

追求真理的一些基本法则和行为规范

法则

  1. 凡是我没有明确地认识到的东西,我绝不把它当成真的接受
  2. 把我所审查的每一个难题按照可能的和必要的程度分成若干部分,以便一一妥为解决
  3. 按次序进行我的思考,从最简单、最容易认识的对象开始,一点一点逐步上升,直到认识最复杂的对象;就连那些本来没有先后关系的东西,也给他们设定一个次序
  4. 在任何情况之下,都要尽量全面的考察,尽量普遍地复查,做到确信毫无遗漏

行为规范

  1. 服从我国的法律和习俗,笃守我靠神保佑从小就领受的宗教,在其他一切事情上以周围最明智的人为榜样,尊奉他们在实践上一致接受的那些最合乎中道、最不走极端的意见,来约束自己
  2. 在行动上尽可能坚定果断,一旦选定某种看法,哪怕它十分可疑,也毫不动摇地坚决遵循,就像它十分可靠一样
  3. 永远只求克服自己,不求克服命运,只求改变自己的愿望,不求改变世界的秩序

proxyio性能测试工具

八月 13, 2014
proxyio
No Comments »

测试工具位于源码目录 perf/ 下,可见:https://github.com/proxyio/xio/tree/master/perf

分别是吞吐量测试和时延测试:

  • 吞吐量测试:thr_sender + thr_recver
  • 时延测试:lat_sender + lat_recver

吞吐量测试

thr_sender不停的发送消息,thr_recver接受消息,记录消息大小,整个过程的耗时,最好得到的结果类似如下:

message size: 100000000 [B]
throughput: 90497 [msg/s]
throughput: 707.013 [Mb/s]

thr_sender用法如下:

usage: thr_sender <connect-to> <msg-size> <msg-count>

其中:

  1. connect-to 表示消息接收端的地址
  2. msg-size 表示发送消息的大小(不包括协议的header,仅指用户消息的长度)
  3. msg-count 表示消息数量

thr_recver用法如下:

usage: thr_recver <bind-to> <msg-count>

其中:

  1. bind-to 表示监听一个socket地址
  2. msg-count 表示消息的数量

 

时延测试

在pingpong模式下,测试每个消息来回的传输时间,测试过程如下,记录时间戳,发送消息,接受响应,记录时间戳,计算时延 rtt / 2

lat_sender用法如下:

usage: lat_sender <connect-to> <msg-size> <roundtrips>

其中:

  1. connect-to 表示消息接收端的地址
  2. msg-size 表示发送消息的大小(不包括协议的header,仅指用户消息的长度)
  3. roundtrips 表示消息来回的次数

lat_recver用法如下:

usage: lat_recver <bind-to> <msg-count>

其中:

  1. bind-to 表示监听一个socket地址
  2. msg-count 表示消息的数量

输出结果类似如下:

message size: 1000 [B]
roundtrip count: 100000
average latency: 67.626 [us]

High performance Network Programming

八月 9, 2014
network programming
No Comments »