以前由于项目需要,临时做过windows的虚拟串口驱动,当时只实现了write,没有实现read(不需要read)。当时不知道如何实现read操作,因为不知道设备什么时候才会有数据,我驱动中该什么时机读呢?难道我read程序中要写一个while循环,一直去读取吗?
现在才明白,可以使用操作系统中的阻塞和非阻塞原理解决上面的问题。
这篇文章主要介绍阻塞的驱动实现。
如上图,在write和read等IO操作中,有两种实现方法,阻塞与非阻塞:
在进行IO操作时,如果获取不到相关的资源,当前的进程会被挂起,进入睡眠,当资源可以被使用时,程序被唤醒,继续IO操作。一般是在中断中唤醒被挂起的进程。
在进行IO操作时,如果获取不到相关的资源,程序会立刻返回,返回值为-EAGAIN。
在Linux中,主要通过等待队列实现阻塞的IO操作。
wait_queue_head_t my_queue; //定义等待队列头
init_waitqueue_head(&my_queue); //初始化等待队列头
DECLARE_WAIT_QUEUE_HEAD(my_queue); //快捷方式,定义并且初始化等待队列头
wait_event(queue, condition) //不可被中断打断
wait_event_interruptible(queue, condition) //可被中断打断
wait_event_timeout(queue, condition, timeout) //如果一段时间后没有被唤醒,就自动唤醒,也就是超时机制。
wait_event_interruptible_timeout(queue, condition, timeout)
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
需要说明一点,即使wake_up唤醒休眠的进程,如果condition为false,依然会继续阻塞。
此示例是在按键中断驱动的基础上修改而来,在驱动初始化结束后,开始阻塞,进程被挂起,当按下按键,中断程序触发,设置condition为true,并且唤醒进程。一共10次循环。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define IMX_GPIO_NR(bank, nr) (((bank) - 1) * 32 + (nr)) //平台相关
#define CYNO_GPIO_BEEP_NUM IMX_GPIO_NR(6,10) //自己使用引脚,由beep引脚改的,所以名称叫做beep
DECLARE_WAIT_QUEUE_HEAD(wq); //定义并且初始化等待队列头
static int condition = 0;
//定义gpio引脚的结构体
static struct pin_desc{
int irq;
unsigned char *name;
unsigned int pin;
};
//实例化一个具体的引脚
static struct pin_desc beep_desc = {
0,
"beep_num",
CYNO_GPIO_BEEP_NUM
};
//中断触发函数
static irqreturn_t beep_interrupt_handler(int irq, void *dev_id)
{
printk("%s\n", __func__);
condition = 1; //设置唤醒条件为真
wake_up_interruptible(&wq); //唤醒被挂起的进程
return IRQ_HANDLED;
}
static int wait_queue_init(void)
{
int ret, count;
printk(KERN_INFO "%s\n", __func__);
//请求gpio
if(gpio_request(beep_desc.pin ,beep_desc.name)){
printk(KERN_ERR "%s : request gpio %d error\n", __func__, beep_desc.pin);
goto err_gpio_request;
}
//设置按键为输入
gpio_direction_input(beep_desc.pin);
//动态获取irq中断号
beep_desc.irq = gpio_to_irq(beep_desc.pin);
printk(KERN_INFO "%s : the irq num is %d\n", __func__, beep_desc.irq);
//申请终端,并且设置中断触发函数,中断触发方式,私有数据等
ret = request_irq(beep_desc.irq, beep_interrupt_handler , IRQF_TRIGGER_FALLING | IRQF_ONESHOT, beep_desc.name , &beep_desc);
if(ret){
printk(KERN_ERR "%s : request_irq is error\n", __func__);
goto err_request_irq;
}
printk("%s : init end\n", __func__);
count = 10;
while(count--){
wait_event_interruptible(wq,condition); //开始阻塞
printk(KERN_INFO "wake up count = %d\n", 10 - count);
condition = 0;
}
return 0;
err_request_irq:
free_irq(beep_desc.irq, &beep_desc);
err_gpio_request:
gpio_free(beep_desc.pin);
return -1;
return 0;
}
static void wait_queue_exit(void)
{
printk(KERN_INFO "%s\n", __func__);
free_irq(beep_desc.irq, &beep_desc);
gpio_free(beep_desc.pin);
}
module_init(wait_queue_init);
module_exit(wait_queue_exit);
MODULE_AUTHOR("xiaolei");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("wait_queue example");