【问题标题】:Linux kernel - how to stop a kthread waiting for a semaphore?Linux内核-如何停止等待信号量的kthread?
【发布时间】: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(&amp;list_lock);return NULL; 行之间调用 up(&amp;sem_list_consumer); 。除非您希望 take_from_list 返回 NULL 时仍会保留 sem_list_consumer 信号量,在这种情况下,您需要以某种方式处理 take_from_list 有时返回 NULL 而不保留信号量。

标签: c multithreading linux-kernel semaphore kernel-module


【解决方案1】:

您需要向等待进程发送信号。然后该进程将从 TASK_INTERRUPTABLE 更改为 TASK_RUNNING,然后将安排并运行 down_interruptable 返回 EINTR。

【讨论】:

  • 感谢您的回答。您能否举一个简短的示例,如何向 kthread 发送信号?我尝试在内核源代码中挖掘它以了解它是如何工作的,但我不确定如何准确地做到这一点,以及需要什么(例如 siginfo 结构)以及我应该在其中写入什么。
【解决方案2】:

缺少的代码意味着这个答案只能使用有根据的猜测。

以下是我对您丢失的代码的假设:

  1. 如果take_from_list 返回一个有效条目,consumer_kthread 会对该条目进行处理并调用up(&amp;sem_list_consumer) 以匹配take_from_list 中对down_interruptible(&amp;sem_list_consumer) 的调用。

  2. 如果take_from_list 返回NULLconsumer_kthread 会对NULL 指针进行一些处理,并假定sem_list_consumer 信号量处于其原始状态。

鉴于这些假设,take_from_list 中存在一个错误,因为它有时会返回 NULL 而没有先调用 up(&amp;sem_list_consumer)。这意味着对take_from_list 的任何后续调用都将阻塞对down_interruptible(&amp;sem_list_consumer) 的调用,直到它们被信号中断。要修复该错误,请将take_from_list 更改为始终将信号量保持在它返回NULL 时离开的状态:

struct some_struct * take_from_list() {
    int rv;
    some_struct *entry;

    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);
        up(&sem_list_consumer);  /* <-- this line was missing */
        return NULL;
    } else {
        entry = list_last_entry(&list, struct some_struct, list);
        if (entry) {
            list_del(&entry->list);
        }
    }

    mutex_unlock(&list_lock);
    return entry;
}

修正

如果consumer_kthread 的缺失代码中有某个地方将自身添加到等待队列并进入睡眠状态,则应在唤醒条件中包含对kthread_should_stop() 的调用。唤醒条件应满足其他条件 OR (||) kthread_should_stop()

从您的exitModule 函数调用kthread_stop(consumer_task) 将唤醒消费者线程。如果它正在等待一个事件,它首先会检查唤醒条件,如果不满足则返回睡眠。通过将kthread_should_stop() 包括为可能的唤醒条件之一,您可以确保消费者线程不会立即返回睡眠状态。

【讨论】:

  • 你是对的,缺少的行可能会导致问题。但这不是根本原因。整个设计是建立在 kthread 在列表为空时休眠并在列表中有内容时由生产者唤醒的想法(这就是我明确提到生产者-消费者问题的原因)。因此,如果列表为空,则线程将永远不会到达您添加缺失行的那一点。您能否举例说明如何向内核中的 kthread 发送信号?我想这会解决我的问题,因为我错过了中断信号并且不知道如何发送它。
  • 您的exitModule 函数应该能够以与生产者线程唤醒它相同的方式唤醒消费者线程。 exitModule 应该不需要向消费者线程发送信号,但它应该在唤醒消费者线程之前设置一个标志,并且在设置该标志时消费者线程应该采取适当的行动。
  • 不使用单独的标志,您可以只调用kthread_should_stop() 作为等待事件唤醒条件的一部分。实际上,这可能是最简单的方法。我会相应地修改我的答案。忘记信号 - 这是解决问题的钝锤方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-19
  • 2012-10-18
  • 2021-04-27
  • 2011-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多