Linux第86步_了解“阻塞和非阻塞IO”以及相关处理函数

1、IO

“应用程序”对“驱动设备“进行输入/输出操作,简称IO操作,它是Input和Output的缩写。

2、阻塞IO

阻塞IO是“应用程序”对“驱动设备”进行操作,若不能获取到设备资源,则阻塞IO应用程序的线程会被“挂起”,直到获取到设备资源为止。

挂起”就是让线程进入休眠,将CPU的资源让出来。线程进入休眠后,当设备文件可以操作时,就必须唤醒这个休眠的线程。通常是在中断函数里完成唤醒工作,Linux内核是采用“等待队列(wait queue)”来完成阻塞线程的唤醒工作。

阻塞IO应用举例:

int fd;

int data = 0;

fd = open("/dev/xxx_dev", O_RDWR);    /* 以阻塞方式打开 */

ret = read(fd, &data, sizeof(data));  /* 读取数据 */

3、非阻塞IO

非阻塞IO是“应用程序”对“驱动设备”进行操作,若不能获取到设备资源,则非阻塞IO应用程序的线程不会被“挂起”,即线程不进入休眠,而是一直“轮询”,直到获取到设备资源为止,或者直接放弃。

非阻塞IO应用举例:

int fd;

int data = 0;

fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK);

/* 非阻塞方式打开 */

/*O_NONBLOCK表示如果路径名指向FIFO/块文字/字符文件,则把文件打开和后继I/O设置为非阻塞*/
ret = read( fd, &data, sizeof(data) );           /* 读取数据 */

4、等待队列

wait_queue_head结构体,需要包含头文件“#include <linux/wait.h>

struct wait_queue_head {

  spinlock_t lock;

  struct list_head head;

};

typedef struct wait_queue_head  wait_queue_head_t;

//给“wait_queue_head”起个别名叫“wait_queue_head_t”

//目的是兼容老版本的代码

初始化“等待队列头”

void init_waitqueue_head(struct wait_queue_head *wq_head)

//wq_head是要初始化的“等待队列头”

//使用宏 DECLARE WAIT OUEUE HEAD一次性完成等待队列头的定义和初始化

“等待队列头”是等待队列的头部,每个访问设备的进程都是一个队列,当不能获取到设备资源的时候,就要将“进程对应的队列项”添加到“等待队列”里面。

wait_queue_entry结构体如下:

struct wait_queue_entry {

    unsigned int      flags;

    void          *private;

    wait_queue_func_t func;

    struct list_head  entry;

};

DECLARE_WAITQUEUE(name, tsk)

//给“当前正在运行的进程tsk”创建并初始化一个“等待队列项name”

//name就是等待队列项的名字

//tsk表示这个等待队列项属于哪个任务(进程),一般设置为current

注意:

在Linux内核中current相当于一个全局变量,表示当前进程;

void add_wait_queue( struct wait_queue_head   *wq_head,

                     struct wait_queue_entry  *wq_entry)

//将“等待队列项wq_entry”添加到“等待队列头wq_head”,允许进程睡眠;

//wq_head:“等待队列项”要加入的“等待队列头”

//wq_entry:要加入的“等待队列项”

void remove_wait_queue( struct wait_queue_head *wq_head,

                        struct wait_queue_entry *wq_entry)

//将“等待队列项wq_entry”从“等待队列头wq_head”中删除,允许访问设备

//wq head:要删除的“等待队列项”所处的“等待队列头”

//wq_entry:要删除的“等待队列项”

等待唤醒1

void wake_up(struct wait_queue_head *wq_head)

//由“驱动程序”去唤醒进入休眠的进程,属于主动唤醒

//wq_head:要唤醒的“等待队列头”

等待唤醒2

void wake_up_interruptible(struct wait_queue_head *wq_head)

//由“驱动程序”去唤醒进入休眠的进程,属于主动唤醒

//wq_head:要唤醒的“等待队列头”

设置“等待队列”等待某个事件,当这个事件满足以后,就可以自动唤醒“等待队列的进程”。

等待事件1

wait_event(wq_head, condition)

//由“wq_head为等待队列头的等待队列”去唤醒进程;

//当condition条件满足(为真)时会执行唤醒,否则会一直阻塞。

//唤醒后,会将进程设置为 TASK_UNINTERRUPTIBLE 状态,即进程不能被信号打断。

等待事件2

wait_event_interruptible(wq_head, condition)

//等待以“wq_head为等待队列头的等待队列”被唤醒,属于等待队列唤醒;

//当condition条件满足(为真)时会执行唤醒,否则会一直阻塞。

