soft lockup的线程无法kill的原因
构造简单驱动在内核态死循环场景,第二个write_lock在当前cpu上一直自旋(线程处于R状态),造成内核soft lockup,
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 队列上去,信号真正得到执行的时机是线程执行完异常/中断返回到用户态的时刻,如下图示:
让信号看起来是一个异步中断的关键就是,正常的用户进程是会频繁的在用户态和内核态之间切换的(这种切换包括:系统调用、缺页异常、系统中断…),所以信号能很快的能得到执行。但这也带来了一点问题,内核进程是不响应信号的,除非它刻意的去查询。所以通常情况下我们无法通过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