Linux驱动学习记录-14.阻塞以及实验

阻塞和非阻塞是Linux驱动开发的常用访问模式。编写程序要考虑。本次介绍阻塞。

一、阻塞介绍

阻塞操作是指在执行设备操作时,不能获取设备资源,就挂起进程直到资源可以访问再进行操作。被挂起的操作进入休眠。而非阻塞操作的进程不能进行设备操作时,不会挂起,要么放弃要么不断查询,直到可以操作。
区别在于:在应用程序中的read、write函数执行时,若设备不可操作,则应用程序会一直卡到这里,同时驱动程序里,的xxxread、xxxwrite函数将进程阻塞,直到资源可以获取,再返回给应用程序。非阻塞是指,驱动程序的xxxread、xxxwrite函数发现设备不可取会立即返回,应用程序也会继续往下执行。
阻塞看似低效率,但是如果非阻塞,应用程序会不停轮询,浪费cpu资源。阻塞访问让不能获取资源的进程进入休眠,让cpu资源让给其他进程。
阻塞的进程进入休眠,要确保有i一个地方可以唤醒休眠的进程,否则永远休眠,唤醒一般放在中断里。
示例代码如下:

int fd;
int data = 0;
fd = open("/dev/xxx+dev", O_EDWR);    //阻塞方式打开
ret = read(fd, &data, sizeof(data));  //读取,直到有值返回

二、等待队列

Linux驱动程序,使用等待队列(wait queue)来唤醒休眠的进程。
下面是流程示意:

1.定义等待队列头

队列头结构体

struct __wait_queue_head{
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

wait_queue_head_t my_queue;  //定义

2.初始化队列头

void init_waitqueue_head(wait_queue_head_t &my_queue)

也可以使用宏 DECLARE_WAIT_QUEUE_HEAD(name),直接定义并初始化name的等待队列头。

3.定义等待队列

struct __wait_queue{
usigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;

使用宏DECLARE_WAITQUEUE(name, tsk),name表示队列项的名字,tsk表示队列属于哪一个进程,一般为current。

4.添加、移除等待队列

void add_wait_queue( wait_queue_head_t *q, // 要加入的等待队列头
					wait_queue_t *wait)    //等待队列项
void remove_wait_queue(wait_queue_head_t *q,
						wait_queue_t *wait)

5.等待唤醒

void wake_up (wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)

第一个可以唤醒TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE状态的进程,第二个只能唤醒TASK_INTERRUPTIBLE 进程。

6.等待事件

除了主动唤醒外,可以设置等待队列等待某个事件,事件满足即可唤醒设备

函数描述
wait_event(wq, condition)等待以wq为等待队列头的等待队列被唤醒,前提是condition条件为真,否则一直阻塞,
wait_event_timeout(wq, condition, timeout)功能和前面一样,可以添加超时时间,以jiffies为单位,如果返回0则超时,且condition为假,如果为1,则condition为真
wait_event_interruptible(wq, conditon)和前面一样,但进程设置TASK_INTERRUPTIBLE,可以被信号打断。
wait_event_interruptible_timeout(wq, condition, timeout)和前面一样,进程设置TASK_INTERRUPTIBLE,可以被信号打断。

三、驱动程序

主干程序思路如下:调用read函数后,定义一个等待队列,如果此时按键按下的标志位为零,则把等待队列加入队列头,设置任务状态(休眠),进行任务切换:schedule()。此时程序卡在这里了!
当在中断程序里,把进程唤醒后,程序会接着执行,下面要判断是不是按键按下标志为真,如果是的,返回值。并将等待队列从队列头移除。
以下撷取部分主干代码:

struct imx6uirq_dev {
    dev_t devid;
    *******
    wait_queue_head_t  r_wait;  //定义队列头
};
void timer_function(unsigned long arg)
{        

	/* 唤醒进程 */
	if(atomic_read(&dev->releasekey)) {	/* 完成一次按键过程 */
		/* wake_up(&dev->r_wait); */
		wake_up_interruptible(&dev->r_wait);  //唤醒进程
	}
}
static int keyio_init(void)
{
	/* 创建定时器 */
     init_timer(&imx6uirq.timer);
     imx6uirq.timer.function = timer_function;

	/* 初始化等待队列头 */
	init_waitqueue_head(&imx6uirq.r_wait);
	return 0;
}
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = 0;
	unsigned char keyvalue = 0;
	unsigned char releasekey = 0;
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

	DECLARE_WAITQUEUE(wait, current);	/* 定义一个等待队列 */
	if(atomic_read(&dev->releasekey) == 0) {	/* 没有按键按下 */
		add_wait_queue(&dev->r_wait, &wait);	/* 将等待队列添加到等待队列头 */
		__set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态 */
		schedule();							/* 进行一次任务切换 */
		if(signal_pending(current))	{			/* 判断是否为信号引起的唤醒 */
			ret = -ERESTARTSYS;
			goto wait_error;
		}
		__set_current_state(TASK_RUNNING);      /* 将当前任务设置为运行状态 */
	    remove_wait_queue(&dev->r_wait, &wait);    /* 将对应的队列项从等待队列头删除 */
	}

	keyvalue = atomic_read(&dev->keyvalue);
	releasekey = atomic_read(&dev->releasekey);

	if (releasekey) { /* 有按键按下 */	
		if (keyvalue & 0x80) {
			keyvalue &= ~0x80;
			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
		} else {
			goto data_error;
		}
		atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
	} else {
		goto data_error;
	}
	return 0;

wait_error:
	set_current_state(TASK_RUNNING);		/* 设置任务为运行态 */
	remove_wait_queue(&dev->r_wait, &wait);	/* 将等待队列移除 */
	return ret;

data_error:
	return -EINVAL;
}

四、编译测试

和前面一样。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值