2014-10-07 92 views
2

我已经写了一个示例linux设备驱动程序代码,它将创建两个内核线程,每个线程将增加一个全局变量。我使用等待队列来执行递增变量的任务,并且每个线程将等待等待队列,直到定时器到期并且每个线程随机唤醒。带等待队列挂起系统的Linux驱动程序代码

但问题是当我插入这个模块,整个系统刚刚冻结,我必须重新启动机器。每次插入模块时都会发生这种情况。我试着调试kthread代码,看看我是否错误地进入了死锁状态,但我无法弄清楚代码有什么问题。

任何人都可以告诉我我在做什么错误的代码来获取挂断情况?

#include <linux/module.h> 
    #include <linux/kernel.h> 
    #include <linux/init.h> 
    #include <linux/errno.h> 
    #include <linux/semaphore.h> 
    #include <linux/wait.h> 
    #include <linux/timer.h> 
    #include <linux/sched.h> 
    #include <linux/kthread.h> 

    spinlock_t my_si_lock; 
    pid_t kthread_pid1; 
    pid_t kthread_pid2 ; 
    static DECLARE_WAIT_QUEUE_HEAD(wqueue); 
    static struct timer_list my_timer; 
    int kthread_num; 

    /* the timer callback */ 
    void my_timer_callback(unsigned long data){ 
    printk(KERN_INFO "my_timer_callback called (%ld).\n", jiffies); 
     if (waitqueue_active(&wqueue)) { 
       wake_up_interruptible(&wqueue); 
     } 
    } 

    /*Routine for the first thread */ 
    static int kthread_routine_1(void *kthread_num) 
    { 
     //int num=(int)(*(int*)kthread_num); 
     int *num=(int *)kthread_num; 
     char kthread_name[15]; 
     unsigned long flags; 
     DECLARE_WAITQUEUE(wait, current); 

     printk(KERN_INFO "Inside daemon_routine() %ld\n",current->pid); 

     allow_signal(SIGKILL); 
     allow_signal(SIGTERM); 

     do{ 
       set_current_state(TASK_INTERRUPTIBLE); 
       add_wait_queue(&wqueue, &wait); 

       spin_lock_irqsave(&my_si_lock, flags); 
       printk(KERN_INFO "kernel_daemon [%d] incrementing the shared data=%d\n",current->pid,(*num)++); 
       spin_unlock_irqrestore(&my_si_lock, flags); 

       remove_wait_queue(&wqueue, &wait); 

       if (kthread_should_stop()) { 
         break; 
       } 

     }while(!signal_pending(current)); 

     set_current_state(TASK_RUNNING); 
     return 0; 
    } 

    /*Routine for the second thread */ 
    static int kthread_routine_2(void *kthread_num) 
    { 
     //int num=(int)(*(int*)kthread_num); 
     int *num=(int *)kthread_num; 
     char kthread_name[15]; 
     unsigned long flags; 
     DECLARE_WAITQUEUE(wait, current); 

     printk(KERN_INFO "Inside daemon_routine() %ld\n",current->pid); 

     allow_signal(SIGKILL); 
     allow_signal(SIGTERM); 

     do{ 
       set_current_state(TASK_INTERRUPTIBLE); 
       add_wait_queue(&wqueue, &wait); 

       spin_lock_irqsave(&my_si_lock, flags); 
       printk(KERN_INFO "kernel_daemon [%d] incrementing the shared data=%d\n",current->pid,(*num)++); 
       spin_unlock_irqrestore(&my_si_lock, flags); 

       remove_wait_queue(&wqueue, &wait); 

       if (kthread_should_stop()) { 
         break; 
       } 

     }while(!signal_pending(current)); 

     set_current_state(TASK_RUNNING); 
     return 0; 
    } 

    static int __init signalexample_module_init(void) 
    { 
     int ret; 

     spin_lock_init(&my_si_lock); 
     init_waitqueue_head(&wqueue); 
     kthread_num=1; 

     printk(KERN_INFO "starting the first kernel thread with id "); 
     kthread_pid1 = kthread_run(kthread_routine_1,&kthread_num,"first_kthread"); 
     printk(KERN_INFO "%ld \n",(long)kthread_pid1); 
     if(kthread_pid1< 0){ 
       printk(KERN_ALERT "Kernel thread [1] creation failed\n"); 
       return -1; 
     } 

     printk(KERN_INFO "starting the second kernel thread with id"); 
     kthread_pid2 = kthread_run(kthread_routine_2,&kthread_num,"second_kthread"); 
     printk(KERN_INFO "%ld \n",(long)kthread_pid2); 
     if(kthread_pid2 < 0){ 
       printk(KERN_ALERT "Kernel thread [2] creation failed\n"); 
       return -1; 
     } 

     setup_timer(&my_timer, my_timer_callback, 0); 

     ret = mod_timer(&my_timer, jiffies + msecs_to_jiffies(2000)); 
     if (ret) { 
       printk("Error in mod_timer\n"); 
       return -EINVAL; 
     } 
     return 0; 
    } 

    static void __exit signalexample_module_exit(void) 
    { 
     del_timer(&my_timer); 
    } 

