ipq5018卸载ecm加速模块的时候,出现kernel panic,内核版本4.4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | Unable to handle kernel NULL pointer dereference at virtual address 00000000 pgd = 862f4000 [00000000] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: test(-) CPU: 0 PID: 3248 Comm: rmmod Tainted: P 4.4.60 #0 Hardware name: Generic DT based system task: 87a1a400 ti: 8a1c4000 task.ti: 8a1c4000 PC is at exit_creds+0x1c/0x7c LR is at __put_task_struct+0x6c/0xb8 pc : [<8123cf8c>] lr : [<81223378>] psr: 20000013 sp : 8a1c5f28 ip : 00000090 fp : 00000000 r10: 00000000 r9 : 8a1c4000 r8 : 81209ce4 r7 : 00000081 r6 : 00000000 r5 : 86475f88 r4 : 86475f80 r3 : 00000000 r2 : ffffffff r1 : 00000091 r0 : 00000000 Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user Control: 10c0383d Table: 462f406a DAC: 00000055 Process rmmod (pid: 3248, stack limit = 0x8a1c4210) Stack: (0x8a1c5f28 to 0x8a1c6000) 5f20: 86475f80 86475f88 00000000 81223378 86475f80 8123bdc0 5f40: 7f002140 76f616c0 7eb18f7f 00000081 81209ce4 8127ebe4 8f5b16e8 74736574 5f60: 00000000 812ca484 00000020 00000000 87a1a400 00000000 87a1a400 00000000 5f80: 817f05c0 00000006 81209ce4 8a1c4000 8a1c5fb0 00000006 81209ce4 00000000 5fa0: 00000000 81209b40 00000000 00000000 76f616c0 00000000 00000000 00000000 5fc0: 00000000 00000000 7eb18f7f 00000081 54b5dafc 54b6df10 00000001 00000000 5fe0: 7eb18da4 7eb18d88 54b5bf70 76f1b70c 20000010 76f616c0 e2800060 e59f2174 [<8123cf8c>] (exit_creds) from [<81223378>] (__put_task_struct+0x6c/0xb8) [<81223378>] (__put_task_struct) from [<8123bdc0>] (kthread_stop+0x94/0x9c) [<8123bdc0>] (kthread_stop) from [<8127ebe4>] (SyS_delete_module+0x11c/0x1e0) [<8127ebe4>] (SyS_delete_module) from [<81209b40>] (ret_fast_syscall+0x0/0x34) Code: e590031c e584331c f57ff05b f590f000 (e1902f9f) ---[ end trace 1049bc8959325377 ]--- |
查看代码,是结束一个内核线程的时候,出现了panic。结束内核线程使用kthread_stop()函数,怀疑是线程提前结束,调用kthread_stop导致kernel panic,写一个小demo验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include <linux/init.h> #include <linux/module.h> #include <linux/kthread.h> struct task_struct *k; static int thread_fn( void *data) { return 0; } static int __init test_init( void ) { k = kthread_run(thread_fn, NULL, "test thread" ); if (!k) return -1; return 0; } static void __exit test_exit( void ) { kthread_stop(k); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE( "GPL" ); MODULE_AUTHOR( "Jianpeng Yuan" ); MODULE_DESCRIPTION( "test module" ); |
安装,然后卸载,复现了问题。thread_fn是自己结束的,内核会自动回收stask_struct结构体。再调用kthread_stop()将导致非法内存访问。
如果内核线程是判断kthread_should_stop()结束的,如下:
static int thread_fn(void *data)
{
while (!kthread_should_stop()) {
schedule_timeout_interruptible(10);
}
return 0;
}
那么,使用kthread_stop()是安全的。kthread_should_stop()非常简单,就是测试KTHREAD_SHOULD_STOP位。
test_bit(KTHREAD_SHOULD_STOP, &to_kthread(current)->flags);
而kthread_stop()会 set_bit(KTHREAD_SHOULD_STOP, &kthread->flags); 因此再kthread_stop()之前,内核线程不会退出。
这就保证了kthread_stop()是安全的。
如果内核线程有多种退出途径,比如:
static int thread_fn(void *data)
{
while (!kthread_should_stop()) {
schedule_timeout_interruptible(10);
if(signal_pending(current))
break;
}
return 0;
}
如果有挂起的信号也退出,那么就可能在kthread_stop之前退出,此时直接使用kthread_stop也是不安全的。
此时的解决办法,是提前
获得task_struct的引用计数。kthread_stop()之后,再释放引用计数。这样可以保证即使线程再stop之前结束,也不会被释放。
static int __init test_init(void)
{
k = kthread_create(thread_fn, NULL, "test thread");
if (!k)
return -1;
get_task_struct(k);
wake_up_process(k);
return 0;
}
static void __exit test_exit(void)
{
kthread_stop(k);
put_task_struct(k);
}
参考
https://stackoverflow.com/questions/37842330/kthread-stop-crashes-the-kernel