linux驱动例2--带阻塞功能的计时器

        在前面的基础上。希望添加一个等待队列,用于阻塞不能得到资源的进程们。在资源释放时,再从阻塞中恢复。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define DEV_NAME		"new_timer"
#define DEV_MAJOR		0
#define DEV_MONITOR		0
#define CLASS_NAME		"cla_ntimer"


struct timer_dev {
	struct cdev   cdev;
	struct class *pclas;
	struct device *pdev;
	int	dev_num;
	struct timer_list timer;
	atomic_t	open_cnt;		//原子操作来保证并发数量限制
	struct semaphore sem;		//信号量操作
	int	counter;				//用来给计时器计数用
	wait_queue_head_t		wqh;	//用来存储阻塞进程用
};

static struct timer_dev mytimer;

static void timer_irq_hand( unsigned long arg)
{
	down( &(mytimer.sem));	//主要与read()互斥
	mytimer.counter++;
	up( &(mytimer.sem));

	printk("time: %d, pid = %d\n", mytimer.counter, current->pid);
	mod_timer( &(mytimer.timer), jiffies + HZ);

#if 0		//测试中断中进行进程调度的后果
	 struct semaphore sem;
	sema_init( &sem, 0);
	down( &sem);		//这里会进入阻塞
		//实验发现,由于进程调度,操作系统确实跑飞了
#endif
}


static int timer_open(struct inode *inode, struct file *fp)
{
	int	ret = 0;
#if 0
	if( atomic_sub_return( 1, &(mytimer.open_cnt))< 0 )	//假如计数已经小于0,表明已经达到最大并发数
	{
		atomic_add(1, &(mytimer.open_cnt));
		return -EBUSY;			//不能得到资源就立即返回
	}
#else
	wait_queue_t	wq;
	init_wait( &(wq));
	printk("I am waiting ,pid = %d\n", current->pid);
	while( atomic_sub_return( 1, &(mytimer.open_cnt))< 0)
	{
		atomic_add( 1, &(mytimer.open_cnt));			//实际上,如果资源释放发生在这三行代码中,可能会导致有资源却没能获得的情况
		if( atomic_read( &(mytimer.open_cnt))<=0 )
			set_current_state( TASK_INTERRUPTIBLE);
		if( atomic_read( &(mytimer.open_cnt))<=0 )
		{
			printk(" waiting ,pid = %d\n", current->pid);
			add_wait_queue( &(mytimer.wqh), &wq);
			schedule();
			printk("waked ,pid = %d\n", current->pid); 		
		}
		if( signal_pending( current))
		{
			ret = -EBUSY;
			goto OUT;
		}
	}
	printk("I am waked ,pid = %d\n", current->pid); 		
#endif

	DEFINE_TIMER( temp, timer_irq_hand, jiffies + HZ, 0);

	mytimer.timer = temp;
	add_timer( &(mytimer.timer));
	sema_init( &(mytimer.sem), 1);
	//mytimer.counter = 0;
	ret = 0;

	OUT:
		remove_wait_queue( &(mytimer.wqh), &wq);
		set_current_state( TASK_RUNNING);
	return ret;
}

static int list_len( struct list_head *phead)
{
	int i = 0;
	struct list_head *pos;
	list_for_each(pos,phead)
	{
		i++;
	}
	return i;
}

