• IO:应用程序对设备驱动进行输入/输出操作。
  • 阻塞IO:当资源不用时,将应用程序对应的线程挂起,当资源可用时,唤醒任务。
  • 非阻塞IO:当资源不可用时,应用程序轮询等待或放弃。具有超时处理机制

设备驱动文件默认读取方式:阻塞式。

应用程序阻塞读取数据

int fd;
int data = 0;fd = open("/dev/xxx", O_RDWR);
ret = read(fd, &data, sizeof(data));

应用程序阻塞读取数据,添加参数O_NONBLOCK

int fd;
int data = 0;fd = open("/dev/xxx", O_RDWR | O_NONBLOCK);
ret = read(fd, &data, sizeof(data));

阻塞—等待队列

当设备文件不可操作时,进程进入休眠状态,将CPU资源让出来。在设备文件可以操作时,唤醒进程,一般是在中断函数中唤醒。

Linux内核使用等待队列实现阻塞进程的唤醒。

等待队列头

等待队列头是等待队列的头部。

使用结构体wait_queue_head_t定义一个等待队列头,然后用init_waitqueue_head函数初始化等待队列头。或使用宏DECLARE_WAIT_QUEUE_HEAD一次性定义初始化等待队列头。

等待队列项

每个访问设备的进程都是一个队列项,当设备不可用的时候,将这些进程对应的等待队列项添加到等待队列。

使用结构体wait_queue_t表示等待队列项,使用宏DECLARE_WAITQUEUE定义并初始化一个等待队列项。

DECLARE_WAITQUEUE(name, tsk)
  • name:等待队列项名字。
  • tsk:等待队列属于的进程,一般设置为current。Linux内核中current是一个全局变量,表示当前进程。

将等待队列项添加到等待队列头

当设备不可访问时,将进程对应的等待队列项添加到之前创建的等待队列头中,只有添加到等待队列头后,进程才能进入休眠态。

add_wait_queue函数原型如下:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
  • q:等待队列项要加入的等待队列头。
  • wait:等待队列项。
  • 返回值:无。

移除等待队列头

当设备可以访问时,将进程对应的等待队列项从等待队列头中删除。

remove_wait_queue函数原型如下:

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
  • q:要删除的等待队列项所处的等待队列头。
  • wait:要删除的等待队列项。
  • 返回值:无。

等待唤醒

当设备可用时,唤醒进入休眠态的进程。wake_up函数唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程。wake_up_interruptible函数只能唤醒处于TASK_INTERRUPTIBLE状态的进程。

void wake_up(wait_queue_head_t *q);
void wake_up_interruptible(wait_queue_heat_t *q);
  • q:要唤醒的等待队列头。
  • 返回值:无。

等待事件

除了主动唤醒,还可以设置等待某个事件,当事件满足后自动唤醒等待队列中的进程。

wait_event(wq, condition)

wait_event函数,condition条件为真时,以wq为等待队列头的等待队列被唤醒,否则一直阻塞,会将进程设置为TASK_UNINTERRUPTIBLE状态。

wait_event_timeout(wq, condition, timeout)

wait_event_timeout函数,和wait_event函数类似,可以添加超时时间,以jiffies为单位,函数返回0,超时时间到,condition为假;返回1,条件满足,condition为真。

wait_event_interruptible(wq, condition)

wait_event_interruptible函数,和wait_event函数类似,将进程设置为TASK_INTERRUPTIBLE状态,可以被信号打断。

wait_event_interruptible_timeout(wq, condition, timeout)

wait_event_interruptible_timeout函数,和wait_event_timeout函数类似,将进程设置为TASK_INTERRUPTIBLE状态,可以被信号打断。

非阻塞—轮询

应用程序以非阻塞方式访问设备,设备驱动程序要提供非阻塞的处理方式。

  • 应用程序通过select、epoll或poll函数查询设备是否可以操作,如果可以操作的话,从设备读取或向设备写入数据。
  • 设备驱动程序执行poll函数。

应用程序

select

