我正在为linux内核模块编写代码,并在其中遇到奇怪的行为。 这是我的代码:
int data = 0;
void threadfn1()
{
int j;
for( j = 0; j < 10; j++ )
printk(KERN_INFO "I AM THREAD 1 %d\n",j);
data++;
}
void threadfn2()
{
int j;
for( j = 0; j < 10; j++ )
printk(KERN_INFO "I AM THREAD 2 %d\n",j);
data++;
}
static int __init abc_init(void)
{
struct task_struct *t1 = kthread_run(threadfn1, NULL, "thread1");
struct task_struct *t2 = kthread_run(threadfn2, NULL, "thread2");
while( 1 )
{
printk("debug\n"); // runs ok
if( data >= 2 )
{
kthread_stop(t1);
kthread_stop(t2);
break;
}
}
printk(KERN_INFO "HELLO WORLD\n");
}
基本上我试图等待线程完成然后在那之后打印一些东西。
上面的代码确实实现了该目标,但没有注释WITH "printk("debug\n");"
。一旦我注释printk("debug\n");
运行代码而不调试并通过insmod命令加载模块,模块就会挂起,看起来它在递归时会丢失。我不知道为什么printk会以如此大的方式影响我的代码?
任何帮助都将不胜感激。
问候。
答案 0 :(得分:4)
您没有同步对数据变量的访问权限。会发生什么,编译器将生成无限循环。原因如下:
while( 1 )
{
if( data >= 2 )
{
kthread_stop(t1);
kthread_stop(t2);
break;
}
}
编译器可以检测到数据的值在while循环中永远不会发生变化。因此,它可以完全将支票移出循环,你最终会得到一个简单的
while (1) {}
如果你插入printk,编译器必须假设全局变量数据可能会改变(毕竟 - 编译器根本不知道printk做了什么)因此你的代码将再次开始工作(以未定义的行为方式) ..)
如何解决这个问题:
使用正确的线程同步原语。如果将对数据的访问包装到受互斥锁保护的代码段中,则代码将起作用。您也可以替换变量数据并使用计数的信号量。
修改强>
此链接解释了linux-kernel中的锁定如何工作:
http://www.linuxgrill.com/anonymous/fire/netfilter/kernel-hacking-HOWTO-5.html
答案 1 :(得分:1)
删除了对printk()
的调用后,编译器正在优化循环到while (1);
。当您将调用添加到printk()
时,编译器不确定data
是否未更改,因此每次循环都会检查该值。
您可以在循环中插入一个屏障,这会强制编译器在每次迭代时重新评估data
。例如:
while (1) {
if (data >= 2) {
kthread_stop(t1);
kthread_stop(t2);
break;
}
barrier();
}
答案 2 :(得分:0)
也许数据应该声明为volatile?可能是编译器不会使用内存来获取循环中的数据。
答案 3 :(得分:0)
Nils Pipenbrinck的答案很明显。我只想添加一些指示。
Rusty's Unreliable Guide to Kernel Locking(每个内核黑客都应该阅读此内容)。 的Goodbye semaphores?,The mutex API(lwn.net篇关于2006年初推出的新互斥API的文章,之前Linux内核使用信号量作为互斥量。)
此外,由于您的共享数据是一个简单的计数器,您只需使用原子API(基本上,将您的计数器声明为atomic_t并使用atomic_ *函数访问它)。
答案 4 :(得分:0)
易变性可能并不总是“坏主意”。一个人需要分开 需要挥发性的时候和互相排斥时的情况 机制是必要的。当使用或误用时,它是非最佳的 另一种机制。在上面的例子中。我会建议 为了获得最佳解决方案,需要两种机制:互斥到 提供互斥,volatile以指示编译器 必须从硬件中读取“info”。否则,在一些 情况(优化-O2,-O3),编译器可能会无意中发生 遗漏所需的代码。