轮询操作基于Linux设备驱动开发详解

int select(int numfds, fs_set readfds, fd_set writefds, fd_set *exceptfds, struct timeval *timeout);
numfds:指集合中所有所有文件描述符的范围,所有描述符的最大值加1
readfds:被select()监视的读操作的文件描述符集合.我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读数据了,如果这个集合中有一个文件可读,select就会返回一个大于零的值,表明有文件可读;如果没有文件可读,则根据timeout参数判断是否超时,若超出timeout的时间,select返回0;若发生错误,返回负值。可以传入NULL值,表示不关心任何文件的读变化。

writefds:被select()监视的写操作的文件描述符的集合
exceptfds:被select()监视的异常处理的文件描述符集合

timeout:timeout是select的超时时间,这个参数很重要,它可以使select处于三种状态:1.若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;2.将时间值设置为0秒0毫秒,就变成纯碎的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;3.timeout的值大于0,这就是select的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回。

函数说明:select()函数用来等待文件描述符状态的改变。参数numfds代表最大的文件描述符加1,
struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,fd_set集合可以通过一些宏来操作:
FD_ZERO(fd_set *set) ; //清除一个文件描述符集合;
FD_SET(int fd, fd_set *set); // 将一个文件描述符加入文件描述符集合中
FD_ISSET(int fd, fd_set *set); // 判断文件描述符是否被置位
FD_CLR(int fd, fd_set *set); // 将一个文件描述符从文件描述符集合中清除

错误代码:
执行成功返回文件描述符已改变的个数,返回0表示在文件描述符状态改变前已超timeout设定的时间,有错发生时返回-1,错误原因存于errno,此时参数readfds 、writefds、exceptfds和timeout的值不可预测。
EBADF:文件描述符无效或者文件已经关闭
EINTR:此调用被信号所中断
EINVAL:参数numfds为负值
ENOMEM:核心内存不足

struct timeval {
int tv_sec; // 秒
int tv_usec; // 微妙
};

poll()的功能和实现原理与select()相似,函数原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll和select的工作流程:
1.复制用户数据到内核空间
2.估计超时时间
3.遍历每个文件并调用f_op-poll取得文件当前就绪状态,如果前面遍历的文件都没有就绪,向文件插入wait_queue节点
4.遍历完成后检查状态:
a)如果已经有就绪的文件转到5;
b)如果有信号产生,重启poll或select(转到1或3);
c)否则挂起进程等待超时或唤醒,超时或被唤醒后再次遍历所有文件取得每个文件的就绪状态
5.将所有文件的就绪状态复制到用户空间
6.清理申请的资源
linux 内核poll/select/epoll实现剖析(https://www.xuebuyuan.com/3234247.html)
poll/select/epoll 对比
通过以上的分析可以看出,poll和select的实现基本是一致,只是用户到内核传递的数据格式有所不同,

select和poll即使只有一个描述符就绪,也要遍历整个集合。如果集合中活跃的描述符很少,遍历过程的开销就会变得很大,而如果集合中大部分的描述符都是活跃的,遍历过程的开销又可以忽略。

epoll的实现中每次只遍历活跃的描述符(如果是水平触发,也会遍历先前活跃的描述符),在活跃描述符较少的情况下就会很有优势,在代码的分析过程中可以看到epoll的实现过于复杂并且其实现过程中需要同步处理(锁),如果大部分描述符都是活跃的,epoll的效率可能不如select或poll。(参见epoll 和poll的性能测试 http://jacquesmattheij.com/Poll+vs+Epoll+once+again)

select能够处理的最大fd无法超出FDSETSIZE。

select会复写传入的fd_set 指针,而poll对每个fd返回一个掩码,不更改原来的掩码,从而可以对同一个集合多次调用poll,而无需调整。

select对每个文件描述符最多使用3个bit,而poll采用的pollfd需要使用64个bit,epoll采用的 epoll_event则需要96个bit

如果事件需要循环处理select, poll 每一次的处理都要将全部的数据复制到内核,而epoll的实现中,内核将持久维护加入的描述符,减少了内核和用户复制数据的开销。

与epoll相关的用户空间编程接口包括:
int epoll_create(int size);
创建一个epoll句柄,size用来告诉内核需要监听多少个fd。当创建号epoll句柄后,它本身也会占用一个fd值,所以要在使用完epoll后,必须调用close()关闭。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
告诉内核要监听什么类型的事件,参数epfd时epoll_create()的返回值,参数op表示动作,包含:
EPOLL_CTL_ADD:注册新的fd到epfd;
EPOLL_CTF_MOD:修改已经注册的fd监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数fd是要监听的fd;第四个参数event是告诉内核需要监听的事件类型,

struct epoll_event {
__uint32_t events ; // Epoll events
epoll_data_t data; // User data variable
};
events可以是以下几个宏的“域”
EPOLLIN:表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里表示的是有socket数据到来);
EPOLLERR:表示对应文件描述符有错误发生;
EPOLHUP:表示对应的文件描述符被挂断;
EPOLLET:将epoll设为边缘出发(Edge Triggered)模式,这时相对于水平触发(Level Triggered)来说的。LT(Level Triggered)是缺省的工作方式,在LT情况下,内核告诉用户一个fd是否就绪了,之后用户可以对这个就绪的fd惊醒I/O操作。但是如果用户不进行任何操作,该事件并不会丢失,而ET(Edge Triggered)是高速工作模式,在这种模式下,当fd从未就绪变为就绪,内核通过嗯poll告诉用户,然后他会假设用户已经知道fd已经就绪,并且不会为那个fd发送更多的就绪通知。
EPOLLONESHOT:意味着一次性监听,当监听完这次事件后,如果还要继续监听这个fd的话,需要再次把这个fd加入到epoll队列

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
等待事件的产生,其中events参数是输出参数,用来从内核达到事件的集合,maxevents参数告诉内核本次最多接受多少事件,maxevents最多不能大于常见epoll_create()时的size,参数timeout是超时时间(以毫秒为单位,0意味着立刻返回,-1意味着永久等待)。返回值是需要处理的时间数目,如已经返回0,表示已经超时。
设备驱动中poll()函数的原型:
unsigned int (*poll)(struct file *filp, struct poll_table *wait);
第一个参数为file结构体指针,第二个参数为轮询表指针,这个函数功能:
1.对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头部添加到poll_talble中;
2.返回值表示是否能对设备进行无阻塞读、写访问的掩码;

用于向poll_table注册队列的关键poll_wait()函数原型:
void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait);
poll_wait()函数所做的工作是把当前进程添加到wait参数所指向的等待列表(poll_wait)中,

// poll()函数的典型模板
static unsigned int xxx_poll(struct file* filp, poll_table *wait)
{
unsigned int mask = 0;
struct xxx_dev *dev=filp->private_data; // 获得设备结构体指针
…..
poll_wait(filp, &dev->r_wait, wait);//加入读等待队列
poll_wait(filp,&dev->w_wait,wait); // 加入写等待队列
if()// 可读
mask |= POLLIN | POLLRDNORM;
if() // 可写
mask |= POLLOUT | POLLWRNORM;

return mask;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值