使用 msleep 唤醒处于睡眠状态的内核线程

wakeup a kernel thread that is in sleep using msleep

我已经使用 "kthread_run" 创建并启动了一个内核线程。 task1 = kthread_run(flash, NULL, "LED_thread"); 我基本上必须以 4 种不同的模式闪烁 LED,并具有不同的开关时间。我为此使用 "msleep"。例如,如果在一个模式中,关闭时间为 10 秒,我会关闭 LED,然后使用 "msleep(10000)"。现在的问题是,如果模式从用户 space 更改,模式仅在 10 秒延迟完成后才会更改。

为了解决这个问题,我启动了第二个线程来监视 LED 的模式,一旦发现模式发生变化,它就会尝试使用 "wake_up_process(task1)"[=10 唤醒第一个线程=]

但这不会唤醒第一个线程。

所以我的问题是: 1. wake_up_process 唤醒睡眠中的线程(从 运行 队列中调度)? 2.如果不行,有没有其他的实现方式

提前致谢 斯里克

在我的 "flash" 线程函数中,我有

更新:

@Craig,这是您在单个线程中实现整个事情的一个很好的例子。在我看来,线程占据了很多 cpu 周期,所以线程越少越好。你同意吗?
除了你避免其他线程的例子之外的替代方法是:
1. 在驱动程序中使用 ioctl 并使用此 ioctl 而不是我正在使用的 sysfs 属性从用户 space 设置模式,并在收到 ioctl 命令时将信号发送到工作线程以将其唤醒.
2. 发送信号唤醒worker线程在mode sysfs属性的store函数中(驱动中的函数,当mode在user中设置时会被调用space)

一种方法是在线程 A 中使用 msleep_interruptible 而不是 msleep。当然不知道,但您可能必须让线程 B 通过发送信号而不是 [=16] 来唤醒=].试试看。

您可能需要一些互锁(例如自旋锁)来防止线程 B 在线程 A 已经唤醒[并且已经看到闪烁类型更改]后发送信号的竞争条件。

或者,线程 A 可以记住旧的类型,如果没有变化就继续剩余的睡眠(这可以补偿不需要锁定的竞争)


这是每个的内核代码:

/**
 * msleep - sleep safely even with waitqueue interruptions
 * @msecs: Time in milliseconds to sleep for
 */
void msleep(unsigned int msecs)
{
    unsigned long timeout = msecs_to_jiffies(msecs) + 1;

    while (timeout)
        timeout = schedule_timeout_uninterruptible(timeout);
}

EXPORT_SYMBOL(msleep);

/**
 * msleep_interruptible - sleep waiting for signals
 * @msecs: Time in milliseconds to sleep for
 */
unsigned long msleep_interruptible(unsigned int msecs)
{
    unsigned long timeout = msecs_to_jiffies(msecs) + 1;

    while (timeout && !signal_pending(current))
        timeout = schedule_timeout_interruptible(timeout);
    return jiffies_to_msecs(timeout);
}

EXPORT_SYMBOL(msleep_interruptible);

更新:

Thanks for pointing to the possible race condition as well. I will try with semaphores/mutexes and see

如果线程 B 进行监视(例如读取 /dev/whatever/proc/whatever)并写入全局 [可能在 "private" 数据结构中],则可能没有必要。您可能必须使用 atomic fetch/store 或 CAS,但将其标记为 volatile 可能就足够了。这是因为 B 是 [唯一的] 作者,而 A 是 [唯一的] reader.

As far as sending signals in kernel space, can we do that? i thought signals are only for userspace. Could you give me an example if that is not the case.

由于 msleep_interruptible 存在于 all,并且它寻找挂起的信号,QED 就在那里。

但是,这里有一些代码可以证明这一点。 allow_signal 中的评论自 2003 年的补丁以来一直存在:

/*
 * Let kernel threads use this to say that they allow a certain signal.
 * Must not be used if kthread was cloned with CLONE_SIGHAND.
 */
int allow_signal(int sig)
{
    if (!valid_signal(sig) || sig < 1)
        return -EINVAL;

    spin_lock_irq(&current->sighand->siglock);
    /* This is only needed for daemonize()'ed kthreads */
    sigdelset(&current->blocked, sig);
    /*
     * Kernel threads handle their own signals. Let the signal code
     * know it'll be handled, so that they don't get converted to
     * SIGKILL or just silently dropped.
     */
    current->sighand->action[(sig)-1].sa.sa_handler = (void __user *)2;
    recalc_sigpending();
    spin_unlock_irq(&current->sighand->siglock);
    return 0;
}

EXPORT_SYMBOL(allow_signal);

int disallow_signal(int sig)
{
    if (!valid_signal(sig) || sig < 1)
        return -EINVAL;

    spin_lock_irq(&current->sighand->siglock);
    current->sighand->action[(sig)-1].sa.sa_handler = SIG_IGN;
    recalc_sigpending();
    spin_unlock_irq(&current->sighand->siglock);
    return 0;
}

EXPORT_SYMBOL(disallow_signal);

