Multithreading linux内核模块中的bug识别

Multithreading linux内核模块中的bug识别,multithreading,debugging,linux-kernel,synchronisation,Multithreading,Debugging,Linux Kernel,Synchronisation,我在给迈克尔做标记,因为他是第一个。感谢osgx和本月最佳员工提供更多信息和帮助 我试图识别消费者/生产内核模块中的一个bug。这是我在大学里学习的一个问题。我的助教没能弄明白,我的教授说如果我上传到网上也没问题(他认为Stack没法弄明白) 我已经包括了模块、makefile和Kbuild 运行程序并不能保证错误会自动出现 我认为这个问题发生在第30行,因为一个线程可能会冲向第36行,并使其他线程处于饥饿状态。我的教授说那不是他想要的 无关问题:第40行的目的是什么?这对我来说似乎不合适,但

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

我试图识别消费者/生产内核模块中的一个bug。这是我在大学里学习的一个问题。我的助教没能弄明白,我的教授说如果我上传到网上也没问题(他认为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");
文件:Makefile

# 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:

tgs_exit()
中清理时,该函数执行以下操作,而不保持自旋锁:

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

一个即将结束的线程可能会在检查和调用
kthread\u stop()

之间将它的
task\u info[i]设置为NULL

你声称这是一个即将到来的考试中的问题,它是由授课人发布的。他们为什么要这样做?然后你说TA没能解决问题。如果助教做不到,谁能指望学生通过考试

(教授)不认为斯塔克能弄明白

如果说这个网站的水平不好,我绝对同意。但是,声称它低于随机大学的预期水平仍然是一种延伸。如果没有这样的要求,我再次问学生们应该怎样做。如果问题解决了怎么办

代码本身不适合教学,因为它与常见的习惯用法有太多的偏离

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

要么内核线程自行退出,要么kthread_stop(可能还有其他东西)是执行此操作所必需的

如果前者为真,则代码在释放后可能会被使用。tgs_exit测试指针是否正确后,目标线程可能已退出。可能是在kthread_停止调用之前,也可能是在执行时。无论哪种方式,传递的指针都可能是过时的,因为该区域已被正在退出的线程释放

如果后者为true,则代码会由于清理不足而遭受资源泄漏-如果在所有线程退出后执行tgs_exit,则不会有kthread_停止调用

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

为了便于讨论,我们假设代码被编译到内核中(而不是作为模块加载)。假设在关机时调用exit func

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

代码位于模块中这一事实使问题更加尖锐:除非您停止kthread_,否则无法判断目标线程是否已消失。特别是“参与者”线程:

actor_info->task = NULL;
因此,在退出处理程序中跳过线程,退出处理程序现在可以完成并让内核卸载模块本身

spin_unlock(&lock);
return 0;
。。。但是这个代码(位于模块中!)可能还没有执行

如果代码始终使用kthread_stop,则不会出现这种情况

另一个问题是,编剧叫醒了所有人(所谓的“雷鸣般的羊群问题”),而不是最多一个演员

也许人们应该发现的问题是每集最多只有一个演员?也许当有剧集已经写完但还没有演完时,模块可以退出

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

另外一个有趣的事实是,围绕写操作锁定以显示_over在正确性方面没有任何作用


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

模块作者没有检查过当代版本的完井文档:-它是否安全?在哪里大概在bug这个词附近。接近三个中的任何一个。@osgx感谢您的回复,但我不确定我是否理解您的意思。你是说让我看看你发布的链接中描述的错误案例吗?如果是这样的话,我看不出这些情况在这个模块中有多明显。我得到的提示是要注意completion变量和锁的用法。我不认为其他变量有问题,因为共享变量是受保护的。嗯,过去一年的选举在幻灯片14中有完全相同的等待/重新安排完成顺序。。。我建议使用lttng进行跟踪(但使用kernelshark的ftrace也可以);所有的演员都会在某个时间醒来。。。现在我们可能有多达4个线程(所有参与者和作者;例如
spin_unlock(&lock);
return 0;