Linux 驱动层实现阻塞和非阻塞

时间:2025-04-08 19:08:13

linux应用层的函数默认是阻塞型的,但是要想真正实现阻塞,还需要驱动的支持才行。

例:open()、scanf()、fgets()、read()、accept() 等

1、默认情形,驱动层不实现阻塞和非阻塞

struct samsung_key{
int major;
int irqno;
struct class *cls;
struct device *dev;
struct key_event event;
};
struct samsung_key *key_dev; ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret; ret = copy_to_user(buf, &key_dev->event, count);
if(ret > )
{
printk("copy_to_user error\n");
return -EFAULT;
}
memset(&key_dev->event, , sizeof(struct key_event)); return ;
} // 中断处理程序,多个按键可以根据 irqno 区分
irqreturn_t key_irq_handler(int irqno, void *dev_id)
{
int ret; printk("------------%s-------------\n", __FUNCTION__); ret = gpio_get_value(key_info[i].key_gpio);
ret ? (key_dev->key.value = ) : (key_dev->key.value = );
printk("key = %d status = %d\n", key_dev->key.name, key_dev->key.value); // 返回值一定要是 IRQ_HANDLED
return IRQ_HANDLED;
} static int __init key_drv_init(void)
{
... ...
key_dev->irqno = IRQ_EINT();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "key_eint", NULL);
... ...
} static int __exit key_drv_exit(void)
{
... ...
free_irq(key_dev->irqno,NULL);
... ...
} // 应用层
fd = open("/dev/key0", O_RDWR);

这种情况下,应用层的 read 会一直不停的读按键值,使用 top 指令查看,发现 cpu 几乎被全部占用。

Linux 驱动层实现阻塞和非阻塞

2、驱动层实现阻塞

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

// 初始化等待队列头
init_waitqueue_head(wait_queue_head_t *q);

// 功能:在特定的时候进行休眠
// 参数1:等待队列头
// 参数2:条件,条件为假,该代码就会阻塞,如果为真,不做任何事情
wait_event_interruptible(wait_queue_head_t q,condition);

// 功能: 资源可达的时候,唤醒
wake_up_interruptible(wait_queue_head_t *q);

struct samsung_key{
int major;
int irqno;
struct class *cls;
struct device *dev;
struct key_event event;
unsigned char have_data;
wait_queue_head_t wq_head;
};
struct samsung_key *key_dev; ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret; // 判断是否阻塞,条件为假,就会阻塞,让出cpu
wait_event_interruptible(key_dev->wq_head,key_dev->have_data); ret = copy_to_user(buf, &key_dev->event, count);
if(ret > )
{
printk("copy_to_user error\n");
return -EFAULT;
}
memset(&key_dev->event, , sizeof(struct key_event));
key_dev->have_data = ; return ;
} // 中断处理程序,多个按键可以根据 irqno 区分
irqreturn_t key_irq_handler(int irqno, void *dev_id)
{
int ret; printk("------------%s-------------\n", __FUNCTION__); ret = gpio_get_value(key_info[i].key_gpio);
ret ? (key_dev->key.value = ) : (key_dev->key.value = );
printk("key = %d status = %d\n", key_dev->key.name, key_dev->key.value); key_dev->have_data = ;
// 唤醒
wake_up_interruptible(&key_dev->wq_head); // 返回值一定要是 IRQ_HANDLED
return IRQ_HANDLED;
} static int __init key_drv_init(void)
{
... ...
key_dev->irqno = IRQ_EINT();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "key_eint", NULL);
// 初始化等待队列头
init_waitqueue_head(&key_dev->wq_head);
... ...
} static int __exit key_drv_exit(void)
{
... ...
free_irq(key_dev->irqno,NULL);
... ...
} // 应用层
fd = open("/dev/key0", O_RDWR);

实现阻塞后,应用层读不到按键值时就会休眠,让出cpu资源

Linux 驱动层实现阻塞和非阻塞

3、驱动层实现非阻塞

实现非阻塞很容易,只需在读数据前加判断就行,可以单独实现非阻塞,也可以同时兼容阻塞和非阻塞、

// 单独实现非阻塞
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret; // 非阻塞,资源不可达的时候,立刻返回,资源可达,直接读写数据
if( (filp->f_flags & O_NONBLOCK) && !key_dev->have_data)
return -EAGAIN; ret = copy_to_user(buf, &key_dev->event, count);
if(ret > )
{
printk("copy_to_user error\n");
return -EFAULT;
}
memset(&key_dev->event, , sizeof(struct key_event));
key_dev->have_data = ; return ;
} // 应用层以非阻塞的方式
fd = open("/dev/key0", O_RDWR | O_NONBLOCK);

非阻塞模式下,当应用程序运行后,没有读到按键值,就会收到一个错误(-EAGAIN)返回值。

// 驱动层兼容阻塞和非阻塞
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret; // 资源不可达的时候,立刻返回,资源可达,直接读写数据
if( (filp->f_flags & O_NONBLOCK) && !key_dev->have_data)
return -EAGAIN; // 判断是否阻塞,条件为假,就会阻塞,让出cpu
wait_event_interruptible(key_dev->wq_head,key_dev->have_data); ret = copy_to_user(buf, &key_dev->event, count);
if(ret > )
{
printk("copy_to_user error\n");
return -EFAULT;
}
memset(&key_dev->event, , sizeof(struct key_event));
key_dev->have_data = ; return ;
}