Linux 驱动层实现阻塞和非阻塞
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 > 0) { printk("copy_to_user error\n"); return -EFAULT; } memset(&key_dev->event, 0, sizeof(struct key_event)); return 0; } // 中断处理程序,多个按键可以根据 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 = 0) : (key_dev->key.value = 1); 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(2); 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 几乎被全部占用。
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 > 0) { printk("copy_to_user error\n"); return -EFAULT; } memset(&key_dev->event, 0, sizeof(struct key_event)); key_dev->have_data = 0; return 0; } // 中断处理程序,多个按键可以根据 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 = 0) : (key_dev->key.value = 1); printk("key = %d status = %d\n", key_dev->key.name, key_dev->key.value); key_dev->have_data = 1; // 唤醒 wake_up_interruptible(&key_dev->wq_head); // 返回值一定要是 IRQ_HANDLED return IRQ_HANDLED; } static int __init key_drv_init(void) { ... ... key_dev->irqno = IRQ_EINT(2); 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资源
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 > 0) { printk("copy_to_user error\n"); return -EFAULT; } memset(&key_dev->event, 0, sizeof(struct key_event)); key_dev->have_data = 0; return 0; } // 应用层以非阻塞的方式 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 > 0) { printk("copy_to_user error\n"); return -EFAULT; } memset(&key_dev->event, 0, sizeof(struct key_event)); key_dev->have_data = 0; return 0; }