就用内线吧。那可能是 do_send_sig_info。使用像 SIGUSR1 这样无伤大雅的东西(或者你可能需要使用 "RT" 信号,因为它们在排队)。

在内核中发送一个信号意味着该信号被或运算​​到任务的挂起信号掩码中,并且该任务被标记为可运行(例如被唤醒)。

用户空间 "jump to handler" 仅在给定任务即将重新进入 [重新进入之前的最后一件事] 用户空间时 出现。如果设置了信号,内核将设置用户空间堆栈帧并将执行切换到处理程序。

在内核线程中,它 不会 导致跳转到 "signal handler",因为没有等效项。内核线程必须查看其挂起的信号掩码才能注意到它。有没有跳转。它不像那样是中断。并且,内核线程必须手动清除挂起的掩码[否则,msleep_interruptible此后将总是 return立即

要检测并清除信号,请使用以下代码:

while (signal_pending(current)) {
    siginfo_t info;
    unsigned long signo;

    signo = dequeue_signal_lock(current, &current->blocked, &info));

    switch (signo) {
    case SIGUSR1:
        break;
    }
}

更新#2:

The msleep_interruptible was not wakeable using wake_up_process, but only with send_sig_info. I think msleep_interruptible is nothing but : setstate(TASK_INTERRUPTIBLE) and schedule() with delay, so i had expected wake_up_process to wakeup the thread sleeping with msleep_interruptible.

[AFAIK] 你必须发送一个信号来提前终止睡眠,因此我最初的评论是关于使用信号(并使用 do_send_sig_info)。有时这只是反复试验。我可能也尝试过 wake_up_process。但是,当这不起作用时,我会开始环顾四周 [通过查看 msleep* 代码]。

But neverthless it would be interesting to understand if it is possible to implement that in a single thread.

是的。它需要一点重组和一两个额外的变量。

My blink thread should set LED on for a second

我们称这个为blink_interval_on

and depending on the mode it is in, has to set LED off after the 1 second for about 10 seconds or 1 second.

我们称这个为blink_interval_off

If we implement it in a single thread, polling will be delayed by those 10 seconds right?

[概念上] 的关键变化是赋予 msleep 的值不必是 整个 间隔(例如 blink_interval_*),但可以是更小的 fixed 区间。我们称之为 sleep_fixed.

我们希望选择此选项以提供您想要的响应能力。如果它 非常 小(例如一微秒),我们就醒得太频繁了。如果我们选择 large 值,例如 1 秒或 10 秒,则响应变为 "sluggish".

因此,一个合适的值是 10-100 毫秒。速度足够慢,我们不会因频繁唤醒而占用资源,但速度足够快,如果闪烁模式发生变化,用户不会注意到差异。

现在,我们必须跟踪给定闪烁间隔的剩余时间 [=sleep_fixed],并在间隔耗尽时翻转 LED 状态。也就是说,我们手动跟踪完整的 msleep 曾经为我们做什么。

Can you give me an example for that.

好的,这里有一个 crude/pseudo-code 版本来 [希望] 解释我的意思:

// NOTE: all times are in milliseconds

int blink_interval_on;                  // LED "on" interval
int blink_interval_off;                 // LED "off" interval

int blink_interval_remaining;           // time remaining in current interval
int curmode;                            // current blink mode

int blink_on_list[2] = { 1000, 1000 };
int blink_off_list[2] = { 1000, 10000 };

int sleep_fixed;                        // small sleep value

// set_led_on -- set LED on or off
void
set_led_on(int onflg)
{
}

// msleep -- sleep for specified number of milliseconds
void
msleep(int ms)
{
}

// getnewmode -- get desired blink mode
// RETURNS: 0=1 second, 1=10 seconds, etc (-1=stop)
int
getnewmode(void)
{
    int newmode;

    // do whatever is necessary, as you're doing now ...
    newmode = 0;

    return newmode;
}

// ledloop -- main thread for single thread case
void
ledloop(void)
{
    int newmode;
    int sleep_fixed;
    int curstate;
    int sleep_current;

    // force an initial state change
    curmode = -2;

    // this is our "good enough" interval
    sleep_fixed = 10;

    while (1) {
        // look for blink mode changes
        newmode = getnewmode();
        if (newmode < 0)
            break;

        // got a mode change
        // force a change at the bottom
        if (curmode != newmode) {
            curstate = 0;
            curmode = newmode;
            blink_interval_remaining = 0;
        }

        // set up current sleep interval
        sleep_current = sleep_fixed;
        if (sleep_current > blink_interval_remaining)
            sleep_current = blink_interval_remaining;

        // do a sleep
        // NOTE: because the sleep is so short, we can use the simple msleep
        // this is the "good enough" way ...
        if (sleep_current > 0) {
            msleep(sleep_current);
            blink_interval_remaining -= sleep_current;
        }

        // flip the LED state at interval end
        if (blink_interval_remaining <= 0) {
            curstate = ! curstate;
            set_led_on(curstate);

            // set new interval
            if (curstate)
                blink_interval_remaining = blink_on_list[curmode];
            else
                blink_interval_remaining = blink_off_list[curmode];
        }
    }
}