module_init(signalexample_module_init); 
module_exit(signalexample_module_exit); 
MODULE_LICENSE("GPL"); 
MODULE_DESCRIPTION("Demonstrates use of kthread"); 
+0

如果你想让人们学习你的内核代码,那么至少要使用内核编码风格来提高可读性。 – sawdust 2014-10-07 18:45:49

+0

尝试打开第二个终端窗口并在加载模块时在该窗口中使用“tail -f ”。您的系统仍会锁定,但您可能会看到一些printk调用在系统日志之前被附加到系统日志中。另外,我怀疑这是你的问题,但将你的kthread_pid变量改为'struct task_struct *'数据类型。调用kthread_run与用户空间中的fork()不同,它[返回不同的数据类型](http://lxr.free-electrons.com/source/include/linux/kthread.h#L22)。 – skrrgwasme 2014-10-07 19:30:32

回答

3

您需要schedule()来电您的两个线程函数:

/* In kernel thread function... */ 

set_current_state(TASK_INTERRUPTIBLE); 
add_wait_queue(&wqueue, &wait); 

schedule(); /* Add this call here */ 

spin_lock_irqsave(&my_si_lock, flags); 
/* etc... */ 

调用set_current_state(TASK_INTERRUPTIBLE)套的状态在当前进程的任务结构,它允许调度移动过程中脱落运行队列一旦休眠。但是你必须告诉调度员,“好吧,我已经设置了一个新的状态,现在重新安排我。”您错过了第二步,因此更改后的标志只有在下一次调度程序决定暂停您的线程时才会生效,并且无法知道它会在多长时间内发生,或者您的代码在执行时会执行哪一行它发生(除了在锁定的代码 - 不应该被打断)。

我不确定为什么它会导致整个系统锁定,因为你的系统状态非常不可预测。由于内核线程并没有等待计时器在获取锁和循环之前过期,我不知道什么时候可以期望调度器对新的任务结构状态进行实际操作,并且很多事情可能发生在与此同时。您的线程反复呼叫add_wait_queue(&wqueue, &wait);remove_wait_queue(&wqueue, &wait);,因此,在定时器回调触发时,谁知道等待队列处于什么状态。事实上,由于内核线程纺,这段代码有一个竞争条件:

if (waitqueue_active(&wqueue)) { 
    wake_up_interruptible(&wqueue); 
} 

这有可能是你有在执行if语句的等待队列活动任务,只把他们的时候掏空了调用wake_up_interruptible(&wqueue);。顺便说一句,我假设你目前增加一个全局变量的目标只是一个学习waitqueues和睡眠状态的练习。如果你想要实际实现一个共享计数器,请看atomic operations,而你可以转储自旋锁。

如果您决定保留自旋锁,则应改为使用DEFINE_SPINLOCK()宏。

此外,正如我在我的评论中提到的,您应该将您的两个kthread_pid变量更改为task_struct *类型。您还需要在您的__exit例程中为每个启动的线程调用kthread_stop(kthread_pid);。如果你不告诉他们停止,kthread_should_stop()将永远不会返回true。