static int timer_release(struct inode *inode, struct file *fp)
{
	printk(" ====> list empty : %d, pid = %d\n", list_len(&(mytimer.wqh.task_list)), current->pid);
	atomic_add( 1, &(mytimer.open_cnt));		//释放资源
	//经过分析怀疑是wake_up_interruptible()函数清空了等待队列,检验发现确实是他干的
	wake_up_interruptible( &(mytimer.wqh));	//唤醒其他等待资源的进程
	printk(" ====pid);
	del_timer( &(mytimer.timer));
	return 0;
}

static ssize_t timer_read(struct file *fp, char __user *buf, size_t sz, loff_t *off)
{
	printk("read....\n");
	int temp;
#if 1		//阻塞版
	down( &(mytimer.sem));	//主要与中断中的自加互斥
#else		//非阻塞版
	if( down_trylock( &(mytimer.sem)))
	{
		return -EBUSY;
	}
#endif
	temp = mytimer.counter;	//安全的获取数据
	up( &(mytimer.sem));
	copy_to_user( buf, &temp, sz);
	return sz;
}


static struct file_operations fops = {
	.owner = THIS_MODULE,
	.open = timer_open,
	.release = timer_release,
	.read = timer_read,
};

static int __init timer_init(void)
{
	int	ret = 0;
	printk("keyBoard_init..........%s\n", __TIME__);
	ret = alloc_chrdev_region( &(mytimer.dev_num), DEV_MONITOR, 1, DEV_NAME);
	if( ret<0 )
	{
		printk("error:%s, %d\n", __FUNCTION__,__LINE__);
		goto ERR_ALLOC_CHRDEV;
	}
	cdev_init( &(mytimer.cdev), &fops);
	mytimer.cdev.owner = THIS_MODULE;
	ret = cdev_add( &(mytimer.cdev), mytimer.dev_num, 1);
	if( ret<0)
	{
		printk("error: %s, %d\n", __FUNCTION__, __LINE__);
		goto ERR_CDEV_ADD;
	}
	mytimer.pclas = class_create( THIS_MODULE, CLASS_NAME);
	if( NULL==mytimer.pclas )
	{
		printk("error: %s, %d\n", __FUNCTION__, __LINE__);
		goto ERR_CLAS_CREAT;
	}
	mytimer.pdev = device_create( mytimer.pclas, NULL, mytimer.dev_num, NULL, DEV_NAME);
	if( NULL==mytimer.pdev )
	{
		goto ERR;
	}
	atomic_set( &(mytimer.open_cnt) , 1);		//只允许打开一次
	init_waitqueue_head( &(mytimer.wqh));

	return 0;
	ERR:
		class_destroy( mytimer.pclas);
	ERR_CLAS_CREAT:
		cdev_del( &(mytimer.cdev));
	ERR_CDEV_ADD:
		unregister_chrdev_region( mytimer.dev_num, 1);
	ERR_ALLOC_CHRDEV:
		return -1;
}

static void __exit timer_exit(void)
{
	printk("keyBoard_exit..........\n");
	device_destroy( mytimer.pclas, mytimer.dev_num);
	class_destroy( mytimer.pclas);
	cdev_del( &(mytimer.cdev));
	unregister_chrdev_region( mytimer.dev_num, 1);
	return;

}

MODULE_LICENSE("GPL");

module_init(timer_init);
module_exit(timer_exit);

        在编程过程中出现了一个错误,最初在timer_open()中的用于获取资源的while()循环是下面

add_wait_queue( &(mytimer.wqh), &wq);
 while( atomic_sub_return( 1, &(mytimer.open_cnt))< 0)
 {
  atomic_add( 1, &(mytimer.open_cnt));   //实际上,如果资源释放发生在这三行代码中,可能会导致有资源却没能获得的情况
  if( atomic_read( &(mytimer.open_cnt))<=0 )
   set_current_state( TASK_INTERRUPTIBLE);
  if( atomic_read( &(mytimer.open_cnt))<=0 )
  {
   printk(" waiting ,pid = %d\n", current->pid);
   schedule();
   printk("waked ,pid = %d\n", current->pid);   
  }
  if( signal_pending( current))
  {
   ret = -EBUSY;
   goto OUT;
  }
 }

        而释放资源的代码是这样

       atomic_add( 1, &(mytimer.open_cnt));		//释放资源
        wake_up_interruptible( &(mytimer.wqh));	//唤醒其他等待资源的进程
  

        会导致死锁。经检查发现原因是 wake_up_interruptible()函数在每次唤醒后会清空等待队列。下面来认真看一下这个函数

	#define wake_up_interruptible(x)	__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

	void __wake_up(wait_queue_head_t *q, unsigned int mode,
				int nr_exclusive, void *key)
	{
		unsigned long flags;
	
		spin_lock_irqsave(&q->lock, flags);
		__wake_up_common(q, mode, nr_exclusive, 0, key);
		spin_unlock_irqrestore(&q->lock, flags);
	}
	void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
				int nr_exclusive, int sync, void *key)
	{
		wait_queue_t *curr, *next;
	
		list_for_each_entry_safe(curr, next, &q->task_list, task_list) {	//由于curr->fun()函数会自动清除自身节点,所以选用了能保证清除安全的list_for_each_entry_safe()
			unsigned flags = curr->flags;
	
			if (curr->func(curr, mode, sync, key) &&
					(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) //为0表示唤醒所有
				break;
		}
	}

        在上面的list_for_each_entry_safe()中会遍历所有满足条件的节点,然后等待节点的清除由curr->func()函数自己完成。去检查会发现,其函数内容如下,

int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	int ret = default_wake_function(wait, mode, sync, key);

	if (ret)
		list_del_init(&wait->task_list);	//就是在这里清除
	return ret;
}


 

static inline void list_del_init(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	INIT_LIST_HEAD(entry);
}


        知识点:1).在驱动中由current宏指向驱动的当前进程。

                       2).wake_up_interruptible()会清除等待队列。

                       3). list_for_each_entry_safe()为在遍历中删除节点提供了安全,预防了由于节点删除导致的遍历断链。

       

你可能感兴趣的:(linux驱动学习)