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

news/2024/11/29 12:29:37/

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.ppmy.cn/news/1418482.html

相关文章

探索NDVI:了解植被指数的意义与应用

随着科技的进步和遥感技术的发展&#xff0c;我们能够更深入地了解地球上的植被覆盖情况&#xff0c;而其中一项重要的工具就是NDVI&#xff08;Normalized Difference Vegetation Index&#xff0c;归一化植被指数&#xff09;。NDVI不仅仅是一个数值&#xff0c;更是一扇窥探…

【Golang学习笔记】从零开始搭建一个Web框架(二)

文章目录 模块化路由前缀树路由 前情提示&#xff1a; 【Golang学习笔记】从零开始搭建一个Web框架&#xff08;一&#xff09;-CSDN博客 模块化路由 路由在kilon.go文件中导致路由和引擎交织在一起&#xff0c;如果要实现路由功能的拓展增强&#xff0c;那将会非常麻烦&…

文心一言VSchatGPT4

文心一言和GPT-4各有优势&#xff0c;具体表现在不同的测试场景下。 在某些测试场景中心一言的表现优于GPT-4&#xff0c;例如在故事的完整度和情节吸引力方面&#xff0c;文心一言表现得更加符合指令&#xff0c;情节更吸引人。这可能得益于其模型在训练时对中文语境的深入理…

咸鱼之王_手游_开服搭建架设_内购修复无bug运营版

视频演示 咸鱼之王_手游_开服 游戏管理后台界面 源码获取在文章末尾 源码获取在文章末尾 源码获取在文章末尾 或者直接下面 https://githubs.xyz/y28.html 1.安装宝塔 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh &…

智能革命:未来人工智能创业的天地

智能革命&#xff1a;未来人工智能创业的天地 一、引言 在这个数字化迅速变革的时代&#xff0c;人工智能(AI)已经从一个边缘科学发展成为推动未来经济和社会发展的关键动力。这一技术领域的飞速进步&#xff0c;不仅影响着科技行业的每一个角落&#xff0c;更是为创业者提供了…

物联网:门锁RNBN-K18使用记录

摘要&#xff1a;对 RNBN品牌下 K18智能门锁日常使用操作经验记录。 常见问题&#xff1a; 1.门锁联网时&#xff0c;找不到 wifi 怎么办。 答&#xff1a;检查一下几个方面&#xff1a;1. wifi 信号是否是2.4G&#xff0c;2.wifi信号是否距离没锁很远。因为门锁只能获取到2…

2024信息工程、软件与计算机工程国际会议(ICIESCE2024)

2024信息工程、软件与计算机工程国际会议&#xff08;ICIESCE2024) 会议简介 随着互联网的不断创新&#xff0c;信息工程、软件和计算机工程在各个领域得到了广泛应用。为了为来自世界各地的专家学者提供一个分享通信和计算机工程领域研究成果的平台&#xff0c;2024年信息工程…

docker 上达梦导入dump文件报错:本地编码:PG GBK,导入女件编码:PGGB18030

解决方案&#xff1a; 第一步进入达梦数据容器内部 docker exec -it fc316f88caff /bin/bash 第二步&#xff1a;在容器中 /opt/dmdbms/bin目录下 执行命令 cd /opt/dmdbms/bin./dimp USERIDSYSDBA/SYSDBA001 FILE/opt/dmdbms/ZFJG_LJ20240407.dmp SCHEMASZFJG_LJUSERIDSYSD…