用“ return”或“ do_exit()”终止内核线程是一种好习惯吗?

时间:2019-06-10 07:43:10

标签: c linux multithreading linux-kernel linux-device-driver

我的目标是仅通过执行固件下载的ONCE来执行驱动程序的探测功能中的内核线程。

为简单起见,放入示例代码(不是实际的代码)

#include<linux/module.h>
#include<linux/init.h>
#include<linux/kthread.h>

MODULE_LICENSE("GPL");

struct task_struct *kthread;


static int thread_func(void* data)
{
    printk("In %s function\n", __func__);
    return 0;
}

static int hello_init(void)
{
    int ret = 0;

    printk("Hello World\n");
    kthread = kthread_run(thread_func,
            NULL, "kthread-test");
    if (IS_ERR(kthread)) {
        ret = PTR_ERR(kthread);
        printk("Unable to run kthread err %d\n", ret);
        return ret;
    }
    return 0;
}


static void hello_exit(void)
{
    printk("Bye World\n");

}

我没有使用以下任何一项,因为:

  1. kthread_should_stop()-用于不需要的连续执行
  2. kthread_stop(struct task_struct *thread)-如果包含在模块退出功能中,则会导致内核崩溃,因为线程在执行一次后就已经终止了

这是正确的方法吗?如果没有,请建议

1 个答案:

答案 0 :(得分:6)

您需要确保在模块退出功能返回之前线程已经消失。一种方法是使用“完成”结构。

基本思想是在启动线程之前初始化完成结构,使线程在退出时将完成结构标记为“完成”,并让模块退出功能(或其他功能)等待完成结构被标记完整。

  1. 先决条件

    #include <linux/completion.h>
    
  2. 初始化完成结构

    如果完成结构变量是静态分配的,则可以使用DECLARE_COMPLETION宏在变量定义中对其进行初始化:

    static DECLARE_COMPLETION(thread_done);
    

    (当完成结构变量在堆栈上时,还有一个DECLARE_COMPLETION_ONSTACK宏可供使用。)

    或者,可以定义未初始化的struct completion(例如,作为动态分配的结构的成员)并随后通过调用init_completion(...)进行初始化:

    struct completion thread_done;
    
    ...
    
    init_completion(&thread_done);
    
  3. 创建线程

    kthread = kthread_run(thread_func, NULL, "kthread-test");
    if (IS_ERR(kthread)) {
        complete(&thread_done); /* <-- may or may not be required */
        ret = PTR_ERR(kthread);
        return ret;
    }
    

    在上面,如果kthread_run(...)失败,则在某些代码稍后等待完成的情况下,完成结构会标记为“完成”。如果保证没有任何东西以后可以等待完成,则可以忽略complete(&thread_done);调用。

  4. 退出线程

    该线程应调用do_exit(...)将该线程标记为“完成”,而不是从线程函数返回或调用complete_and_exit(...)

    complete_and_exit(&thread_done, 0);
    

    呼叫complete_and_exit(...)比分别呼叫complete(...)do_exit(...)更为安全。如果分别调用complete(...)do_exit(...),则有可能在complete(...)返回时已经卸载了模块代码,因此线程可以执行不存在或随机的代码。调用complete_and_exit(...)可以避免这种情况的发生,因为该函数存在于模块代码之外,并且永远不会返回。

  5. 确保线程已完成

    要确保线程已完成,请调用wait_for_completion(...)

    wait_for_completion(&thread_done);
    

    返回时,线程将要么已经退出,要么在对complete_and_exit(...)的调用中仍在运行,并且即将退出。无论哪种情况,它都不再运行任何模块代码,因此可以安全地继续。