本文主要讲解linux kernel panic系列其中一种情况:

Attempted to kill init! exitcode=0x0000000b

背景:linux kernel 的panic ,在日常的kernel维护中比较常见,不同的

kernel panic 有不同的背景,而这些背景的触发则有其一些类似的

处理思想。

下面列一下我们是怎么排查并解决这个问题的。

一、故障现象

oppo云内核团队接到连通性告警报障,发现机器复位:

  1. KERNEL: /usr/lib/debug/lib/modules/3.10.0-1062.18.1.el7.x86_64/vmlinux
  2. DUMPFILE: vmcore [PARTIAL DUMP]
  3. CPUS: 72
  4. LOAD AVERAGE: 0.11, 0.07, 0.15
  5. TASKS: 2229
  6. RELEASE: 3.10.0-1062.18.1.el7.x86_64
  7. VERSION: #1 SMP Tue Mar 17 23:49:17 UTC 2020
  8. MACHINE: x86_64 (3100 Mhz)
  9. PANIC: "Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b"
  10. PID: 1
  11. COMMAND: "systemd"------进程名称,这个进程比较特殊
  12. TASK: ffff8ca429570000 [THREAD_INFO: ffff8ca429578000]
  13. CPU: 20
  14. STATE: (PANIC)
  15. crash> bt
  16. PID: 1 TASK: ffff8ca429570000 CPU: 20 COMMAND: "systemd"
  17. #0 [ffff8ca42957bb30] machine_kexec at ffffffff8c665b34
  18. #1 [ffff8ca42957bb90] __crash_kexec at ffffffff8c722592
  19. #2 [ffff8ca42957bc60] panic at ffffffff8cd74a16
  20. #3 [ffff8ca42957bce0] do_exit at ffffffff8c6a28e6
  21. #4 [ffff8ca42957bd78] do_group_exit at ffffffff8c6a296f
  22. #5 [ffff8ca42957bda8] get_signal_to_deliver at ffffffff8c6b377e
  23. #6 [ffff8ca42957be40] do_signal at ffffffff8c62c527
  24. #7 [ffff8ca42957bf30] do_notify_resume at ffffffff8c62cc32
  25. #8 [ffff8ca42957bf50] retint_signal at ffffffff8cd8457c
  26. RIP: 00005653070bb420 RSP: 00007ffc3b683438 RFLAGS: 00010202
  27. RAX: 0000000000000000 RBX: 000000000000000e RCX: ffffffffffffffff
  28. RDX: 00007ffc3b683440 RSI: 00007ffc3b683570 RDI: 0000000000000007
  29. RBP: 000056530719e920 R8: 0000000000000001 R9: 00005653085048a0
  30. R10: 00000000ffffff00 R11: 0000000000000206 R12: 0000000000000009
  31. R13: 0000565308389ab0 R14: 0000000000000004 R15: 0000565308389ab0
  32. ORIG_RAX: ffffffffffffffff CS: 0033 SS: 002b

从堆栈看,我们的1号进程因为收到了退出信号,内核出于对1号进程退出的保护,

会根据情况触发panic,保存当前堆栈。

二、故障现象分析

1、理论知识:

我们了解到0号进程是系统所有进程的先祖,是一个静态构造task_struct

而运行的进程,

0号进程创建1号进程的方式如下:

kernel_thread(kernel_init, NULL, CLONE_FS);

由0号进程创建1号进程(内核态),1号内核线程负责执行内核的部分初始化工作及进行系统配置

之后,调用 try_to_run_init_process 来选择对应的用户态程序,然后

