识别linux内核模块中的bug

时间:2017-05-17 01:38:18

标签: multithreading debugging linux-kernel synchronisation

我正在纪念迈克尔,因为他是第一个。感谢osgx和当月的员工提供更多信息和帮助。

我正在尝试识别消费者/生产内核模块中的错误。这是我在大学课程中遇到的一个问题。我的助教无法理解,我的教授说如果我在网上上传就没关系(他不认为Stack可以搞清楚!)。

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

档案: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

文件: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:

2 个答案:

答案 0 :(得分:2)

tgs_exit()中清理时,该功能执行以下操作而不按住自旋锁:

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

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

答案 1 :(得分:2)

我在这里很困惑。

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

  

(教授)认为Stack无法弄清楚

如果声称该网站上的级别不好,我绝对同意。但是,声称它低于随机大学预期的水平是一个延伸。如果没有这种说法,我再次询问学生应该如何做到这一点。如果问题得到解决怎么办?

代码本身不适合教学,因为它偏离了常见的习语。

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

要么自己退出的内核线程会清除所有内容,或者需要kthread_stop(可能还有别的东西)。

如果前者是真的,那么代码可能会在免费后使用。在tgs_exit测试指针之后,目标线程可以退出。也许在kthread_stop调用之前或者可能就像执行它一样。无论哪种方式,传递的指针都可能是陈旧的,因为该区域已经被正在退出的线程释放。

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

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

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

有一个设计问题是有2个退出机制,它会转换为错误,因为它们没有协调。这种情况的一种可能解决方案是为写入者设置一个标志,并等待写入计数器降为0。

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

actor_info->task = NULL;

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

spin_unlock(&lock);
return 0;

...但是这段代码(位于模块中!)可能还没有执行。

如果代码遵循惯用语,如果总是使用kthread_stop,那就不会发生这种情况。

其他问题是作家唤醒每个人(所谓的“雷鸣般的群体问题”),而不是最多只有一个演员。

也许应该找到的错误是每一集最多只有一个演员?也许该模块可以在写入但没有采取行动的情况下退出?

代码非常奇怪,如果在用户空间中显示了一个合理的线程安全队列实现,你应该看看这里展示的内容是不合适的。例如,为什么它在没有检查剧集的情况下立即阻止?

另一个有趣的事实是,围绕写入show_over的锁定在正确性方面没有任何作用。

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