//唤醒后,会将进程设置为 TASK_INTERRUPTIBLE 状态,即进程可以被信号打断。

等待事件3

wait_event_timeout(wq_head, condition, timeout)

//等待以“wq_head为等待队列头的等待队列”被唤醒,属于等待队列唤醒;

//如果返回值为0,表示超时,且condition为假,阻塞直到超时,再执行唤醒;

//如果返回值为1,表示condition为真,阻塞直到条件满足,再执行唤醒;

等待事件4

wait_event_interruptible_timeout(wq_head, condition, timeout)

//等待以“wq head为等待队列头的等待队列”被唤醒,属于等待队列唤醒;

//如果返回值为0,表示超时,且condition为假,阻塞直到超时,再执行唤醒;

//如果返回值为1,表示condition为真,阻塞直到条件满足,再执行唤醒;

//唤醒后,会将进程设置为TASK_INTERRUPTIBLE状态,即进程可以被信号打断。

5、轮询

在非阻塞处理方式中,poll()、epoll()和select()用于处理轮询若不能获取到设备资源,就从设备读取或者向设备写入数据。当应用程序调用poll()、epoll()或select()函数时,设备驱动程序中的poll()函数就会被执行,因此需要在设备驱动程序中编写poll()函数。

应用程序需要包含“#include <poll.h>

驱动程序需要包含“#include <linux/poll.h>

1)、select()函数

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

//nfds所要监视的这“三类文件描述集合”中,“最大文件描述符”加1;

/*readfds用于监视指定描述符集的“读变化”,监视这些文件是否可以读取;

若可以读取,则返回大于0的值;若没有文件可以读取,则根据timeout判断是否超时;*/

/*writefds用于监视指定描述符集的“写变化”,监视这些文件是否可以写入;

若文件可以执行写操作,则返回大于0的值;若没有文件可以写入,则根据timeout判断是否超时;*/

//exceptfds用于监视文件的异常

//timeout超时时间

struct timeval {

  long tv_sec; /* 秒 */

  long tv_usec; /* 微妙 */

}

当timeout为NULL时,表示无限期等待;

缺点:

在单个线程中,select()函数能够监视的“文件描述符数量”有最大的限制,一般为 1024;我们可以修改内核,将监视的“文件描述符数量”改大,但是这样做会降低效率

void FD_ZERO(fd_set *set)

//将fd_set型变量的所有位都清零,即将所有的文件描述符从fd_set中删除;

void FD_SET(int fd, fd_set *set)

//将fd_set型变量的某个位置1,即向fd_set添加一个文件描述符;

//fd是要加入的文件描述符

void FD_CLR(int fd, fd_set *set)

//将fd_set型变量的某个位置0,即将一个文件描述符从fd_set中删除;

//fd是要删除的文件描述符

int FD_ISSET(int fd, fd_set *set)

//测试一个文件是否属于某个集合

//参数fd就是要判断的文件描述符

select()函数非阻塞读访问举例:

void main(void)

{

  int ret, fd;    /* 要监视的文件描述符 */

  fd_set readfds; /* 读操作文件描述符集 */

  struct timeval timeout; /* 超时结构体 */

  fd = open("dev_xxx", O_RDWR | O_NONBLOCK);

  //打开文件成功,则fd 为“文件描述符”

  /* 非阻塞式访问 */

  /*O_NONBLOCK表示如果路径名指向FIFO/块文字/字符文件,则把文件打开和后继I/O设置为非阻塞*/

  FD_ZERO(&readfds);    /* 清除readfds */

//readfds变量的所有位都清零,即将所有的文件描述符从readfds中删除;

  FD_SET(fd, &readfds); /* 将fd添加到readfds里面 */

  /* 构造超时时间 */

  timeout.tv_sec = 0;

  timeout.tv_usec = 500000; /*设置超时时间为500ms */

  ret = select(fd + 1, &readfds, NULL, NULL, &timeout);

//“fd + 1”表示“最大文件描述符”加1

/*readfds用于监视指定描述符集的“读变化”,监视这些文件是否可以读取;

若可以读取,则返回大于0的值;若没有文件可以读取,则根据timeout判断是否超时;*/

//NULL不关注写

//NULL不关注文件的异常

//timeout设置超时时间为500ms;

  switch (ret)

  {

     case 0: /* ret=0超时 */

        printf("timeout!\r\n");

        break;

     case -1: /* 错误 */

        printf("error!\r\n");

        break;

     default: /* 可以读取数据 */

     if( FD_ISSET(fd, &readfds) ) /* 判断是否为fd文件描述符 */

     {

        /* 使用read函数读取数据 */

     }

     break;

   }

}

