4

我正在标记迈克尔,因为他是第一个。感谢 osgx 和本月最佳员工提供更多信息和帮助。

我正在尝试识别消费者/生产内核模块中的错误。这是大学课程给我的一个问题。我的助教想不通,我的教授说我上传到网上也没关系(他认为 Stack 想不通!)。

  • 我已经包含了模块、makefile 和 Kbuild。
  • 运行程序并不能保证错误会出现。
  • 我认为问题出在第 30 行,因为一个线程可能会冲到第 36 行,并饿死其他线程。我的教授说这不是他想要的。
  • 不相关的问题:第 40 行的目的是什么?这对我来说似乎不合适,但我的教授说这是有目的的。
  • 我的教授说这个错误非常微妙。该错误不是死锁。
  • 我的方法是识别关键部分和共享变量,但我很难过。我不熟悉跟踪(作为一种调试方法),并被告知虽然它可能会有所帮助,但没有必要识别问题。

文件:final.c

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

static int actor_kthread(void *);
static int writer_kthread(void *);

static DECLARE_COMPLETION(episode_cv);
static DEFINE_SPINLOCK(lock);
static int episodes_written;
static const int MAX_EPISODES = 21;
static bool show_over;
static struct task_info {
    struct task_struct *task;
    const char *name;
    int (*threadfn) (void *);
} task_info[] = {
    {.name = "Liz", .threadfn = writer_kthread},
    {.name = "Tracy", .threadfn = actor_kthread},
    {.name = "Jenna", .threadfn = actor_kthread},
    {.name = "Josh", .threadfn = actor_kthread},
};

static int actor_kthread(void *data) {
    struct task_info *actor_info = (struct task_info *)data;
    spin_lock(&lock);
    while (!show_over) {
        spin_unlock(&lock);
        wait_for_completion_interruptible(&episode_cv); //Line 30
        spin_lock(&lock);
        while (episodes_written) {
            pr_info("%s is in a skit\n", actor_info->name);
            episodes_written--;
        }
        reinit_completion(&episode_cv); // Line 36
    }

    pr_info("%s is done for the season\n", actor_info->name);
    complete(&episode_cv); //Why do we need this line?
    actor_info->task = NULL;
    spin_unlock(&lock);
    return 0;
}

static int writer_kthread(void *data) {
    struct task_info *writer_info = (struct task_info *)data;
    size_t ep_num;

    spin_lock(&lock);
    for (ep_num = 0; ep_num < MAX_EPISODES && !show_over; ep_num++) {
        spin_unlock(&lock);

        /* spend some time writing the next episode */
        schedule_timeout_interruptible(2 * HZ);

        spin_lock(&lock);
        episodes_written++;
        complete_all(&episode_cv);
    }

    pr_info("%s wrote the last episode for the season\n", writer_info->name);
    show_over = true;
    complete_all(&episode_cv);
    writer_info->task = NULL;
    spin_unlock(&lock);
    return 0;
}

static int __init tgs_init(void) {
    size_t i;
    for (i = 0; i < ARRAY_SIZE(task_info); i++) {
        struct task_info *info = &task_info[i];
        info->task = kthread_run(info->threadfn, info, info->name);
    }
    return 0;
}

static void __exit tgs_exit(void) {
    size_t i;
    spin_lock(&lock);
    show_over = true;
    spin_unlock(&lock);
    for (i = 0; i < ARRAY_SIZE(task_info); i++)
        if (task_info[i].task)
            kthread_stop(task_info[i].task);
}

module_init(tgs_init);
module_exit(tgs_exit);
MODULE_DESCRIPTION("CS421 Final");
MODULE_LICENSE("GPL");

文件:kbuild

Kobj-m := final.o

文件:生成文件

# Basic Makefile to pull in kernel's KBuild to build an out-of-tree
# kernel module

KDIR ?= /lib/modules/$(shell uname -r)/build

all: modules

clean modules:
4

2 回答 2

2

在函数中进行清理时,在tgs_exit()不持有自旋锁的情况下执行以下操作:

    if (task_info[i].task)
        kthread_stop(task_info[i].task);

结束的线程可能会task_info[i].task在检查和调用之间将其设置为 NULL kthread_stop()

于 2017-05-17T22:36:04.900 回答
2

我在这里很困惑。

您声称这是即将到来的考试中的一个问题,并且是由提供课程的人发布的。他们为什么要那样做?那你说TA没能解决问题。如果 TA 做不到,谁能指望学生通过?

(教授)不认为 Stack 能弄明白

如果声称这个网站上的水平很差,我绝对同意。但是,声称它低于随机大学的预期水平还是有些牵强。如果没有这种要求,我再一次问学生们应该怎么做。如果问题解决了怎么办?

代码本身不适合教学,因为它与常见习语有太多偏差。

此处的另一个答案指出了实际问题的一个副作用。也就是说,据说 tgs_exit 中的循环可以与自行退出的线程竞争,并测试 ->task 指针是否为非 NULL,而之后它会变为 NULL。这是否会导致 kthread_stop(NULL) 调用的讨论并不真正相关。

要么一个内核线程自行退出将清除所有内容,要么 kthread_stop(也许还有其他东西)是必要的。

如果前者为真,则代码可能会遭受释放后使用。在 tgs_exit 测试该指针后,目标线程可能已经退出。可能在 kthread_stop 调用之前,或者可能就在它执行时。无论哪种方式,传递的指针都可能是陈旧的,因为该区域已经被退出的线程释放。

如果后者为真,则代码由于清理不足而遭受资源泄漏 - 如果在所有线程退出后执行 tgs_exit,则没有 kthread_stop 调用。

kthread_* api 允许线程退出,因此效果如第一个变体中所述。

为了论证,假设代码被编译到内核中(而不是作为模块加载)。假设退出函数在关机时被调用。

存在一个设计问题,即有 2 个退出机制,并且由于它们不协调而转化为错误。对于这种情况,一个可能的解决方案是设置一个标志让写入器停止,并等待写入器计数器下降到 0。

代码在模块中的事实使问题更加尖锐:除非您使用 kthread_stop,否则您无法判断目标线程是否已消失。特别是“演员”线程:

actor_info->task = NULL;

所以线程在退出处理程序中被跳过,现在可以完成并让内核卸载模块本身......

spin_unlock(&lock);
return 0;

...但是此代码(位于模块中!)可能尚未执行。

如果代码始终使用 kthread_stop,则如果代码遵循惯用语,则不会发生这种情况。

另一个问题是作家会唤醒每个人(所谓的“雷声从众问题”),而不是最多一个演员。

也许人们应该发现的错误是每一集最多只有一个演员?也许当有剧集编写但尚未执行时,模块可以退出?

该代码非常奇怪,如果您在用户空间中看到了线程安全队列的合理实现,您应该会看到这里显示的内容不适合。例如,为什么它会立即阻止而不检查剧集?

还有一个有趣的事实是,锁定对 show_over 的写入对正确性没有任何作用。

还有更多问题,很可能我错过了一些。事实上,我认为这个问题质量很差。它看起来不像现实世界的任何东西。

于 2017-05-18T15:34:30.977 回答