上篇文章:介绍了linux中的五种I/O模型,本篇,就来使用阻塞式I/O非用阻塞式I/O两种方式进行按键的读取实验,并对比之前使用输入捕获和中断法检测的按键程序,查看CPU的使用率是否降低。

文章目录

  • 1 阻塞I/O方式的按键检测
    • 1.1 阻塞I/O之等待队列
    • 1.2 阻塞I/O程序编写
      • 1.2.1驱动程序
      • 1.2.2 应用程序
    • 1.2 实验
  • 2 非阻塞I/O方式的按键检测
    • 2.1 非阻塞I/O之select/poll
    • 2.2 非阻塞I/O程序编写
      • 2.2.1 驱动程序
      • 2.2.2 应用程序
        • 2.2.2.1 poll方式读取
        • 2.2.2.2 select方式读取
    • 2.3 实验
      • 2.3.1 poll方式读取
      • 2.3.2 select方式读取
  • 3 总结

1 阻塞I/O方式的按键检测

1.1 阻塞I/O之等待队列

阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作。

等待队列头使用结构体wait_queue_head_t 表示:

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

使用 init_waitqueue_head 函数初始化等待队列头:

/*** q: 要初始化的等待队列头* return: 无*/
void init_waitqueue_head(wait_queue_head_t *q)

当设备不可用的时, 将这些进程对应的等待队列项(wait_queue_t )添加到等待队列里面:

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

使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项:

DECLARE_WAITQUEUE(name, tsk)

当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中:

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

当设备可以访问以后再将进程对应的等待队列项从等待队列头中删除即可:

/*** q: 要删除的等待队列头* wait:要删除的等待队列项* return: 无*/
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

当设备可以使用的时候就要唤醒进入休眠态的进程:

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

1.2 阻塞I/O程序编写

这里仅介绍与之前按键程序的主要区别。

1.2.1驱动程序

阻塞读取逻辑如下,首先要定义一个等待队列,当按键没有按下时,就要阻塞等待了(将等待队列添加到等待队列头),然后进行行一次任务切换,交出CPU的使用权。等待有按键按下时,会有信号唤醒该等待,并将按键值返回给应用层的程序。

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){//printk("releasekey!\r\n");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;
}

按键的定时器去抖逻辑中的,读取到按键后,触发唤醒,这里以其中的一个按键为例,其逻辑如下:

void timer1_function(unsigned long arg)
{unsigned char value;struct irq_keydesc *keydesc;struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;keydesc = &dev->irqkeydesc[0];value = gpio_get_value(keydesc->gpio); /* 读取IO值 */if(value == 1) /* 按下按键 */{printk("get key1: high\r\n");atomic_set(&dev->keyvalue, keydesc->value);}else /* 按键松开 */{printk("key1 release\r\n");atomic_set(&dev->keyvalue, 0x80 | keydesc->value);atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */            }/* 唤醒进程 */if(atomic_read(&dev->releasekey)){wake_up_interruptible(&dev->r_wait);}
}

1.2.2 应用程序

应用程序不需要修改,还使用之前的轮询读取的方式,为了在测试时看出阻塞与非阻塞方式的区别,在read函数前后添加打印,如果程序运行正常,会先打印read前一句的打印,直到有按键按下后,read函数才被接触阻塞,read后一句的打印才会打印出。

/* 循环读取按键值数据! */
while(1)
{printf("[APP] read begin...\r\n");read(fd, &keyvalue, sizeof(keyvalue));printf("[APP] read end\r\n");if (keyvalue == KEY1VALUE){printf("[APP] KEY1 Press, value = %#X\r\n", keyvalue);}else if (keyvalue == KEY2VALUE){printf("[APP] KEY2 Press, value = %#X\r\n", keyvalue);}
}

1.2 实验

和之前一样,使用Makefile编译驱动程序和应用程序,并复制到nfs根文件系统中。

