Multithreading linux内核模块中的bug识别
我在给迈克尔做标记,因为他是第一个。感谢osgx和本月最佳员工提供更多信息和帮助 我试图识别消费者/生产内核模块中的一个bug。这是我在大学里学习的一个问题。我的助教没能弄明白,我的教授说如果我上传到网上也没问题(他认为Stack没法弄明白)Multithreading linux内核模块中的bug识别,multithreading,debugging,linux-kernel,synchronisation,Multithreading,Debugging,Linux Kernel,Synchronisation,我在给迈克尔做标记,因为他是第一个。感谢osgx和本月最佳员工提供更多信息和帮助 我试图识别消费者/生产内核模块中的一个bug。这是我在大学里学习的一个问题。我的助教没能弄明白,我的教授说如果我上传到网上也没问题(他认为Stack没法弄明白) 我已经包括了模块、makefile和Kbuild 运行程序并不能保证错误会自动出现 我认为这个问题发生在第30行,因为一个线程可能会冲向第36行,并使其他线程处于饥饿状态。我的教授说那不是他想要的 无关问题:第40行的目的是什么?这对我来说似乎不合适,但
- 我已经包括了模块、makefile和Kbuild
- 运行程序并不能保证错误会自动出现
- 我认为这个问题发生在第30行,因为一个线程可能会冲向第36行,并使其他线程处于饥饿状态。我的教授说那不是他想要的
- 无关问题:第40行的目的是什么?这对我来说似乎不合适,但我的教授说它有目的
- 我的教授说这个错误很微妙。错误不是死锁
- 我的方法是确定关键部分和共享变量,但我被难住了。我不熟悉跟踪(作为一种调试方法),并且被告知虽然跟踪可能有帮助,但没有必要确定问题
#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;