1.问题来源
公司线上环境出现MQ不能接受消息的异常,运维和开发人员临时切换另一台服务器的MQ后恢复。同时运维人员反馈在出现问题的服务器上很多基本的命令都不能运行,出现如下错误:
2. 初步原因分析和解决
让运维的兄弟在服务上查看内存、CPU、网络、IO等基本信息都正常。于是自己到运维的服务器上看了一下,下面是slabtop –s c的运行结果,问题初步原因貌似出现了:
如果看到这个截图你看不出什么异常的话,下面的内容你可能不感兴趣,哈哈。。。
task_struct是内核对进程的管理单位,通过slub(slab的升级版,如果你对slub不了解也不影响下面的内容,只要了解slab就行了)进行节点的管理,正常负载的服务不应该出现task_struct的slub结构体占用内存最大的情况,这说明这台服务器上开启了大量的进程(Linux内核态对进程和线程都是一个单位,不要纠结这个,后面可能会进程、线程混用)。
通过这个信息,兄弟们发现这台服务器上有近3万个线程,同时也定位到出问题的网元(一个新同学的代码没有Review直接上线,里面有一个BUG触发了异常创建大量线程)。
问题貌似到这里就结束了,但是作为一个有情怀的程序员,这只是一个开始(哥的情怀白天都被繁琐的工作磨没了,只能在这深夜独享了。。。)
3. Linux线程数的限制
3.1 应用层测试代码
#define MEMSIZE (1024 * 1024 * 256) void thread(void) { sleep(100); return; } int main() { pthread_t id; int ret; int num = 0; while (1) { ret = pthread_create(&id, NULL, (void*)thread, NULL); ++num; if (ret != 0) break; } printf("pthread_create fail with ret=%d, total num=%d\n", ret, num); sleep(100); return 0; } |
通过strace跟踪,发现问题出现在copy_process函数,那剩下的工作就是分析copy_process返回异常的原因了。
3.2 逆向分析
这个时候逆向分析最简单直接,可以直接定位到问题原因。
首先通过strace分析,查找出问题的系统调用是clone函数。
SYS_clone—>do_fork—>copy_process。内核态函数的分析工具这次试用了systemtap,下面就是没有任何美感的stap代码了,将就着看看吧
probe kernel.statement("*@kernel/fork.c:1184") { printf("In kernel/fork.c 1184\n"); } probe kernel.statement("*@kernel/fork.c:1197") { printf("In kernel/fork.c 1197\n"); } probe kernel.statement("*@kernel/fork.c:1206") { printf("In kernel/fork.c 1206\n"); } probe kernel.statement("*@kernel/fork.c:1338") { printf("In kernel/fork.c 1338\n"); } probe kernel.statement("*@kernel/fork.c:1342") { printf("In kernel/fork.c 1342\n"); } probe kernel.statement("*@kernel/fork.c:1363") { printf("In kernel/fork.c 1363\n"); } probe kernel.statement("*@kernel/fork.c:1369") { printf("In kernel/fork.c 1369\n"); } probe kernel.statement("*@kernel/fork.c:1373") { printf("In kernel/fork.c 1373\n"); } probe kernel.function("copy_process").return { printf("copy_process return %d\n", $return) } function check_null_pid:long(addr:long) { struct pid *p; p = (struct pid*)THIS->l_addr; if (p == NULL) THIS->__retvalue = 0; else THIS->__retvalue = 1; } probe kernel.function("alloc_pid") { printf("alloc_pid init\n"); } probe kernel.statement("*@kernel/pid.c:301") { printf("alloc_pid 301\n"); } probe kernel.statement("*@kernel/pid.c:312") { printf("alloc_pid 312\n"); } probe kernel.function("alloc_pid").return { printf("alloc_pid return %ld\n", check_null_pid($return)); } |