【问题标题】:How to join a thread in Linux kernel?如何在 Linux 内核中加入线程?
【发布时间】:2015-04-30 04:40:49
【问题描述】:

主要问题是:我们如何才能等待 Linux 内核中的一个线程完成?我看过一些关于在 Linux 内核中处理线程的正确方法的帖子,但我不确定我们如何等待主线程中的单个线程完成(假设我们需要线程 [3] 完成然后继续):

#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/slab.h>

void *func(void *arg) {
    // doing something
    return NULL;
}

int init_module(void) {
    struct task_struct* thread[5];
    int i;
    for(i=0; i<5; i++) {
        thread[i] = kthread_run(func, (void*) arg, "Creating thread");
    }
    return 0;
}

void cleanup_module(void) {
    printk("cleaning up!\n");
}

【问题讨论】:

  • 用户空间线程,还是kthread?

标签: linux multithreading linux-kernel


【解决方案1】:

AFAIK 内核中没有 pthread_join() 的等价物。另外,我觉得你的模式(启动一堆线程并只等待其中一个)在内核中并不常见。话虽如此,内核确实很少有同步机制可用于实现您的目标。

请注意,这些机制不能保证线程完成,它们只会让主线程知道他们完成了他们应该做的工作。真正停止这种脚步并释放所有资源可能还需要一些时间。

信号量

您可以创建一个锁定的信号量,然后在您的主线程中调用down。这会让它进入睡眠状态。然后,您将在退出之前将 up 这个信号量放在您的线程中。比如:

struct semaphore sem;

int func(void *arg) {
    struct semaphore *sem = (struct semaphore*)arg; // you could use global instead

    // do something

    up(sem);
    return 0;
}

int init_module(void) {
    // some initialization
    init_MUTEX_LOCKED(&sem);
    kthread_run(&func, (void*) &sem, "Creating thread");
    down(&sem); // this will block until thread runs up()
}

这应该可行,但不是最佳解决方案。我提到这一点是因为它是一种在用户空间中也使用的已知模式。内核中的信号量是为大多数可用的情况而设计的,并且这种情况具有很高的争用性。因此创建了针对这种情况优化的类似机制。

完成次数

您可以使用以下方式声明完成:

struct completion comp;
init_completion(&comp);

或:

DECLARE_COMPLETION(comp);

然后您可以使用wait_for_completion(&amp;comp); 代替down() 在主线程中等待,并在您的线程中使用complete(&amp;comp); 代替up()

这是完整的示例:

DECLARE_COMPLETION(comp);
struct my_data {
    int id;
    struct completion *comp;
};

int func(void *arg) {
    struct my_data *data = (struct my_data*)arg;
    // doing something

    if (data->id == 3)
        complete(data->comp);

    return 0;
}

int init_module(void) {
    struct my_data *data[] = kmalloc(sizeof(struct my_data)*N, GFP_KERNEL);

    // some initialization
    for (int i=0; i<N; i++) {
        data[i]->comp = &comp;
        data[i]->id = i;
        kthread_run(func, (void*) data[i], "my_thread%d", i);
    }
    wait_for_completion(&comp); // this will block until some thread runs complete()
}

多线程

我真的不明白为什么你会启动 5 个相同的线程并且只想等待第三个线程,但当然你可以向每个线程发送不同的数据,并使用一个描述它的 id 的字段,然后调用 upcomplete 仅当此 id 等于 3 时。如完成示例中所示。还有其他方法可以做到这一点,这只是其中之一。

注意事项

在使用其中任何一个之前,请先阅读有关这些机制的更多信息。有一些重要的细节我没有在这里写。这些示例也经过简化且未经测试,仅用于展示总体思路。

【讨论】:

  • 如果有一堆线程,你如何将struct completion与单个线程相关联? @krzysztof
  • @Farhad:如果您只想等待一个线程,则不必这样做。您可以将它传递给所有线程并在除一个线程之外的所有线程中忽略它。只有这个人会打电话给complete
  • 如果这是通用线程函数:void *func(void *arg) { struct completion comp; init_completion(&amp;comp); // doing something return NULL; }你怎么能在主线程中调用wait_for_competion(&amp;comp),它只影响一个线程?
  • @Farhad:这样做是错误的。您应该完全按照信号量示例中的描述执行此操作 - 您在主线程中声明完成并将其传递给线程函数。我添加了包含多个线程和完成的完整示例。
  • @KrzysztofAdamski:这些同步机制都不足以确定线程功能已结束。这是因为信号动作是在该函数内部而不是在它之后执行的。因此,即使在包含函数代码的模块已被卸载后,该函数也可能被执行。这会导致崩溃。
【解决方案2】:

kthread_stop() 是内核等待线程结束的方式。

除了等待之外,kthread_stop() 还为等待的线程设置 should_stop 标志并在需要时唤醒它。对于无限重复某些动作的线程很有用。

对于单次任务,通常使用 work 来代替 kthread 更简单。

编辑: 注意:kthread_stop()只有在kthread(task_struct)结构没有被释放时才能调用。

任何一个线程函数都应该在它发现 kthread_should_stop() 返回 true 之后返回,或者应该在启动线程之前调用 get_task_struct()(并且应该调用 put_task_struct()在 kthread_stop()) 之后。

【讨论】:

  • 那么内核线程实现中joinexit的区别在哪里?
  • joinother 线程执行,exit 由线程自身执行。 should_stop 标志实际上是对线程的提示:“其他线程等待我终止”。如果已设置,则由线程检查该标志并做出反应。没有办法kill内核线程。
  • @Tsyvarev:正如你所写,kthread_stop() 将唤醒线程。这非常重要,这正是我不将其用作一般“加入”替代品的原因。好吧,至少在使用kthread_run 时。不过,它可能更适合kthread_create
  • @KrzysztofAdamski:由于大多数等待实际上是事件等待,所以从kthread_stop() 虚假唤醒不是问题。另一方面,kthread_stop() 是破坏kthread和释放资源的唯一途径。
  • @Tsyvarev:但如果你在睡觉,它可能会搞砸。不过,我不明白你的第二句话。 Kthread调用do_exit()时会销毁并释放资源(返回时会自动运行)。此外,在已经结束的任务上调用kthread_stop 是不安全的,因为do_exit() 最终会调用put_task_struct(),这将释放task_struct。因此,这种技术很有用,但需要谨慎。我想我会编辑我的答案来描述所有这些细节。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-15
  • 2011-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-24
  • 1970-01-01
相关资源
最近更新 更多