int select(int            nfds,fd_set         *readfds,fd_set         *writefds,fd_set         *exceptfds,struct timeval *timeout);
  • nfds:要监视的三类文件描述符集合中,最大文件描述符加1。
  • readfds、writefds和exceptfds:都是fd_set类型,fd_set类型变量的每一个位都代表一个文件描述符。readfds用于监视文件描述符集是否可以读取,如果集合中有一个文件可以读取,返回一个大于0的值表示可读,如果没有文件可以读取,根据timeout参数判断是否超时。如果readfds设置为NULL表示不关心文件的读变化。writefds用于监视文件是否可以进行写操作。exceptfds用于监视文件的异常。
  • timeout:超时时间。当timeout设置为NULL表示无限期等待。
  • 返回值:0,超时发生,但是没有任何文件描述符可以进行操作;-1,发生错误;其他值,进行操作的文件描述符个数。

当定义一个fd_set类型变量,可以进行使用宏进行操作。

void FD_ZERO(fd_set *set)
void FD_SET(int fd, fd_set *set)
void FD_CLR(int fd, fd_set *set)
int FD_ISSET(int fd, fd_set *set)
  • FD_ZERO用于将fd_set变量的所有位都清零。
  • FD_SET用于将fd_set变量的某个位置1,也就是向 fd_set添加一个文件描述符,参数fd就是要加入的文件描述符。
  • FD_CLR用于将 fd_set变量的某个位清零,也就是将一个文件描述符从fd_set中删除,参数fd就是要删除的文件描述符。
  • FD_ISSET 用于测试一个文件是否属于某个集合,参数fd就是要判断的文件描述符。

select函数非阻塞访问示例:

void main(void) {int ret, fd;             /* 要监视的文件描述符 */fd_set readfds;          /* 读操作 文件描述符 */struct timeval timeout;  /* 超时时间 */fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞访问 */FD_ZERO(&readfds);       /* 清楚readfds */FD_SET(fd, &readfds);    /* 将fd添加到readfds */timeout.tv_sec = 0;timeout.tv_usec = 500000; /* 500 ms */ret = select(fd + 1, &readfds, NULL, NULL, &timeout);switch(ret) {case 0: /* 超时 */printf("timeout\r\n");break;case -1: /* 错误 */printf("error\r\n");break;default: /* 读数据 */if(FD_ISSET(fd, &readfds)) { /* 判断是否为文件描述符 *//* read函数读取数据代码 */}break;            }
}

poll

在单个线程中,select函数能够监视的文件描述符最大数量为1024,poll函数没有最大文件描述符限制。

struct pollfd {int    fd;      /* 文件描述符 */short  events;  /* 请求的事件 */short  revents; /* 返回的事件 */
};int poll(struct pollfd *fds;nfds_t        nfds,int           timeout)
  • fd:要监视的文件描述符,fd无效则events监视事件无效,revents返回0。
  • events:要监视的事件。events可监视事件的类型。
    • POLLIN              有数据可以读取
    • POLLPRI           有紧急的数据需要读取
    • POLLOUT          可以写数据
    • POLLERR          指定的文件描述符发生错误
    • POLLHUP          无效的请求
    • POLLNVAL         指定的文件描述符挂起
    • POLLRDNORM  等同于POLLIN
  • revents:返回的事件,由内核设置具体的返回事件。
  • fds:要监视的文件描述符集合及要监视的事件,fds为一个数组,数组元素都是结构体pollfd类型。
  • nfds:poll函数要监视的文件描述符数量。
  • timeout:超时时间,单位ms。
  • 返回值:返回发生事件或错误的文件描述符数量,0,超时;-1,发生错误并设置errno为错误类型。

poll函数非阻塞访问示例:

void main(void) {int ret, fd;       /* 要监视的文件描述符 */struct pollfd fds; /* pollfd结构体 */ fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞访问 */fds.fd = fd;fds.events = POLLIN; /* 监视数据是否可读 */ret = poll(&fds, 1, 500);  /* 轮询文件是否可操作, 超时500ms */if (ret) {/* 读取数据代码 */} else if (ret == 0) {/* 超时 */} else if (ret == -1) {/* 错误 */}
}

epoll

select和poll函数都会随着监听fd数量的增加,出现效率低下的问题,poll函数每次必须遍历所有描述符来检查就绪的描述符,浪费时间。涉及的文件描述符较少时适合用select和poll函数。

epoll函数为处理大并发而准备的,常用于网络编程。

应用程序先使用epoll_create创建一个epoll句柄。

