什么是IO多路复用
简单来说,就是解决能够同时操作多个设备的方法,及时处理多个设备的数据。
具体的,是指单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力。
IO多路复用的三种方法
Linux的IO多路复用有三种方法:select
、poll
、epoll
,且这三种方法都是系统调用。
这里介绍嵌入式中常用的两种select
和poll
。
select
相关接口
/* According to POSIX.1-2001, POSIX.1-2008 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_CLR(int fd, fd_set *set); void FD_ZERO(fd_set *set);
select把要监视的文件描述符分成三类:
readfds、
writefds、
exceptfds`。
readfds
是需要进行读操作的文件描述符,writefds
是需要进行写操作的文件描述符,exceptfds
是需要进行异常处理的文件描述符。对于不需要监听的,填NUL
L即可。
第一次对n个文件进行select()
的时候,若任何一个文件满足要求,select()
就直接返回;第2次再进行select()
的时候,没有文件满足读写要求,select()
的进程阻塞且睡眠。示意图如下:
fd_set
是文件描述符组,需要通过FD_XX系列的函数来操作。例如:
FD_ZERO
:清空文件描述符组
fd_set writefds; FD_ZERO(&writefds)
FD_SET
:添加一个文件描述符到组中
FD_CLR
:删除一个组中的文件描述符
FD_SET(fd, &writefds);//将fd加进组中 FD_CLR(fd, &writefds);//将fd从组中删除
FD_ISSET
:检测一个文件描述符是否在组中,我们用这个来检测一次select调用之后有哪些文件描述符可以进行IO操作
if (FD_ISSET(fd, &readfds)){ /* fd可读 */ }
poll
#include <poll.h> int poll(struct pollfd fds[], nfds_t nfds, int timeout);
poll()
函数也是一个系统调用,可实现类似select()
的效果。与select()
不同的是,select
将要监听的文件描述符分为读、写、异常三种,而poll
则使用一个文件描述符集struct pollfd fds[]
来管理。
struct pollfd
用来描述一个需要监听的文件描述符,传参时传入struct pollfd
类型数组fds
。
参数说明
参数 | 说明 |
fds | 一个struct pollfd结构类型的数组,用于存放需要检测其状态的文件描述符集; |
nfds | 用于标记数组fds中的结构体元素的总数量 |
timeout | 阻塞的时间,单位:毫秒;如果timeout==0,那么poll() 函数立即返回而不阻塞,如果设置为负数,那么poll() 函数会一直阻塞下去,直到所检测的文件描述符上的感兴趣的事件发生是才返回。 |
返回值:
- >0:数组fds中准备好读、写或出错状态的那些文件描述符的总数量
- ==0:代表poll超时
- -1:代表poll函数调用失败,同时会自动设置全局变量errno
struct pollfd结构体
struct pollfd{ int fd; /*文件描述符*/ short events; /* 等待的需要测试事件 */ short revents; /* 实际发生了的事件,也就是返回结果 */ }
events和revents事件值:
常量 | 说明 |
POLLIN | 普通或优先级带数据可读 |
POLLRDNORM | 普通数据可读 |
POLLRDBAND | 优先级带数据可读 |
POLLPRI | 高优先级数据可读 |
POLLOUT | 普通数据可写 |
POLLWRNORM | 普通数据可写 |
POLLWRBAND | 优先级带数据可写 |
POLLERR | 发生错误 |
POLLHUP | 发生挂起 |
POLLNVAL | 描述字不是一个打开的文件 |
poll系统调用过程
poll系统调用在内核中的入口函数是sys_poll()
,sys_poll()
的系统调用关系如下:
关键点是在vfs_poll()
函数:
//include/linux/poll.h static inline __poll_t vfs_poll(struct file *file, struct poll_table_struct *pt) { if (unlikely(!file->f_op->poll)) return DEFAULT_POLLMASK; return file->f_op->poll(file, pt);//调用到驱动中的poll函数 }
file->f_op->poll(file,pt)
会调用到驱动中的poll
函数.
驱动中poll机制实现
驱动中只需要实现struct file_operations
结构体里的poll
函数,代码如下:
static unsigned int my_poll(struct file *fp, struct poll_table_struct *wait) { unsigned int mask = 0; poll_wait(fp, &btn_waitq, wait);//将驱动中的等待队列btn_waitq添加进等待队列表table中 if(ev_press) mask |= POLLIN|POLLRDNORM; return mask; } static struct file_operations my_fops = { .owner = THIS_MODULE, ...... .poll = my_poll, };
应用程序调用poll()
时,内核中会调用每个设备驱动中的poll
函数,这些底层函数都会调用poll_wait()
将本设备驱动中的等待队列添加到一个等待队列表中(table)
如果监控设备没有事件发生,在调用完所有要监控设备驱动的poll
函数后,进程会休眠(table)
如果任何一个监控的设备有事件发生,内核中的poll
又会重新带调用每个设备驱动中的poll
方法。看看有多少个设备有事件发生,然后才返回到应用层。
end
猜你喜欢: