kthread stopped without running

Viewed 652

If I create a kernel thread with kthread_run then kthread_stop it immediately, the kernel thread might be stopped without running. I checked the source code of kthread_run and kthread_stop in Linux-5.4.73

/**
 * 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;
}

It seems that the kernel thread should have been waken up before kthread_stop return, but it might not. I am really confused, could anybody help me?

My test code is as follows.

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)

Makefile

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

The command to run

sudo insmod test1.ko
sudo rmmod test1

The dmesg of the first run

[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 has executed before stopped this time.

The dmesg of the second run

[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 stopped without running. But t1 and t3 which are not stopped immediately run before being stopped.

1 Answers

(This answer corresponds to Linux kernel version 5.4.)

The newly created kernel thread task executes the function kthread in "kernel/kthread.c". If all is well kthread calls the thread function referred to by the kthread_run's (or kthread_create's) threadfn parameter. However, the final test before calling the threadfn function pointer is to check the kernel thread's KTHREAD_SHOULD_STOP bit. If the KTHREAD_SHOULD_STOP bit is set, the threadfn function pointer will not be called and the new kernel thread task will call do_exit with the exit code -EINTR. The relevant piece of code at the end of function kthread is as follows:

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

Although kthread_run wakes up the newly created kernel thread task before returning, it is possible for the kthread_stop function to be called and set the kernel thread's KTHREAD_SHOULD_STOP bit before the kernel thread reaches the final check of its KTHREAD_SHOULD_STOP bit prior to calling the threadfn function pointer. In that case, the kernel thread will exit without the threadfn function pointer ever being called.

OP's original code can be changed to print the return value of kthread_stop as follows:

    int exit_code;

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

Then it should show that thread t2 exited with code -EINTR (probably -4) if it was stopped before the entry function func was called.

Related