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 00000000pgd = 862f4000[00000000] *pgd=00000000Internal error: Oops: 5 [#1] PREEMPT SMP ARMModules linked in: test(-)CPU: 0 PID: 3248 Comm: rmmod Tainted: P 4.4.60 #0Hardware name: Generic DT based systemtask: 87a1a400 ti: 8a1c4000 task.ti: 8a1c4000PC is at exit_creds+0x1c/0x7cLR is at __put_task_struct+0x6c/0xb8pc : [<8123cf8c>] lr : [<81223378>] psr: 20000013sp : 8a1c5f28 ip : 00000090 fp : 00000000r10: 00000000 r9 : 8a1c4000 r8 : 81209ce4r7 : 00000081 r6 : 00000000 r5 : 86475f88 r4 : 86475f80r3 : 00000000 r2 : ffffffff r1 : 00000091 r0 : 00000000Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment userControl: 10c0383d Table: 462f406a DAC: 00000055Process rmmod (pid: 3248, stack limit = 0x8a1c4210)Stack: (0x8a1c5f28 to 0x8a1c6000)5f20: 86475f80 86475f88 00000000 81223378 86475f80 8123bdc05f40: 7f002140 76f616c0 7eb18f7f 00000081 81209ce4 8127ebe4 8f5b16e8 747365745f60: 00000000 812ca484 00000020 00000000 87a1a400 00000000 87a1a400 000000005f80: 817f05c0 00000006 81209ce4 8a1c4000 8a1c5fb0 00000006 81209ce4 000000005fa0: 00000000 81209b40 00000000 00000000 76f616c0 00000000 00000000 000000005fc0: 00000000 00000000 7eb18f7f 00000081 54b5dafc 54b6df10 00000001 000000005fe0: 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