2)、poll()函数

int poll(struct pollfd *fds, nfds_t nfds, int timeout)

//fds是一个pollfd型结构数组
//nfds是要监视的文件描述符数量

//timeout超时时间,单位为ms;

//函数返回值为0,超时;

//函数返回值为-1,发生错误,并且设置errno为错误类型

//成功:函数返回值为revents域中不为0的pollfd结构体个数,也就是发生事件或错误的文件描述符数量;

缺点:poll()函数都会随着所监听的fd数量的增加,出现效率低下,且每次必须遍历“所有的描述符”来检查就绪的描述符,这个过程很浪费时间;

struct pollfd {

  int fd;         /* 要监视的文件描述符*/

  short events;   /* 要监视的事件,若文件描述符无效,则监视事件无效*/

  short revents/* 返回的事件*/

};

events事件类型:

POLLIN     有数据可以读取。

POLLPRI    有紧急的数据需要读取。

POLLOUT    可以写数据。

POLLERR    指定的文件描述符发生错误。

POLLHUP    指定的文件描述符挂起。

POLLNVAL  无效的请求。

POLLRDNORM  有数据可以读取,等同于POLLIN;

poll()函数读非阻塞访问应用举例:

void main(void)

{

  int ret;

  int fd; /* 要监视的文件描述符 */

  struct pollfd fds;

  fd = open(filename, O_RDWR | O_NONBLOCK);

  //打开文件成功,则fd 为“文件描述符”

  /* 非阻塞式访问 */

  /*O_NONBLOCK表示如果路径名指向FIFO/块文字/字符文件,则把文件打开和后继I/O设置为非阻塞*/

  /* 构造结构体 */

  fds.fd = fd; /* 要监视的文件描述符*/

  fds.events = POLLIN; /* 监视数据是否可以读取 */

  ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时500ms */

//fds是一个pollfd型结构数组
//nfds=1是要监视的文件描述符数量

//timeout=500,超时时间,单位为ms;

  if (ret) /* 数据有效 */

  {

    ......

    /* 读取数据 */

    ......

  }

  else if (ret == 0) /* 超时 */

  {

    ......

  }

  else if (ret < 0) /* 错误 */

  {

    ......

  }

}

3)、epoll()函数

int epoll_create(int size)

//size:从Limux2.6.8开始该参数已经没有意义了,设置size>0就可以了;

//返回值:成功返回的是epoll句柄,如果为-1的话,表示创建失败;

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

//epfd:要操作的epoll句柄,也就是使用 epoll_create()函数创建的epoll句柄。

//op:表示要对epfd(epoll句柄)进行操作

EPOLL CTL ADD   向epfd添加文件参数fd表示的描述符;

EPOLL CTL MOD   修改参数fd的 event 事件;

EPOLL CTL DEL   从epfd中删除fd描述符;

//fd要监视的文件描述符

//event为epoll_event型结构指针,表示要监视的事件类型;

struct epoll_event {

    uint32_t events;   /* epoll事件 */

    epoll_data_t data; /* 用户数据*/

};

events事件类型:

POLLIN     有数据可以读取。

POLLOUT    可以写数据。

POLLPRI    有紧急数据需要读取。

POLLERR    指定的文件描述符发生错误。

POLLHUP    指定的文件描述符挂起。

EPOLLET    设置epo1l为边沿触发,默认触发模式为水平触发

EPOLLONESHOT  一次性的监视,当监视完成以后还需要再次监视某个fd ,那么就需要将fd重新添加到epoll里面;

注意:

上面这些事件可以进行“或”操作,也就是说可以设置监视多个事件

//函数返回值为0,成功;

//函数返回值为-1,失败,并且设置 errno 的值为相应的错误码;

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

epfd:要等待的 epoll;

events:指向epoll_event 结构体的数组,当有事件发生的时候,Linux内核会填写events,调用者可以根据 events 判断发生了哪些事件;

maxevents:events数组大小,必须大于0;

timeout:超时时间,单位为 ms;

函数返回值:0,超时;-1,错误;其他值,准备就绪的文件描述符数量;

注意:

epoll()更多的是用在大规模的“并发服务器”上,因为在这种场合下select()和 poll()并不适合。当设计到的文件描述符(fd)比较少的时候就适合用selcet()和poll();

4)、Linux驱动下的poll()函数

需要包含“#include <linux/delay.h>

unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)

filp:要打开的设备文件(文件描述符);

wait:poll_table_struct类型指针,由应用程序传递进来的。一般将此参数传递给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)

//wait_address是要添加到poll_table中的等待队列头;