开始测试,按如下图,当没有按键按下时,应用程序被阻塞:

按键程序在后台运行,此时使用top指令开查看CPU的使用率,可以发现阻塞式按键驱动这种方式,CPU的暂用率几乎为0,虽然按键应用程序中仍实现循环读取的方式,但因平时读取不到按键值,按键应用程序被阻塞住了,CPU的使用权被让出,自然CPU的使用率就降下来了。

2 非阻塞I/O方式的按键检测

按键应用程序以非阻塞的方式读取,按键驱动程序也要以非阻塞的方式立即返回。应用程序可以通过select、poll或epoll函数来
查询设备是否可以操作,驱动程序使用poll函数。

2.1 非阻塞I/O之select/poll

  • select函数原型:
/*** nfs: 所要监视的这三类文件描述集合中,最大文件描述符加1* readfds: 用于监视指定描述符集的读变化* writefds: 用于监视文件是否可以进行写操作* exceptfds: 用于监视文件的异常* timeout: 超时时间* return: 0 超时发生, -1 发生错误, 其他值 可以进行操作的文件描述符个数 */
int select(int    nfds,  fd_set *readfds,  fd_set *writefds, fd_set *exceptfds,  struct timeval *timeout)

其中超时时间使用结构体timeval表示:

struct timeval { long tv_sec;  /* 秒   */ long tv_usec; /* 微妙 */
};

当timeout为NULL的时候就表示无限等待。

  • poll函数原型:
/*** fds: 要监视的文件描述符集合以及要监视的事件,为一个数组* nfds: 监视的文件描述符数量* timeout: 超时时间,单位为 ms* return: 0 超时发生, -1 发生错误, 其他值 可以进行操作的文件描述符个数 */
int poll(struct pollfd *fds,  nfds_t nfds,  nt     timeout)

2.2 非阻塞I/O程序编写

2.2.1 驱动程序

poll函数处理部分:

unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{unsigned int mask = 0;struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;/* 将等待队列头添加到poll_table中 */poll_wait(filp, &dev->r_wait, wait);/* 按键按下 */if(atomic_read(&dev->releasekey)){mask = POLLIN | POLLRDNORM;            /* 返回PLLIN */}return mask;
}/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {.owner = THIS_MODULE,.open = imx6uirq_open,.read = imx6uirq_read,.poll = imx6uirq_poll,
};

read函数处理部分:

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;/* 非阻塞访问 */if (filp->f_flags & O_NONBLOCK){/* 没有按键按下,返回-EAGAIN */if(atomic_read(&dev->releasekey) == 0){return -EAGAIN;}}/* 阻塞访问 */else{/* 加入等待队列,等待被唤醒,也就是有按键按下 */ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); if (ret){goto wait_error;}}keyvalue = atomic_read(&dev->keyvalue);releasekey = atomic_read(&dev->releasekey);/* 有按键按下 */if (releasekey){//printk("releasekey!\r\n");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:return ret;
data_error:return -EINVAL;
}

2.2.2 应用程序

2.2.2.1 poll方式读取

注意open函数的参数是O_NONBLOCK,即非阻塞访问,并且为了在测试时看出阻塞读取与非阻塞读取的区别,在poll函数前后添加打印,如果程序正常运行,poll函数则不会被阻塞,500ms超时未读取到按键值后会再次循环读取,实际效果就是可以看打一直有打印输出。

    filename = argv[1];fd = open(filename, O_RDWR | O_NONBLOCK);    /* 非阻塞访问 */if (fd < 0){printf("[APP] Can't open file %s\r\n", filename);return -1;}/* 构造结构体 */fds.fd = fd;fds.events = POLLIN;while(1){printf("[APP] poll begin... \r\n", data);ret = poll(&fds, 1, 500);printf("[APP] poll end \r\n", data);/* 数据有效 */if (ret > 0){ret = read(fd, &data, sizeof(data));if(ret < 0){/* 读取错误 */}else{if(data){printf("[APP] key value = %d \r\n", data);}}     }/* 超时 */else if (ret == 0){/* 用户自定义超时处理 */}/* 错误 */else{/* 用户自定义错误处理 */}}

2.2.2.2 select方式读取

select方式读取与poll方式类似,都是非阻塞读取,程序类似:

while(1)
{FD_ZERO(&readfds);FD_SET(fd, &readfds);/* 构造超时时间 */timeout.tv_sec = 0;timeout.tv_usec = 500000; /* 500ms */ret = select(fd + 1, &readfds, NULL, NULL, &timeout);switch (ret){/* 超时 */case 0:/* 用户自定义超时处理 */break;/* 错误 */case -1:/* 用户自定义错误处理 */break;/* 可以读取数据 */default:if(FD_ISSET(fd, &readfds)){ret = read(fd, &data, sizeof(data));if (ret < 0){/* 读取错误 */}else{if (data){printf("key value=%d\r\n", data);}}}break;}
}

2.3 实验

2.3.1 poll方式读取

和之前一样,使用Makefile编译驱动程序和应用程序,并复制到nfs根文件系统中。

开始测试,按如下图,当没有按键按下时,应用程序也没有被阻塞,从不断的打印就可以看出应用程序在循环运行。当有按键按下时,能够读取到对应的按键值。

按键程序在后台运行,此时使用top指令开查看CPU的使用率,可以发现非阻塞式按键驱动这种方式,CPU的暂用率也几乎为0,虽然按键应用程序中仍实现循环读取的方式,但poll函数有500ms的超时设置,在超时等待的时间里,CPU的使用权也是被让出,所以CPU的使用率也降下来了。

2.3.2 select方式读取

select方式读取与poll方式读取的效果一样。

使用ps指令查看poll方式的按键进行号,使用kill杀带该进程,再运行select方式的按键应用程序:

select非阻塞读取的方式,CPU的暂用率也几乎为0:

3 总结

本篇使用两种I/O模型进行按键读取:阻塞式I/O非用阻塞式I/O,通过实际的实验,对比两者方式的实际运行效果与主要区别,并查看CPU的占用率,两种方式的CPU使用率都几乎为0。

【i.MX6ULL】驱动开发10——阻塞非阻塞式按键检测相关推荐

  1. 五种IO模型:阻塞/非阻塞/复用/信号驱动/异步IO模型

    五种IO模型:阻塞/非阻塞/复用/信号驱动/异步IO模型 1. IO基本概念 1.1 IO概念 1.2 IO的两个阶段 1.2.1 IO的两个阶段-例子说明 1.2 IO种类 2. 五种IO模型 2. ...

  2. 搭稳Netty开发的地基,用漫画帮你分清同步异步阻塞非阻塞

    Netty Netty是一款非常优秀的网络编程框架,是对NIO的二次封装,本文将重点剖析Netty客户端的启动流程,深入底层了解如何使用NIO编程客户端. Linux网络编程5种IO模型 根据UNIX ...

  3. 详解Linux驱动技术(五) _设备阻塞/非阻塞读写

    在Linux驱动程序编写过程中,设备阻塞/非阻塞读写是一种非常重要的技术.它可以实现高效的数据传输和事件处理,提高系统的性能和响应速度.在本文中,我们将深入探讨Linux驱动技术(五) _设备阻塞/非 ...

  4. Linux驱动学习9(同步/异步与阻塞/非阻塞的区别 )

    很多人对阻塞,非阻塞,同步,异步,并发,竞态的概念不是很清晰,今天我把我理解的用一个模型来说明一下这些概念. 首先建立一个模型: 我们去银行办理业务,屌丝的做法是: 1.银行未准备好,则一直排队,直到 ...

  5. 同步/异步 阻塞/非阻塞区别

    我喜欢用自己的语言通过联系现实生活中的一些现象解释一些概念,当我能做到这一点时,说明我已经理解了这个概念.今天要解释的概念是:同步/异步与阻塞/非阻塞的区别. 这两组概念常常让人迷惑,因为它们都是涉及 ...

  6. 15分钟读懂进程线程、同步异步、阻塞非阻塞、并发并行,太实用了!

    作者:Martin cnblogs.com/mhq-martin/p/9035640.html 基本概念 1 进程和线程 进程(Process): 是Windows系统中的一个基本概念,它包含着一个运 ...

  7. 【面试】迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章(快快珍藏)...

    网上有很多讲同步/异步/阻塞/非阻塞/BIO/NIO/AIO的文章,但是都没有达到我的心里预期,于是自己写一篇出来. 常规的误区 假设有一个展示用户详情的需求,分两步,先调用一个HTTP接口拿到详情数 ...

  8. io-同步 异步 阻塞 非阻塞

    异步io是kernel帮你的线程盯着该线程所要的数据是否可用,而线程可以去做别的事情.当数据可用时kernel通知你的线程.需要利用事件等机制来完成. 同步io是你的线程自己去向内核查询所要的数据是否 ...

  9. 怎样理解阻塞非阻塞与同步异步的区别?

    发现很多人对这两个概念往往混为一谈(包括本人,不是很理解). 阻塞"与"非阻塞"与"同步"与"异步"不能简单的从字面理解,提供一个 ...

  10. 确定不来了解一下什么是 BIO NIO AIO 阻塞 非阻塞 同步 异步?

    本文内容涉及同步与异步, 阻塞与非阻塞, BIO.NIO.AIO等概念, 这块内容本身比较复杂, 很难用三言两语说明白. 而书上的定义更不容易理解是什么意思. 下面跟着我一起解开它们神秘的面纱. BI ...

最新文章

  1. 手写体数字识别(理解起来更简单一点)
  2. 让 QtWebkit 支持跨域CROS - nowboy的CSDN博客 - 博客频道 - CSDN.NET
  3. 浪潮英特尔在德国发布KEEP升级计划 用户可提前体验英特尔KNM
  4. 【Python学习系列二】Python默认编码和Eclipse环境的冲突问题
  5. 路由策略原理及配置请查收......
  6. hdu 1421 动态规划
  7. 计算机基础知识:原码、反码、补码
  8. LiveVideoStack线上交流分享 (十四) —— 深度学习在视频分析处理的实践
  9. Kubernetes集群部署及简单命令行操作
  10. NullReferenceException
  11. plsqldev 乱码
  12. MD5的认识,建议所有菜菜都看下
  13. 系统安装 使用VMware15安装Win7系统
  14. python时间序列峰值检测_Python中的峰值检测算法
  15. Jump gameII
  16. redit mysql_开发者经常用到的75 个功能强大的 jQuery插件和教程汇总(上篇)
  17. android仿酷狗界面,Android仿酷狗动感歌词(支持翻译和音译歌词)显示效果
  18. 错误1053: 服务没有及时响应启动或控制请求
  19. Nginx 指定域名(或子域名)和网站绑定
  20. 蚂蚁全媒体中心刘鑫炜解答:为什么要打造个人品牌

热门文章

  1. 2022高处吊篮安装拆卸工(建筑特殊工种)考题及在线模拟考试
  2. Qt for Bluetooth 蓝牙开发系列文章总纲
  3. HDOJ 1201 18岁生日
  4. pdf怎么翻译?有这个工具就够了
  5. Visio2013中插入Mathtype公式的方法
  6. PhantomJS简介
  7. DCDC基础(5)-- BUCK电路中输出电容的作用是什么?如果只是滤波的话去掉这颗电容行不行?
  8. 利用swiper和css3实现手机滑屏与动画效果
  9. 本科广东省计算机学校排名,2018计算机类专业大学排名 大学本科专业排行榜
  10. Linux下gcc编译器的安装与使用