我编写了一个示例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 :(得分: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)
设置当前流程中的状态&#39;任务结构,允许调度程序在进程休眠后将进程从运行队列中移出。但是你必须告诉调度员,&#34;好的,我已经设置了一个新状态。现在重新安排我。&#34;您已经错过了第二步,因此更改的标记将在下次调度程序决定暂停您的主题时生效,并且无法知道这种情况会在多长时间内发生,或者你的代码在发生时会执行哪一行(锁定代码除外 - 不应该被中断)。
我不确定为什么它导致整个系统锁定,因为你的系统状态非常不可预测。由于内核线程在获取锁定和循环之前没有等待计时器到期,我不知道何时可以期望调度程序实际对新的任务结构状态采取操作,并且很多事情都可能发生同时。您的线程重复调用add_wait_queue(&wqueue, &wait);
和remove_wait_queue(&wqueue, &wait);
,因此在计时器回调触发时,谁知道等待队列处于什么状态。实际上,由于内核线程正在旋转,因此该代码具有竞争条件:
if (waitqueue_active(&wqueue)) {
wake_up_interruptible(&wqueue);
}
执行if语句时,您可能在waitqueue上有活动任务,但只有在调用wake_up_interruptible(&wqueue);
时才清空它们。
顺便说一下,我假设你当前增加全局变量的目标只是学习等待和睡眠状态的练习。如果您想要实际实现共享计数器,请查看atomic operations,然后您就可以转储自旋锁。
如果您决定保留自旋锁,则应切换到使用DEFINE_SPINLOCK()
宏。
此外,正如我在评论中提到的,您应该将两个kthread_pid
变量更改为task_struct *
类型。对于您启动的每个线程,还需要在__exit例程中调用kthread_stop(kthread_pid);
。如果你没有告诉他们停止,kthread_should_stop()
将永远不会回归。