【发布时间】:2017-03-02 02:36:36
【问题描述】:
在编写 Linux 内核模块时,我遇到了一个 kthread 问题,我在等待信号量解锁时无法唤醒。这会导致线程无法停止并且rmmod 在尝试卸载模块时冻结。
请注意:此模块在 3.10 内核上运行,我无法将其更新到较新版本(客户要求在具有 3.10 内核的库存 CentOS 7 上运行)。 p>
以下是模块源代码中有趣的部分。它代表了一个简单的生产者消费者问题,列表不受大小限制(因此不需要生产者信号量)并由互斥锁保护。从列表中获取某些内容的函数由一个信号量保护,该信号量由生产者升高并由消费者降低。生产者函数是从这个代码中没有显示的外部事件(实际上是一个字符设备)调用的,sn-ps 保持尽可能小。该过程完美无缺,除了模块卸载。
导致冻结的部分在代码sn-ps中用cmets标记。我知道停止 kthread 的唯一方法是在其上调用 kthread_stop,在这种情况下它会失败,因为它显然无法唤醒睡眠线程。因为它等待线程退出,所以调用永远不会返回,模块也不会卸载。
如何唤醒和停止等待信号量成功卸载模块的kthread?
列表实现:
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/semaphore.h>
static LIST_HEAD(list);
DEFINE_MUTEX(list_lock);
DEFINE_SEMAPHORE(sem_list_consumer);
void add_to_list(struct *some_struct) {
int rv = mutex_lock_interruptible(&list_lock);
if(rv != 0) {
return;
}
list_add(&some_struct->list, &list);
mutex_unlock(&list_lock);
up(&sem_list_consumer);
}
struct some_struct * take_from_list() {
int rv;
some_struct *entry;
/* this is where the kthread will freeze when module is unloaded */
rv = down_interruptible(&sem_list_consumer);
if(rv != 0) {
return NULL;
}
rv = mutex_lock_interruptible(&list_lock);
if(rv != 0) {
up(&sem_list_consumer);
return NULL;
}
if (list_empty(&list)) {
mutex_unlock(&list_lock);
return NULL;
} else {
entry = list_last_entry(&list, struct some_struct, list);
if (entry) {
list_del(&entry->list);
}
}
mutex_unlock(&list_lock);
return entry;
}
消费者kthread实现:
#include <linux/kthread.h>
#include <linux/sched.h>
int consumer_kthread(void *data) {
struct some_struct *entry;
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
/* Here the function including the semaphore is called */
entry = take_from_list();
if(entry != NULL) {
/* Do something with 'entry' here */
} else {
/* Some handling of returned NULL pointers */
}
set_current_state(TASK_INTERRUPTIBLE);
}
set_current_state(TASK_RUNNING);
return 0;
}
模块实现:
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/sched.h>
static struct task_struct *consumer_task;
static int __init initModule(void) {
consumer_task = kthread_run(consumer_kthread, NULL, "list-consumer");
return 0;
}
static void __exit exitModule(void) {
/* this call will cause rmmod to freeze forever */
kthread_stop(consumer_task);
}
module_init(initModule);
module_exit(exitModule);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("My Module");
【问题讨论】:
-
您的
take_from_list函数中有三个地方返回NULL。在这三个地方之一,它返回时仍然持有sem_list_consumer信号量。我怀疑这种不一致与您的问题有关。否则,调用者如何判断它是否需要释放信号量? -
这可能会导致以后出现问题,但是即使您只是加载和卸载模块,而没有调用生产者代码,我的问题仍然存在。此外,只有在某些事情中断/唤醒 kthread 时才会出现问题,但这正是我未能实现的。
-
您已经确定生产者代码不是问题,因此错误在于您的消费者代码。由于缺少代码,很难说清楚,但我怀疑您需要做的就是在
take_from_list函数中的mutex_unlock(&list_lock);和return NULL;行之间调用up(&sem_list_consumer);。除非您希望take_from_list返回NULL时仍会保留sem_list_consumer信号量,在这种情况下,您需要以某种方式处理take_from_list有时返回NULL而不保留信号量。
标签: c multithreading linux-kernel semaphore kernel-module