int epoll_create(int size)
  • size:Linux版本2.6.8开始size参数没有意义,写一个大于0的值即可。
  • 返回值:epoll句柄,-1表示创建失败。

epoll句柄创建成功后使用epoll_ctl向其中添加要监视的文件描述符和监视的事件。

struct epoll_event {uint32_t     event; /* epoll事件 */epoll_data_t data;  /* 用户数据 */
};int epoll_ctl(int                epfd,int                op,int                fd,struct epoll_event *event)
  • events:要监视的事件。

    • EPOLLIN 数据可读。
    • EPOLLOUT 可写数据。
    • EPOLLPRI 有紧急的数据需要读取。
    • EPOLLERR 指定的文件描述符发生错误。
    • EPOLLHUP 指定的文件描述符挂起。
    • EPOLLET设置epoll为边沿触发,默认为水平触发。
    • EPOLLONESHOT 一次性监视,当监视完成后还需要再次监视某个fd,需要将fd重新添加到epoll。
  • epfd:epoll_create创建的句柄。
  • op:对epfd进行的操作。
    • EPOLL_CTL_ADD 向epfd添加参数fd表示的描述符。
    • EPOLL_CTL_MOD 修改参数fd的event事件。
    • EPOLL_CTL_DEL 从epfd中删除fd文件描述符 。
  • fd:要监视的文件描述符。
  • event:要监视的事件类型。
  • 返回值:0,成功;-1,失败,并设置errno的值为相应的错误码。

应用程序使用epoll_wait等待事件发生。

int epoll_wait(int                 epfd;struct epoll_event *events;int                 maxevents;int                 timeout)
  • epfd:要等待的epoll。
  • events:指向epoll_event结构体的数组,当有事件发生时Linux内核会填写events。
  • maxevents:events数组大小,必须大于0。
  • timeout:超时时间,单位ms。
  • 返回值:0,超时;-1,错误;其他值,准备就绪的文件描述符数量。

设备驱动程序—poll操作函数

应用程序调用select或poll函数对驱动程序进行非阻塞访问时,驱动程序file_operations操作集中poll函数就会执行。

unsigned int (*poll)(struct file *filp, struct poll_table_struct *wait)
  • filp:要打开的设备文件,即文件描述符。
  • wait:poll_table_struct类型指针,应用程序传进来,wait参数传给poll_wait函数。
  • 返回值:向应用程序返回设备状态。
    • POLLIN              有数据可以读取
    • POLLPRI           有紧急的数据需要读取
    • POLLOUT          可以写数据
    • POLLERR          指定的文件描述符发生错误
    • POLLHUP          无效的请求
    • POLLNVAL         指定的文件描述符挂起
    • POLLRDNORM  等同于POLLIN

驱动程序的poll函数中调用poll_wait函数,poll_wait不会引起阻塞,只是将应用程序添加到poll_table中。

void poll_wait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)
  • filp:要打开的设备文件,即文件描述符。
  • wait_address:要添加到poll_table中的等待队列头。
  • p:file_operation中poll函数的wait参数。

