kthread 在没有 运行 的情况下停止

kthread stopped without running

如果我使用 kthread_run 创建内核线程,然后立即 kthread_stop,内核线程可能会在没有 运行ning 的情况下停止。我在Linux-5.4.73

中查看了kthread_runkthread_stop的源代码
/**
 * kthread_run - create and wake a thread.
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @namefmt: printf-style name for the thread.
 *
 * Description: Convenient wrapper for kthread_create() followed by
 * wake_up_process().  Returns the kthread or ERR_PTR(-ENOMEM).
 */
#define kthread_run(threadfn, data, namefmt, ...)              \
({                                     \
    struct task_struct *__k                        \
        = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
    if (!IS_ERR(__k))                          \
        wake_up_process(__k);                      \
    __k;                                   \
})
/**
 * kthread_stop - stop a thread created by kthread_create().
 * @k: thread created by kthread_create().
 *
 * Sets kthread_should_stop() for @k to return true, wakes it, and
 * waits for it to exit. This can also be called after kthread_create()
 * instead of calling wake_up_process(): the thread will exit without
 * calling threadfn().
 *
 * If threadfn() may call do_exit() itself, the caller must ensure
 * task_struct can't go away.
 *
 * Returns the result of threadfn(), or %-EINTR if wake_up_process()
 * was never called.
 */
int kthread_stop(struct task_struct *k)
{
    struct kthread *kthread;
    int ret;

    trace_sched_kthread_stop(k);

    get_task_struct(k);
    kthread = to_kthread(k);
    set_bit(KTHREAD_SHOULD_STOP, &kthread->flags);
    kthread_unpark(k);
    wake_up_process(k);
    wait_for_completion(&kthread->exited);
    ret = k->exit_code;
    put_task_struct(k);

    trace_sched_kthread_stop_ret(ret);
    return ret;
}

看来内核线程应该在kthread_stopreturn之前就被唤醒了,但也有可能没有。我真的很困惑,有人可以帮助我吗?

我的测试代码如下

test1.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/semaphore.h>
#include <linux/spinlock.h> 

MODULE_LICENSE("GPL");

static struct task_struct *t1;
static struct task_struct *t2;
static struct task_struct *t3;

static int func(void *__para)
{
    const char *msg = (const char *)__para;
    printk("%s %s\n", __func__, msg);
    /* Wait for kthread_stop */
    set_current_state(TASK_INTERRUPTIBLE);
    while (!kthread_should_stop()) {
        schedule();
        set_current_state(TASK_INTERRUPTIBLE);
    }
    set_current_state(TASK_RUNNING);
    printk("%s %s return\n", __func__, msg);
    return 0;
}
static int __init start_init(void)
{
    printk(KERN_INFO "Thread Creating...\n");
    t1 = kthread_run(func, "t1", "t1");
    if (IS_ERR(t1)) {
        WARN_ON(1);
        return 0;
    }

    t2 = kthread_run(func, "t2", "t2");
    if (IS_ERR(t2)) {
        WARN_ON(1);
        return 0;
    }
    printk("Stopping t2\n");
    kthread_stop(t2);
    printk("t2 stopped\n");

    t3 = kthread_run(func, "t3", "t3");
    if (IS_ERR(t3)) {
        WARN_ON(1);
        return 0;
    }

    return 0;
}
static void __exit end_exit(void)
{
    printk(KERN_INFO "Cleaning Up...\n");
    if (IS_ERR(t1))
        return;
    printk("Stopping t1\n");
    kthread_stop(t1);
    printk("t1 stopped\n");

    printk("Stopping t3\n");
    kthread_stop(t3);
    printk("t3 stopped\n");
}

module_init(start_init)
module_exit(end_exit)

生成文件

obj-m += test1.o

all:
    $(MAKE) -C /lib/modules/$(shell uname -r)/build M=`pwd`

clean:
    $(MAKE) -C /lib/modules/$(shell uname -r)/build M=`pwd` clean

命令运行

sudo insmod test1.ko
sudo rmmod test1

第一个运行

的dmesg
[10914.046211] Thread Creating...
[10914.046515] func t1
[10914.046530] Stopping t2
[10914.046531] func t2
[10914.046533] func t2 return
[10914.046538] t2 stopped
[10914.046555] func t3
[10938.895544] Cleaning Up...
[10938.895545] Stopping t1
[10938.895552] func t1 return
[10938.895561] t1 stopped
[10938.895562] Stopping t3
[10938.895566] func t3 return
[10938.895587] t3 stopped

t2在本次停止前已经执行

第二个的dmesg运行

[10940.775771] Thread Creating...
[10940.776109] func t1
[10940.776126] Stopping t2
[10940.776138] t2 stopped
[10940.776162] func t3
[10956.375606] Cleaning Up...
[10956.375607] Stopping t1
[10956.375613] func t1 return
[10956.375674] t1 stopped
[10956.375674] Stopping t3
[10956.375678] func t3 return
[10956.375697] t3 stopped

t2 没有 运行ning 就停止了。但是 t1 和 t3 在停止之前没有立即停止 运行。

(此答案对应 Linux 内核版本 5.4。)

新创建的内核线程任务执行“kernel/kthread.c”中的函数kthread。如果一切顺利,kthread 将调用 kthread_run(或 kthread_create 的)threadfn 参数引用的线程函数。然而,在调用threadfn 函数指针之前的最后测试是检查内核线程的KTHREAD_SHOULD_STOP 位。如果设置了 KTHREAD_SHOULD_STOP 位,则不会调用 threadfn 函数指针,新的内核线程任务将调用 do_exit,退出代码为 -EINTRkthread函数末尾的相关代码如下:

    ret = -EINTR;
    if (!test_bit(KTHREAD_SHOULD_STOP, &self->flags)) {
        cgroup_kthread_ready();
        __kthread_parkme(self);
        ret = threadfn(data);
    }
    do_exit(ret);

虽然kthread_run在returning之前唤醒了新创建的内核线程任务,但是可以调用kthread_stop函数并设置内核线程的KTHREAD_SHOULD_STOP 位在内核线程到达其 KTHREAD_SHOULD_STOP 位的最终检查之前调用 threadfn 函数指针。在这种情况下,内核线程将在 threadfn 函数指针未被调用的情况下退出。

OP的原始代码可以更改为打印kthread_stop的return值,如下所示:

    int exit_code;

    /* ... */
    printk("Stopping t2\n");
    exit_code = kthread_stop(t2);
    printk("t2 stopped, exit code %d\n", exit_code);

然后它应该显示线程 t2 以代码 -EINTR(可能是 -4)退出,如果它在入口函数 func 被调用之前被停止。