调用do_execve运行可执行程序init,并从内核态线程演变成用户态1号进程,即init进程

  1. static int __ref kernel_init(void *unused)
  2. {
  3. ...
  4. if (!try_to_run_init_process("/sbin/init") ||
  5. !try_to_run_init_process("/etc/init") ||
  6. !try_to_run_init_process("/bin/init") ||
  7. !try_to_run_init_process("/bin/sh"))
  8. return 0;
  9. ...
  10. }
  11. 而当前系统的/sbin/init 则是 systemd
  12. # file /sbin/init
  13. /sbin/init: symbolic link to `../lib/systemd/systemd'

1号用户态进程慢慢从 sysvinit,upstart,演进到 systemd,提高了启动速度,此为后话。

2、实战分析:

查看当前系统状态,当前属于restart状态。

crash> p system_state

system_state = $1 = SYSTEM_RESTART

根据调用堆栈,代码分析如下:

对应的调用链为:


  1. do_group_exit-->do_exit-->exit_notify-->forget_original_parent-->find_new_reaper
  2. 592 static struct task_struct *find_new_reaper(struct task_struct *father)
  3. 593 __releases(&tasklist_lock)----内核常见写法,会有检查获取锁的顺序
  4. 594 __acquires(&tasklist_lock)
  5. 595 {
  6. ....
  7. 607
  8. 608 if (unlikely(pid_ns->child_reaper == father)) {
  9. 609 qwrite_unlock_irq(&tasklist_lock);
  10. 610 if (unlikely(pid_ns == &init_pid_ns)) {//caq:进入这个流程产生panic
  11. 611 panic("Attempted to kill init! exitcode=0x%08x\n",
  12. 612 father->signal->group_exit_code ?:
  13. 613 father->exit_code);
  14. 614 }
  15. ...}

分析的数据为:

  1. crash> task_struct.signal,exit_signal ffff8ca429570000
  2. signal = 0xffff8cb3a9ed8000
  3. exit_signal = 17
  4. crash> signal_struct.flags 0xffff8cb3a9ed8000
  5. flags = 4---这个信号下面有
  6. #define SIGNAL_STOP_STOPPED 0x00000001 /* job control stop in effect */
  7. #define SIGNAL_STOP_CONTINUED   0x00000002 /* SIGCONT since WCONTINUED reap */
  8. #define SIGNAL_GROUP_EXIT   0x00000004 /* group exit in progress */----------------------------------------4就是退出标志
  9. #define SIGNAL_GROUP_COREDUMP   0x00000008 /* coredump in progress */

当前1号进程是收到什么信号导致退出的呢?最近有些同事会问到一些基本的x86汇编

的栈操作,在此描述一下,这些属于汇编的基本功。

  1. crash> dis -l get_signal_to_deliver|grep do_group_exit -B 5
  2. /usr/src/debug/kernel-3.10.0-1062.18.1.el7/linux-3.10.0-1062.18.1.el7.x86_64/kernel/signal.c: 2386
  3. 0xffffffff8c6b376d <get_signal_to_deliver+445>: mov %r12,%rdi
  4. 0xffffffff8c6b3770 <get_signal_to_deliver+448>: callq 0xffffffff8c8b54a0 <do_coredump>
  5. /usr/src/debug/kernel-3.10.0-1062.18.1.el7/linux-3.10.0-1062.18.1.el7.x86_64/kernel/signal.c: 2392
  6. 0xffffffff8c6b3775 <get_signal_to_deliver+453>: mov (%r12),%edi-----入参
  7. 0xffffffff8c6b3779 <get_signal_to_deliver+457>: callq 0xffffffff8c6a2930 <do_group_exit>
  8. crash> dis -l do_group_exit |head -10
  9. /usr/src/debug/kernel-3.10.0-1062.18.1.el7/linux-3.10.0-1062.18.1.el7.x86_64/kernel/exit.c: 991
  10. 0xffffffff8c6a2930 <do_group_exit>: nopl 0x0(%rax,%rax,1) [FTRACE NOP]
  11. 0xffffffff8c6a2935 <do_group_exit+5>: push %rbp
  12. 0xffffffff8c6a2936 <do_group_exit+6>: mov %rsp,%rbp
  13. 0xffffffff8c6a2939 <do_group_exit+9>: push %r14
  14. 0xffffffff8c6a293b <do_group_exit+11>: push %r13
  15. /usr/src/debug/kernel-3.10.0-1062.18.1.el7/linux-3.10.0-1062.18.1.el7.x86_64/arch/x86/include/asm/current.h: 14
  16. 0xffffffff8c6a293d <do_group_exit+13>: mov %gs:0x10e80,%r13
  17. /usr/src/debug/kernel-3.10.0-1062.18.1.el7/linux-3.10.0-1062.18.1.el7.x86_64/kernel/exit.c: 991
  18. 0xffffffff8c6a2946 <do_group_exit+22>: push %r12---------上一个栈的入参压栈在此

我们看到,r12是在rbp,r14,r13之后压栈的,所以能取出它的值来:

  1. #3 [ffff8ca42957bce0] do_exit at ffffffff8c6a28e6
  2. ffff8ca42957bce8: ffff8ca429570804 000000012957bf58
  3. ffff8ca42957bcf8: 0000000000000000 0000000000000000
  4. ffff8ca42957bd08: 00000000000200cd ffff8cbe89ef90e0
  5. ffff8ca42957bd18: ffff8ca42957bd30 ffffffff8c6b03c3
  6. ffff8ca42957bd28: ffff8ca429570558 ffff8ca42957bd30
  7. ffff8ca42957bd38: ffff8ca42957bd30 00000000b1625208
  8. ffff8ca42957bd48: ffff8cb3a9ed8000 000000000000000b
  9. ffff8ca42957bd58: ffff8ca429570000 ffff8cc2fb710140
  10. ffff8ca42957bd68: ffff8cc2fb710148 ffff8ca42957bda0
  11. ffff8ca42957bd78: ffffffff8c6a296f
  12. #4 [ffff8ca42957bd78] do_group_exit at ffffffff8c6a296f
  13. ffff8ca42957bd80: 000000000000000b ffff8ca42957be70 ------------r12压栈在此
  14. r12
  15. ffff8ca42957bd90: ffff8ca429570000 ffff8cc2fb710140
  16. r13 r14
  17. ffff8ca42957bda0: ffff8ca42957be38 ffffffff8c6b377e
  18. rbp
  19. #5 [ffff8ca42957bda8] get_signal_to_deliver at ffffffff8c6b377e

那取出对应的信号分析:

  1. crash> siginfo_t ffff8ca42957be70
  2. struct siginfo_t {
  3. si_signo = 11, ---------------信号
  4. si_errno = 0,
  5. si_code = 128,

好的,到目前为止,我们知道了1号进程是收到了段错误信号,也就是11信号,那么

正常情况下,我们给1号进程发送11号信号,会出现这个panic么?

三、故障复现

操作如下:

  1. #kill -11 1
  2. Broadcast message from systemd-journald@centos7 (触发时间):
  3. systemd[1]: Caught <SEGV>, dumped core as pid 17969.
  4. Broadcast message from systemd-journald@centos7 (触发时间):
  5. systemd[1]: Freezing execution.-------------对应的就是 log_emergency("Freezing execution.");

如上,我们发现如果使用kill 11 去杀死1号进程,并不能触发当前的crash,那么,

到底是什么原因触发了crash呢?

首先,发送完11信号之后,我们看下1号进程处于什么状态,

此时systemd的状态为:

  1. (gdb) bt
  2. #0 0x00007efede1fdb80 in waitid () from /lib64/libc.so.6
  3. #1 0x000055759d6ace8e in freeze ()
  4. #2 0x000055759d636528 in crash ()
  5. #3 <signal handler called>--------------注意这个,信号处理状态
  6. #4 0x00007efede237483 in epoll_wait () from /lib64/libc.so.6
  7. #5 0x000055759d6dda69 in sd_event_wait ()
  8. #6 0x000055759d6de57d in sd_event_run ()
  9. #7 0x000055759d63f0c3 in manager_loop ()
  10. #8 0x000055759d6335fb in main ()

也就是说,虽然我们kill 11信号没有能够杀死1号进程并产生crash,但其实

当前1号进程的状态也不太对,还处于信号处理的堆栈中,正常工作状态应该是:

  1. # cat /proc/1/stack
  2. [<0000000000000000>] ep_poll+0x23e/0x360
  3. [<0000000000000000>] SyS_epoll_wait+0xed/0x120
  4. [<0000000000000000>] system_call_fastpath+0x22/0x27
  5. [<0000000000000000>] 0xffffffffffffffff
  6. (gdb) bt
  7. #0 0x00007f2e12bb7643 in epoll_wait () at ../sysdeps/unix/syscall-template.S:81
  8. #1 0x0000560bf6913ee9 in sd_event_wait ()
  9. #2 0x0000560bf69149fd in sd_event_run ()
  10. #3 0x0000560bf68742e3 in manager_loop ()
  11. #4 0x0000560bf686872b in main ()

关注到这个细节之后,我们来看一下1号进程的信号处理函数:

  1. 正常的其他机器上的systemd 1号进程的信号处理:
  2. crash> sig 1
  3. PID: 1 TASK: ffff886eadff0000 CPU: 14 COMMAND: "systemd"
  4. SIGNAL_STRUCT: ffff886ead848000 NR_THREADS: 1
  5. SIG SIGACTION HANDLER MASK FLAGS
  6. ...
  7. [11] ffff88794dad8148 55568fde1420 0000000000000000 44000000 (SA_RESTORER|SA_NODEFER)---注意比较

而我们当前crash进程的信号处理函数为:

  1. crash> sig 1
  2. PID: 1 TASK: ffff8ca429570000 CPU: 20 COMMAND: "systemd"
  3. SIGNAL_STRUCT: ffff8cb3a9ed8000 NR_THREADS: 1
  4. SIG SIGACTION HANDLER MASK FLAGS
  5. ...
  6. [11] ffff8cc2fb710148 SIG_DFL 0000000000000000 44000000 (SA_RESTORER|SA_NODEFER)--注意比较

我们发现,当前出现crash的1号进程对11号信号的处理函数是 SIG_DFL,而正常的并不是。

我们查看systemd的信号处理函数源码:

  1. #define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT
  2. static void install_crash_handler(void) {
  3.         static const struct sigaction sa = {
  4.                 .sa_handler = crash,
  5.                 .sa_flags = SA_NODEFER, /* So that we can raise the signal again from the signal handler */
  6.         };
  7.         int r;
  8.         /* We ignore the return value here, since, we don't mind if we
  9.          * cannot set up a crash handler */
  10.         r = sigaction_many(&sa, SIGNALS_CRASH_HANDLER, -1);
  11. 所以可以看出,crash函数负责处理SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT 这些信号,
  12. 也就是包含11号信号:
  13. 220 _noreturn_ static void crash(int sig) {
  14. 221 struct sigaction sa;
  15. 222 pid_t pid;
  16. 223
  17. 224 if (getpid_cached() != 1)
  18. 225 /* Pass this on immediately, if this is not PID 1 */
  19. 226 (void) raise(sig);
  20. 227 else if (!arg_dump_core)
  21. 228 log_emergency("Caught <%s>, not dumping core.", signal_to_string(sig));
  22. 229 else {
  23. 230 sa = (struct sigaction) {
  24. 231 .sa_handler = nop_signal_handler,
  25. 232 .sa_flags = SA_NOCLDSTOP|SA_RESTART,
  26. 233 };
  27. 234
  28. 235 /* We want to wait for the core process, hence let's enable SIGCHLD */
  29. 236 (void) sigaction(SIGCHLD, &sa, NULL);
  30. 237
  31. 238 pid = raw_clone(SIGCHLD);
  32. 239 if (pid < 0)
  33. 240 log_emergency_errno(errno, "Caught <%s>, cannot fork for core dump: %m", signal_to_string(sig));
  34. 241 else if (pid == 0) {--------子进程
  35. 242 /* Enable default signal handler for core dump */
  36. 243
  37. 244 sa = (struct sigaction) {
  38. 245 .sa_handler = SIG_DFL,
  39. 246 };
  40. 247 (void) sigaction(sig, &sa, NULL);
  41. ........
  42. 255 /* Raise the signal again */
  43. 256 pid = raw_getpid();
  44. 257 (void) kill(pid, sig); --------------自己发送信号给自己
  45. 258
  46. 259 assert_not_reached("We shouldn't be here...");
  47. 260 _exit(EXIT_EXCEPTION);
  48. 261 } else {--------父进程
  49. 262 siginfo_t status;
  50. 263 int r;
  51. ......
  52. 266 r = wait_for_terminate(pid, &status);
  53. 267 if (r < 0)
  54. 268 log_emergency_errno(r, "Caught <%s>, waitpid() failed: %m", signal_to_string(sig));
  55. 269 else if (status.si_code != CLD_DUMPED) {
  56. 270 const char *s = status.si_code == CLD_EXITED
  57. 271 ? exit_status_to_string(status.si_status, EXIT_STATUS_LIBC)

因为云安卓虚拟化项目的缘故,最近也看了一些安卓的资料,如果有对安卓比较熟悉的朋友,

可以看到上面的代码与crash_dump进程的代码有异曲同工之妙:

  1. pid_t forkpid = fork();
  2. if (forkpid == -1) {
  3. PLOG(FATAL) << "fork failed";
  4. } else if (forkpid == 0) {
  5. fork_exit_read.reset();
  6. } else {
  7. // We need the pseudothread to live until we get around to verifying the vm pid against it.
  8. // The last thing it does is block on a waitpid on us, so wait until our child tells us to die.
  9. fork_exit_write.reset();
  10. char buf;
  11. TEMP_FAILURE_RETRY(read(fork_exit_read.get(), &buf, sizeof(buf)));
  12. _exit(0);
  13. }

如果仔细研究了一下上面信号处理的代码,就应该知道怎么复现当前的crash堆栈了,比如你连续

两次:

kill -11 1

kill -11 1

或者其他类似的信号,比如:

  1. # gdb -p 1
  2. GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
  3. .....
  4. (gdb) bt
  5. #0 0x00007f15138ca483 in epoll_wait () from /lib64/libc.so.6
  6. #1 0x00005588a7b44a69 in sd_event_wait ()
  7. #2 0x00005588a7b4557d in sd_event_run ()
  8. #3 0x00005588a7aa60c3 in manager_loop ()
  9. #4 0x00005588a7a9a5fb in main ()
  10. (gdb) c
  11. Continuing.
  12. ^C

上面的操作,就是gdb 1号进程,之后continue运行,然后再按ctrl +c ,

都会有类似的结果,注意,不要在线上环境做类似动作,高度风险。

四、故障规避或解决

可能的解决方案是:

尝试修改systemd对应的信号处理函数,或者,什么都不做,毕竟触发的概率也不是很高。

五、作者简介

陈安庆,目前在oppo混合云负责linux内核及容器,虚拟机等虚拟化方面的工作,

联系方式:微信与手机同号:18752035557。

systemd之导致内核 crash的更多相关文章

  1. 一次内核 crash 的排查记录

    一次内核 crash 的排查记录 使用的发行版本是 CentOS,内核版本是 3.10.0,在正常运行的情况下内核发生了崩溃,还好有 vmcore 生成. 准备排查环境 crash 内核调试信息rpm ...

  2. Linux内核Crash分析

    转载自:http://linux.cn/article-3475-1.html 在工作中经常会遇到一些内核crash的情况,本文就是根据内核出现crash后的打印信息,对其进行了分析,使用的内核版本为 ...

  3. 案例:Standby RAC遭遇ORA-1157,1111,1110导致实例crash处理

    案例:Standby RAC遭遇ORA-1157,1111,1110导致实例crash处理 环境:RHEL 6.5 + Oracle RAC 11.2.0.4 + Dataguard 今天在实验环境的 ...

  4. 打开Voice Over时,CATextLayer的string对象兼容NSString和NSAttributedString导致的Crash(二解决思路3)

    续前一篇:打开Voice Over时,CATextLayer的string对象兼容NSString和NSAttributedString导致的Crash(二解决思路2)ok,到这里已经能够锁定范围了, ...

  5. 关于CALayer导致的crash问题

    push到一个页面进行绘图时,设置如下: CALayer * layer = [CALayer layer]; layer.frame = CGRectMake(, , , ); layer.dele ...

  6. 捉虫日记 | MySQL 5.7.20 try_acquire_lock_impl 异常导致mysql crash

    背景 近期线上MySQL 5.7.20集群不定期(多则三周,短则一两天)出现主库mysql crash.触发主从切换问题,堆栈信息如下: 从堆栈信息可以明显看出,在调用 try_acquire_loc ...

  7. 硬件错误导致的crash

    [683650.031028] BUG: unable to handle kernel paging request at 000000000001b790--------------------- ...

  8. 解决iOS7中UITableView在使用autolayout时layoutSubviews方法导致的crash

    近期公司项目上线后,出现了大量的crash,发生在iOS7系统上,和UITableView相关: Auto Layout still required after executing -layoutS ...

  9. 一些Windows API导致的Crash以及使用问题总结

    RegQueryValueEx gethostbyname/getaddrinfo _localtime64 FindFirstFile/FindNextFile VerQueryValue Crea ...

随机推荐

  1. python常用标准库(os系统模块、shutil文件操作模块)

    常用的标准库 系统模块 import os 系统模块用于对系统进行操作. 常用方法 os模块的常用方法有数十种之多,本文中只选出最常用的几种,其余的还有权限操作.文件的删除创建等详细资料可以参考官方文 ...

  2. Maven笔记---超详细

    显眼位置标注来源:此文章为B站课程黑马程序员Maven全套教程笔记,由本人整理. Maven简介 Maven的本质是一个项目管理工具,将项目开发和管理过程抽象成一个项目对象模型(POM) POM (P ...

  3. ubuntu使用postfix和AWS-SES发送邮件

    在日常开发中,邮件发送是个比较常见的场景.因此出现了很多相关的软件和服务,各大云厂商也推出自己的邮件服务.今天笔者就像大家介绍一种常见的组合,AWS的邮件服务 SES 与邮件服务器 postfix 的 ...

  4. Jetpack架构组件学习(3)——Activity Results API使用

    原文地址:Jetpack架构组件学习(3)--Activity Results API使用 - Stars-One的杂货小窝 技术与时俱进,页面跳转传值一直使用的是startActivityForRe ...

  5. Blazor WebAssembly + Grpc Web = 未来?

    Blazor WebAssembly是什么 首先来说说WebAssembly是什么,WebAssembly是一个可以使C#,Java,Golang等静态强类型编程语言,运行在浏览器中的标准,浏览器厂商 ...

  6. IIS版本与Windows Server版本对应关系

    IIS 6.0随着Windows XP Professional 64位和Windows Server 2003发布. IIS 7.0随着Windows Vista和Windows Server 20 ...

  7. 【RocketMQ】NameServer的启动

    NameServer是一个注册中心,Broker在启动时向所有的NameServer注册,生产者Producer和消费者Consumer可以从NameServer中获取所有注册的Broker列表,并从 ...

  8. 基于Vite+React构建在线Excel

    Vite是随着Vue3一起发布的一款新型前端构建工具,能够显著的提升前端开发体验,它主要由两部分组成: (1)一个开发服务器,它基于**原生ES模块提供了丰富的内建功能,如速度快到惊人的 模块热更新( ...

  9. SAP Picture Control(图片加载)

    Screen display 效果 源代码 program sap_picture_demo. set screen 200. TYPE-POOLS cndp. ******************* ...

  10. Windows下MySQL的安装和删除

    Windows下MySQL的安装和删除 安装Mysql 1 下载mysql 地址 2 安装教程 2.1配置环境变量 变量名:MYSQL_HOME 变量值:D:\software\programming ...