阻塞和非阻塞是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;
}
四、编译测试
和前面一样。