soft lockup的线程无法kill的原因

构造简单驱动在内核态死循环场景,第二个write_lock在当前cpu上一直自旋(线程处于R状态),造成内核soft lockup,

soft lockup的线程无法kill的原因

238214 root      20   0   13240   1964   1832 R 100.0  0.0  5124099h insmod

[275512.284998] watchdog: BUG: soft lockup - CPU#11 stuck for 22s! [insmod:238214]
[275512.285665] RIP: 0010:queued_write_lock_slowpath+0x4f/0x80
[275512.285675] Call Trace:
[275512.285676]  ? 0xffffffffc0943000
[275512.285678]  _raw_write_lock+0x1e/0x30
[275512.285680]  my_init+0x48/0x70 [test_rwlock]
[275512.285682]  do_one_initcall+0x51/0x1b0
[275512.285684]  ? kmem_cache_alloc_trace+0xa0/0x1c0
[275512.285685]  do_init_module+0x60/0x205
[275512.285688]  load_module+0x21b6/0x2950
[275512.285690]  ? m_show+0x1c0/0x1c0
[275512.285693]  SYSC_finit_module+0xa9/0x100
[275512.285696]  SyS_finit_module+0xe/0x10
[275512.285698]  do_syscall_64+0x6c/0x1b0
[275512.285699]  entry_SYSCALL64_slow_path+0x25/0x25

此时用kill -6,-9均无法将该线程杀死,其中缘由涉及到内核对信号(signal)的处理机制,

对于处于运行状态(TASK_RUNNING)的线程,信号发送后挂到目标线程的信号队列,进程返回用户态的时候在 do_notify_resume() 中处理信号。对一个线程发送一个信号以后,并没有硬中断发生,只是简单把信号挂载到目标线程的信号 pending 队列上去,信号真正得到执行的时机是线程执行完异常/中断返回到用户态的时刻,如下图示:

soft lockup的线程无法kill的原因

让信号看起来是一个异步中断的关键就是,正常的用户进程是会频繁的在用户态和内核态之间切换的(这种切换包括:系统调用、缺页异常、系统中断…),所以信号能很快的能得到执行。但这也带来了一点问题,内核进程是不响应信号的,除非它刻意的去查询。所以通常情况下我们无法通过kill命令去杀死一个内核进程。

对于阻塞状态的进程又怎么样来响应信号呢?

让一个进程进入阻塞状态,我们可以选择让其进入可中断(TASK_INTERRUPTIBLE)或者不可中断(TASK_UNINTERRUPTIBLE)状态,比如 mutex 操作分为 mutex_lock() 和 mutex_lock_interruptible()。所谓的可中断和不可中断就是说是否可以被中断信号打断:如果进程处于可中断(TASK_INTERRUPTIBLE)状态,信号发送函数会直接唤醒进程,让进程处理完内核态操作去返回用户态,让进程迅速去执行信号处理函数;如果进程处于不可中断(TASK_UNINTERRUPTIBLE)状态俗称为 D 进程,信号只会挂到信号队列,但是没有机会去立即执行。

kernel/signal.c:

  • __send_signal() -> complete_signal() -> signal_wake_up() -> signal_wake_up_state()

 

参考:

http://kernel.meizu.com/linux-signal.html

https://www.oreilly.com/library/view/understanding-the-linux/0596005652/ch04s09.html