【问题标题】:Strange behavior of printk in linux kernel modulelinux内核模块中printk的奇怪行为
【发布时间】:2010-11-06 12:59:53
【问题描述】:

我正在为 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 对我的代码有这么大的影响?

任何帮助将不胜感激。

问候。

【问题讨论】:

    标签: multithreading linux-kernel printk


    【解决方案1】:

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

      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

    【讨论】:

      【解决方案2】:

      删除对printk() 的调用后,编译器将循环优化为while (1);。当您添加对printk() 的调用时,编译器不确定data 是否未更改,因此每次循环都会检查该值。

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

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

      【讨论】:

        【解决方案3】:

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

        【讨论】:

        • volatile 会有些工作,但这总是一个坏主意,因为您无法确保 data++ 是原子指令。在具有多个线程争夺同一个变量的多处理器系统上,这将为您提供有保证的竞争条件。
        【解决方案4】:

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

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

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

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-11-29
            • 1970-01-01
            • 2021-12-10
            • 2014-01-24
            相关资源
            最近更新 更多