c - 具有等待队列挂起系统的Linux驱动程序代码

标签 c linux multithreading linux-device-driver

我编写了一个示例 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");

最佳答案

您需要在两个线程函数中调用 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); 时它们被清空。

顺便说一句,我假设您当前增加全局变量的目标只是学习等待队列和 sleep 状态的练习。如果您想实际实现共享计数器,请查看 atomic operations相反,您将能够转储自旋锁。

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

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

关于c - 具有等待队列挂起系统的Linux驱动程序代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26242171/

相关文章:

linux - ActiveMQ 5.9 启动脚本?

java - 在Java中,对字符串的只读访问是否需要同步?

c - 是否有任何 C 库为 GNU/Linux 实现 C11 线程?

c - 有没有一种无需重新编码即可并行运行 C/C++ 程序的简单方法?

c++ - 切换到 MPI_LONG_LONG_INT 会崩溃吗?

c - #行和字符串文字连接

python - Airflow BashOperator 不起作用,但 PythonOperator 可以

linux - 在 Linux 中打开 GPIO 有线 LED

c++ - 如何成功地将功能对象(或 lambda)传递给轨迹栏回调的第二个参数(void*)?

c# - C#中的线程执行顺序