//p是 poll_table型指针,就是file_operations中poll()函数的wait参数;

6、绑定信息文档

设备树是用来描述板子上的设备信息,不同的设备其信息不同,反映到设备树中就是属性不同。

在设备树中,添加一个硬件对应的节点,我们从哪里查阅相关的说明呢?

在Linux内核源码中,有详细的TXT文档描述了如何添加节点,这些TXT文档叫做绑定文档,路径为:

Linux 源码目录/Documentation/devicetree/bindings

绑定文档Documentation/devicetree/bindings/gpio/gpio.txt,详细描述了 gpio 控制器节点各个属性信息;

led0 {

       compatible = "zgq,led";

       status = "okay";

       led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;

    };

    key0 {

       compatible = "zgq,key";

       status = "okay";

       key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;

       interrupt-parent = <&gpiog>;/*指定父中断器为&gpiog*/

       interrupts = <3 IRQ_TYPE_EDGE_FALLING>;

       /*指定中断号为3,中断类型和触发方式为下降沿触发*/

    };

EXTI控制器的设备树绑定信息参考文档 :

Documentation/devicetree/bindings/interrupt-controller/st,stmm32-exti.txt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/815717.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用LNMP部署动态网站环境

目录 实验环境 一、配置LNMP架构环境 二、验证部署的LNMP 动态网站环境是否可用 三、配置过程中遇到的问题及解决思路 实验环境 centos7 192.168.81.131/24 一、配置LNMP架构环境 概念及配置手册参考第20章 使用LNMP架构部署动态网站环境。 | 《Linux就该这么学》 安装g…

Java编程练习之接口的声明及实现

1.创建老师类和学生类&#xff0c;两个类都实现了问候接口和工作接口&#xff0c;模拟上课的场景&#xff0c;运行效果如下&#xff1a; package Zaria; interface hello{public void speak(); } interface work{public void dowork(); } class Student implements hello,work{…

PCL 基于马氏距离KMeans点云聚类

文章目录 一、简介二、算法步骤三、代码实现四、实现效果参考资料一、简介 在诸多的聚类方法中,K-Means聚类方法是属于“基于原型的聚类”(也称为原型聚类)的方法,此类方法均是假设聚类结构能通过一组原型刻画,在现实聚类中极为常用。通常情况下,该类算法会先对原型进行初始…

嵌入式与移动物联网开发教程和案例

一、嵌入式与移动物联网概述 嵌入式系统是指嵌入到设备中的专用计算机系统&#xff0c;用于控制、监视或辅助设备操作。而移动物联网则是指通过物联网技术将各种智能设备与互联网连接起来&#xff0c;实现设备之间的互联互通和智能化管理。嵌入式与移动物联网技术的结合&#…

深入剖析Cargo缓存机制

一、介绍 Cargo作为Rust的包管理工具&#xff0c;不仅在项目构建中扮演了重要的角色&#xff0c;其高效的缓存机制也为Rust开发者节省了大量的时间。本文将深入探讨Cargo的缓存原理和使用技巧&#xff0c;并提供丰富的示例让你轻松掌握Cargo缓存的管理和优化。 二、Cargo缓存…

相位校正啊

相位校正是信号处理中的一种常见技术&#xff0c;用于确保多个信号在相位上对齐&#xff0c;这对于后续的信号分析和处理至关重要。在处理三相信号时&#xff0c;相位校正尤为重要&#xff0c;因为它可以保证三相之间的相位关系准确&#xff0c;从而正确分析信号特性和检测问题…

MongoDB聚合运算符:$pow

文章目录 语法使用 举例 MongoDB聚合运算符&#xff1a; p o w ‘ pow pow‘pow聚合运算符用于求数字指定的指数并返回结果。 语法 { $pow: [ <number>, <exponent> ] }参数说明&#xff1a; <number>表达式可以是任何可解析为数值的表达式<exponent&g…

golang 协程题目

都是一个货色&#xff0c;要么使用无缓冲channel, 要么使用有缓冲chanwaitgroup等待协程退出&#xff0c;或者使用全局变量判断是否终止协程 2个协程交替打印奇数和偶数 无缓冲channel实现 package mainimport "fmt"func main() {maxval : 10ch1 : make(chan stru…

python——双下划线

一、名称修饰&#xff08;Name Mangling&#xff09; 在Python中&#xff0c;当你在一个类中定义一个方法&#xff0c;其名称以两个下划线 __ 开头但不以两个下划线结尾时&#xff08;例如&#xff1a;__private_method&#xff09;&#xff0c;Python会对这个方法名进行名称修…

