2

我正在为 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");

 }

基本上我试图等待线程完成然后打印一些东西。上面的代码确实实现了该目标,但"printk("debug\n");"没有注释。一旦我注释掉printk("debug\n");运行代码而不进行调试并通过 insmod 命令加载模块,模块就会挂起,并且它似乎在递归中丢失了。我不为什么 printk 会以如此大的方式影响我的代码?

任何帮助,将不胜感激。

问候。

4

5 回答 5

4

您没有同步对数据变量的访问。发生的情况是,编译器将生成一个无限循环。原因如下:

  while( 1 )
        {
            if( data >= 2 )
            {
                kthread_stop(t1);
                kthread_stop(t2);
                break;
            }
        }

编译器可以检测到 data 的值在 while 循环内永远不会改变。因此,它可以完全将检查移出循环,您最终会得到一个简单的

 while (1) {} 

如果您插入 printk 编译器必须假设全局变量数据可能会改变(毕竟 - 编译器不知道 printk 详细做了什么)因此您的代码将再次开始工作(以一种未定义的行为方式..)

如何解决这个问题:

使用适当的线程同步原语。如果您将对数据的访问包装到受互斥锁保护的代码段中,则该代码将起作用。您也可以替换变量数据并使用计数信号量。

编辑:

此链接解释了 linux-kernel 中的锁定是如何工作的:

http://www.linuxgrill.com/anonymous/fire/netfilter/kernel-hacking-HOWTO-5.html

于 2010-11-06T13:11:07.687 回答
1

通过调用printk()remove ,编译器将循环优化为while (1);. 当您将调用添加到printk()编译器时,不确定data是否没有更改,因此每次通过循环检查该值。

您可以在循环中插入一个屏障,这会强制编译器data在每次迭代时重新评估。例如:

while (1) {
        if (data >= 2) {
                kthread_stop(t1);
                kthread_stop(t2);
                break;
        }

        barrier();
}
于 2010-11-06T13:43:38.190 回答
0

也许数据应该被宣布为易失性?可能是编译器不会在内存中获取循环中的数据。

于 2010-11-06T13:06:03.007 回答
0

Nils Pipenbrinck 的回答很到位。我只是添加一些指针。

Rusty's Unreliable Guide to Kernel Locking(每个内核黑客都应该阅读这个)。
再见信号量?,互斥量 APIlwn.net上关于 2006 年初引入的新互斥量 API 的文章,在此之前 Linux 内核使用信号量作为互斥量)。

此外,由于您的共享数据是一个简单的计数器,您可以只使用 atomic API(基本上,将您的计数器声明为 atomic_t 并使用 atomic_* 函数访问它)。

于 2010-11-06T15:11:17.043 回答
0

易失性可能并不总是“坏主意”。需要区分何时需要 volatile 和何时需要互斥机制。当一个人为另一个使用或滥用一种机制时,这是非最佳的。在上述情况下。我建议最佳解决方案,这两种机制都需要:互斥体提供互斥,易失性指示编译器必须从硬件中读取“信息”。否则,在某些情况下(优化 -O2、-O3),编译器可能会无意中遗漏所需的代码。

于 2011-02-11T00:29:46.623 回答