qemu发生了crash。这种类型的问题比较少见,这里说一下这个问题的分析过程。
生成的coredump,一种是配置了/proc/sys/kernel/core_pattern
并且配置了ulimit
的情况,coredump文件会按照core pattern
生成。
还有一种是apport的,生成的文件使用apport-unpack
可以解压出来。
加载coredump文件,查看trace:
看到了原因是abort,那么还好,毕竟不是最难分析的。
从trace中来看,应该是frame 3
发生了错误,导致了qemu自己主动abort了。那么具体分析一下frame 3
再结合frame 2的第一个参数,可见,是pthread_create
的返回值是11。
打开/usr/include/asm-generic/errno-base.h
可见,errno 11是EAGAIN
。
既然pthread_create的返回值是EAGAIN,那么只好继续分析glibc的nptl
(glibc的pthread在nptl中实现)了。
同时,还要找到对应的glibc的版本。有两种办法供参考:
a,在gdb的命令行中敲info proc mappings
可以看到qemu当时映射了哪个glibc的文件,可以判断出来。
b,在shell中敲ldd /bin/qemu-system-x86_64 | grep libc,再通过symbolic link找到对应的文件
确定了glibc的版本是2.23,那么就可以去gnu下载对应的glibc的源代码了。
找到glibc-2.23/nptl/pthread_create.c
,分析EAGAIN的具体原因。
在这里先大致说一下linux平台上pthread的实现:linux并不区分进程和线程,内核中只有task。多个task组成一个group,同一个组里面的task共享虚拟内存空间,fs,signal handler等等几乎所有的资源。但是,每个task都需要有自己的用户栈,所以就需要在创建线程之前先为task分配page size对齐的内存。分配好内存之后,就可以使用系统调用sys_clone
创建线程了。
找到第一个可能返回EAGAIN的代码
如果在为新的线程分配栈内存的时候失败,那么就会返回EAGAIN。
stack用的内存比较大块,glibc维护了cache,尽量避免每次都需要syscall。
先确认cache是不是真的有,如果没有,很可能就是内存分配失败导致的。
再来确认stack_cache
的地址。
最后确认pthread的handler的list的内容,通过上面的几个关键字段的地址,可以分析出来pthread的list双链表都是指向了stack_cache。
综上,可以判断出来,内存是分配成功的。一,当时的stack_cache有一个缓存,直接分配给了那次分配;二,当时的stack_cache是空的,向kernel要了内存,并且成功了,在后面执行失败的时候,把内存归还给了cache。
此二者,无论那种情况,都可以认为这条路径下,不会返回EAGAIN的。
继续分析,看看还有哪里可能返回EAGAIN。
分析到了sys_clone,它的返回值可能是EAGAIN。继续分析linux-4.4/kernel/fork.c
,重点do_fork函数中可能返回EAGAIN的可能性:
a,qemu的thread的个数超过了限制?从cat /proc/sys/kernel/threads-max
和ulimit看进程的最大线程数,另外在gdb中info threads
可以看到所有的线程数。对比之下,coredump进程的线程数远小于限制,排除。
b,copy process执行的时候遇到了ENOMEM?这个看起来也不太像,从host的dmesg中没有看到任何oom信息。
c,host的pid max不足?cat /proc/sys/kernel/pid-max
,发现只有32768。一来这个数值偏小,二来测试在host上跑过多线程模拟的测试,这里看起来可能性最大了。
修改了pid-max之后,在原来的环境上一台虚拟机出现的问题,现在改用七台同时复现。目前七台虚拟机已经跑了超过了一天。还没有遇到。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。