gitee如何新建仓库并用小乌龟上传代码

目录 1.登录并注册gitee账号 2.创建新仓库 3.填写仓库信息 4.初始化本地仓库 5.上传数据 7.gitee官网查看上传文件 8.如何安装小乌龟 1.登录并注册gitee账号 2.创建新仓库 登录后&#xff0c;点击页面右上角的「」按钮&#xff0c;选择「新建仓库」。 3.填写仓库信息 …

@JvmDefaultWithout/WithCompatibility

JvmDefaultWithoutCompatibility JvmDefaultWithCompatibility 使用 JvmDefaultWithoutCompatibility Used only with -Xjvm-defaultall-compatibility. JvmDefaultWithCompatibility Used only with -Xjvm-defaultall. 区别 前置知识 当一个接口包含默认方法&#xff0…

入门Adaptive AUTOSAR(一) -- 为什么要提Adaptive(1)

目录 1.Adaptive AUTOSAR 1.1 AUTOSAR的由来 1.2 AUTOSAR的方法论 1.3 Why Adaptive 2.小结 1.Adaptive AUTOSAR 1.1 AUTOSAR的由来 2017年&#xff0c;国内绝大部分供应商还在思考如何用最小代价切入到AUTOSAR Classic Platform的时候&#xff0c;AUTOSAR Adaptive Pla…

把持中国互联网流量的“四大家族”,各个牛逼plus!

中国互联网80%流量被四大家族把持着&#xff0c;其余要么去这些家族批发流量&#xff0c;要么去抢占剩余20%。 以下是对中国互联网流量四大家族的介绍和代表性的流量入口产品&#xff1a; 百度系&#xff1a; 百度是中国最大的搜索引擎公司&#xff0c;其搜索引擎百度是中国互…

【位运算】Leetcode 两整数之和

题目解析 371. 两整数之和 算法讲解 异或的本质就是无进位相加&#xff0c;但是我们需要处理进位&#xff0c;就需要知道哪一位上有进位&#xff0c;再让无进位相加的结果 进位即可&#xff0c;在重复这个过程&#xff0c;当进位等于0的时候&#xff0c;说明相加的过程已经结…

OSPF防环文档

OPSF在区域内会产生俩类LSA&#xff1a;Router LSA &#xff0c;Network LSA 路由器以自己为树根构建最短路径树 &#xff0c;这里的最短路径树按两步形 成&#xff0c;第一步&#xff0c;仅考虑路由器和传输网络之间的连接。通过 Dijkstra 算法&#xff0c;根据链路状态数据…

[Linux] keytool 命令(by Copilot)

keytool 是一个用于管理密钥和证书的 Java 工具。它可以用于生成、导入、导出和修改密钥库和证书。以下是一些常用的 keytool 命令和参数&#xff1a; 1.生成密钥对&#xff1a; 使用 -genkeypair 命令生成密钥对&#xff0c;例如&#xff1a; keytool -genkeypair -alias m…

Docker - MongoDB

博文目录 文章目录 说明命令后置 说明 Docker Hub MongoDB 数据卷数据卷印射在容器内的路径mongo/data/dbmongo.config.db/data/configdb 容器内的路径说明/data/db数据目录/data/configdb不太清楚 部分环境变量是否必要说明MONGO_INITDB_DATABASE可选设置数据库的名称, 首次…

吴恩达2022机器学习专项课程(一) 第二周课程实验:特征工程和多项式回归(Lab_04)

目标 探索特征工程和多项式回归&#xff0c;使用线性回归来拟合非常复杂甚至非线性的函数。 1.为什么线性回归能拟合非线性函数&#xff1f; fxw*xb&#xff0c;属于线性回归的扩展&#xff0c;这个公式在数学中不属于线性&#xff0c;因为有x&#xff0c;而在机器学习中属于…

图文教程 | 2024Typora最新版免费激活使用教程(新旧版可用)

一、打开官网下载最新版Typora Typora 官网下载 安装&#xff1a; Typora中文官网&#xff1a;https://typoraio.cn/ Typora官网&#xff1a;https://typora.io/releases/all 官网长这个样子 下面这个不是官网&#xff01;&#xff01;&#xff01;&#xff01;注意&#x…

通讯录的实现(顺序表版本)

我们知道通讯录是基于顺序表的前提下&#xff0c;要写好通讯录我们就要深入了解好顺序表。我们先来看看什么是顺序表。&#xff08;注意今天代码量有点多&#xff0c;坚持一下&#xff09;。冲啊&#xff01;兄弟们&#xff01; 顺序表的简单理解 对于顺序表&#xff0c;我们首…