【发布时间】:2014-07-16 17:00:53
【问题描述】:
问题
我需要一个能够长时间工作而不屈服的内核线程,基本上完全按需分配一个 CPU 内核:
int my_kthread(void *arg)
{
while(!kthread_should_stop()) {
do_some_work();
if(sleeping_enabled) msleep(1000);
else {
// What to do here to avoid lockup warnings
// and ensure system stability?
}
}
return 0;
}
背景
当我正在处理的模块被加载时,线程是这样创建的:
my_task = kthread_run(&my_kthread, (void *)some_data, "My KThread")
set_cpus_allowed(my_task, *cpumask_of(10)); // Pin thread to core #10
并在模块卸载时停止:
kthread_stop(my_task);
当sleeping_enabled 为true 时,一切正常。
否则,在线程启动后不久,内核就会抱怨明显的锁定。 起初,我只是为了避免各种警告,例如
BUG: soft lockup - CPU#10 stuck for 22s!
和
INFO: rcu_sched detected stalls on CPUs/tasks: { 10} (detected by 15, t=30567 jiffies)
因为他们倾向于用所有 >20 个内核的转储来淹没我的控制台,并且“锁定”是理想的行为。
我试着像这样戳看门狗:
if(sleeping_enabled) msleep(1000);
else touch_softlockup_watchdog();
结合(echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress)
并且几乎得到了我想要的(一个永不休眠的线程,成功地完成了我想要的操作,并且控制台中没有垃圾邮件)。
然而,这个“解决方案”不仅感觉像是在作弊,而且似乎我通过占用一个核心完全破坏了某些东西:当通过rmmod 卸载模块时,整个系统冻结。控制台开始定期转储所有内核上的软锁定,并带有以下调用跟踪:
[<ffffffff810c96b0>] ? queue_stop_cpus_work+0xd0/0xd0
[<ffffffff810c9903>] cpu_stopper_thread+0xe3/0x1b0
[<ffffffff8108639a>] ? finish_task_switch+0x4a/0xf0
[<ffffffff8169e654>] ? __schedule+0x3c4/0x700
[<ffffffff81080e98>] ? __wake_up_common+0x58/0x90
[<ffffffff810c9820>] ? __stop_cpus+0x80/0x80
[<ffffffff81077e93>] kthread+0x93/0xa0
[<ffffffff816a9724>] kernel_thread_helper+0x4/0x10
[<ffffffff81077e00>] ? flush_kthread_worker+0xb0/0xb0
[<ffffffff816a9720>] ? gs_change+0x13/0x13
同时,我的内核线程继续运行(正如它不时打印出来的一些控制台消息所证明的那样),所以它从未看到kthread_should_stop() 返回true。
在我切换到根本不睡觉之前,卸载确实工作正常并停止了线程。现在,我无法在不重新启动的情况下进行迭代修改。
请注意,我在这里简化了很多描述。我正在尝试将这样一个线程(以轮询一些硬件寄存器并记录它们的更改)添加到 GPU 驱动程序,因此可能存在与模块相关的原因导致卸载时冻结。但是,这并没有改变我关于如何最好地实现永不休眠线程的一般问题。
【问题讨论】:
-
有时你需要让其他东西在那个 CPU 上运行。在
else子句中尝试schedule()。只要没有其他 CPU 密集型进程试图在此 CPU 上运行,它就会快速返回。 -
@Peter
else schedule()确实修复了模块卸载时系统冻结 :) 在我原本空闲的系统上执行需要 5 到 20 微秒。但是,我正在轮询的寄存器的变化可能比这快一点(比如每 3 μs 一次),理想情况下,我希望注意到每一个变化。有没有更快的方法?
标签: linux-kernel scheduling watchdog