我正在纪念迈克尔,因为他是第一个。感谢osgx和当月的员工提供更多信息和帮助。
我正在尝试识别消费者/生产内核模块中的错误。这是我在大学课程中遇到的一个问题。我的助教无法理解,我的教授说如果我在网上上传就没关系(他不认为Stack可以搞清楚!)。
档案: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:
答案 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的锁定在正确性方面没有任何作用。
还有更多问题,我很可能错过了一些问题。事实上,我认为质量很差。它看起来不像现实世界。