【Linux驱动开发】阻塞和非阻塞IO相关推荐

  1. linux驱动开发 - 10_阻塞和非阻塞 IO

    文章目录 1 阻塞和非阻塞 IO 1.1 阻塞和非阻塞简介 1.2 等待队列 1.等待队列头 2.等待队列项 3.将队列项添加/移除等待队列头 4.等待唤醒 5.等待事件 1.3 Linux驱动下的p ...

  2. Linux驱动开发6 阻塞与非阻塞

    阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式,在编写驱动的时候 一定要考虑到阻塞和非阻塞.本章我们就来学习一下阻塞和非阻塞 IO ,以及如何在驱动程序中 处理阻塞与非阻塞, ...

  3. 嵌入式Linux 阻塞和非阻塞 IO 驱动设备访问模式

    阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式, 在编写驱动的时候一定要考虑到阻塞和非阻塞. 阻塞与非阻塞简介 阻塞操作是指在执行设备操作时, 若不能获得资源, 则挂起进程直 ...

  4. linux驱动系列学习之阻塞与非阻塞IO(六)

    一. 阻塞与非阻塞IO概念     阻塞操作是指在执行设备操作时,若不能获取资源,则挂起进程进入休眠状态,等待可满足条件后进行操作.被挂起的进程从调度器队列移动到挂起队列(睡眠状态).当操作驱动程序r ...

  5. linux驱动学习笔记(三)阻塞与非阻塞IO

    Linux驱动中阻塞与非阻塞IO 前言 阻塞 非阻塞 一.等待队列 1.等待队列头 2.等待队列 模板 二.轮询 模板 总结 前言 阻塞和非阻塞io是两种不同的设备访问方式. 阻塞 阻塞IO表示在执行 ...

  6. Linux设备驱动中的阻塞和非阻塞IO

    这篇文章我们来了解下Linux设备驱动中阻塞和非阻塞. 阻塞:阻塞是指执行设备操作时,如果不能获得设备资源,则挂起进程,是进程进入休眠模式,直到设备资源可以获取. 非阻塞:非阻塞是在不能获取设备资源时 ...

  7. Linux驱动(六)设备驱动中的阻塞与非阻塞IO

    我们在Linux学习(二十三)IO模型中了解了LINUX中IO模型,IO模型最简单的可以分为阻塞IO和非阻塞IO.并且学习了一个用如何使用阻塞操作和非阻塞操作.而应用层之所以能实现阻塞操作和非阻塞操作 ...

  8. linux驱动 阻塞和非阻塞IO 篇二

    @上一篇介绍了linux阻塞与非阻塞的基本概念,以及应用程序的小demo和kernel层对应的api函数.那接下来就以实例来分析,如何在linux驱动层添加等待队列和轮询的方法,以及区别. ** 一: ...

  9. linux驱动开发-阻塞非阻塞

    从并发的需要,引起的竞争状态,到解决竞争状态的方法机制: 禁止中断 信号量 自旋锁 Completion 原子操作 目标: 掌握进程睡眠和唤醒的方法  掌握阻塞型I/O的实现方法  掌握poll/se ...

  10. Linux 阻塞和非阻塞IO 实验

    目录 阻塞和非阻塞IO 阻塞和非阻塞简介 等待队列 轮询 Linux 驱动下的poll 操作函数 阻塞IO 实验 硬件原理图分析 实验程序编写 运行测试 非阻塞IO 实验 硬件原理图分析 实验程序编写 ...

最新文章

  1. ring0下的 fs:[124]
  2. shell的几个实战脚本例子(欠)
  3. html 图像 ppt,用HTML设置的文本和图像.ppt
  4. (138)System Verilog覆盖率目标设置
  5. 计算机软件知识pdf,[计算机软件及应用]PDF基础知识.doc
  6. shell提取sql数据库文件里的单个表
  7. mysql 多条件 sumif_关于求和我只用数据库函数Dsum,从不用Sum、Sumif等,你信吗?...
  8. 你对计算机专业考研知道多少
  9. cmd打开记事本并写字_Windows中的记事本和写字板之间有什么区别?
  10. 云计算时代迎接挑战方能脱颖而出
  11. hexo+yilia添加复制代码块的功能
  12. linux卷空间不足问题 gparted工具重新分配根分区空间
  13. 商户号该产品权限未开通,请前往商户平台,产品中心检查后重试
  14. 如何把microsoft store里面的软件添加到桌面
  15. python练习:简单火柴人游戏
  16. SCRM:SpringBoot + RabbitMQ + 企微 实现发送消息到企业微信
  17. photoshop安装_如何在Photoshop中安装画笔
  18. Mac 蓝牙鼠标卡顿的解决方法
  19. 全栈修炼:如何从Web前端迈向全栈开发
  20. sql语句基础-提升

热门文章

  1. MySQL 繁体转简体
  2. SD/MicroSD CARD PCB布局布线设计指南
  3. Chromedriver(谷歌浏览器驱动)安装教程
  4. web安全day1:wifi安全
  5. PKMS包管理服务分析-证书校验流程04
  6. 拜占庭协议(Byzantine Agreement)和拜占庭广播(Byzantine Braodcast)
  7. java打印三角形、等腰三角形、直角三角形
  8. width、margin-left、margin-right使用auto
  9. 【板栗糖GIS】3dmax—使用脚本批量导入obj格式时一直 需要点击重命名改如何解决
  10. 为什么你看了很多书 ,却依然没有洞见 (深度好文)