Multithreading linux内核模块中printk的奇怪行为

Multithreading linux内核模块中printk的奇怪行为,multithreading,linux-kernel,printk,Multithreading,Linux Kernel,Printk,我正在为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++ )

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

 }
基本上,我是在等待线程完成,然后再打印一些东西。 上述代码确实实现了该目标,但使用了printkdebug\n;没有评论。一旦我注释了printkdebug\n;要在不调试的情况下运行代码并通过insmod命令加载模块,模块将挂起,并且似乎在递归中丢失了它。我不知道为什么printk会对我的代码产生如此大的影响

任何帮助都将不胜感激


关于。

数据是否应该声明为易失性?这可能是因为编译器没有进入内存以获取循环中的数据。

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

  while( 1 )
        {
            if( data >= 2 )
            {
                kthread_stop(t1);
                kthread_stop(t2);
                break;
            }
        }
编译器可以检测到数据的值在while循环中从未改变。因此,它可以完全将支票移出循环,您将得到一个简单的

 while (1) {} 
如果插入printk,编译器必须假设全局变量数据可能会发生更改-编译器不知道printk的详细操作,因此代码将以某种未定义的行为重新开始工作

如何解决此问题:

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

编辑:

此链接说明linux内核中的锁定是如何工作的:

删除对printk的调用后,编译器将优化while 1;中的循环;。当您添加对printk的调用时,编译器不确定数据是否没有更改,因此每次通过循环检查值

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

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

        barrier();
}

尼尔斯·皮布林克的答案是正确的。我只想添加一些指针

每一个内核黑客都应该阅读这篇文章。 ,关于2006年初引入的新互斥体API的文章,在此之前Linux内核使用信号量作为互斥体


此外,由于共享数据是一个简单的计数器,因此您基本上可以使用原子API,将计数器声明为原子,并使用原子函数访问它。

Volatile可能并不总是一个坏主意。一个人需要分开 需要易变性和互斥性的情况 机制是必要的。当一个人使用或误用时,它是非最优的 一种机制代表另一种机制。在上述情况下。我建议 为了获得最佳解决方案,需要两种机制:互斥到 提供互斥、易失性,以向编译器表明 必须从硬件读取新信息。否则,在某些情况下 情况优化-O2,-O3,编译器可能会无意中
省略所需的代码。

volatile可能会起到一定的作用,但这始终是一个坏主意,因为您无法确保data++是一条原子指令。在多个线程争夺同一变量的多处理器系统上,这将为您提供一个有保证的竞争条件。