2

#导入Word文档图片# 阻塞与非阻塞IO操作​

 1 year ago
source link: https://blog.51cto.com/u_11822586/5474034
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Linux内核版本:linux3.5​

1.1 简介

阻塞操作是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,从CPU调度器的运行队列中移走, 直到等待的条件被满足。 而非阻塞操作的进程在获取不到资源时不会进入睡眠状态, 它或者放弃获取资源, 或者不停地查询, 直到资源获取为止。

驱动程序通常需要提供这样的能力:当应用程序进行 read()、 write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备, 驱动程序应在设备驱动的 xxx_read()、 xxx_write()等操作中将进程阻塞直到资源可以获取, 此后, 应用程序的 read()、 write()等调用才返回, 整个过程仍然进行了正确的设备访问,用户并没有感知到;若用户以非阻塞的方式访问设备文件, 则当设备资源不可获取时, 设备驱动的 xxx_read()、 xxx_write()等操作应立即返回, read()、 write()等系统调用也随即被返回。​

阻塞从字面上听起来似乎意味着低效率,实则不然,如果设备驱动不阻塞,则用户想获取设备资源只能不停地查询,这反而会无谓地耗费 CPU 资源。 而阻塞访问时,不能获取资源的进程将进入休眠, 它将 CPU 资源让给其他进程。​

因为阻塞的进程会进入休眠状态,因此,必须确保有一个地方能够唤醒休眠的进程。唤醒进程的地方最大可能发生在中断里面,因为硬件资源获得的同时往往伴随着一个中断。​

1.2 进程的状态

Linux将进程状态描述为如下五种:​

TASK_RUNNING :可运行状态。处于该状态的进程可以被调度执行而成为当前进程。​

TASK_INTERRUPTIBLE :信号可中断的睡眠状态。处于该状态的进程在所需资源有效时被唤醒,也可以通过信号或定时中断唤醒(因为有signal_pending()函数)。​

TASK_UNINTERRUPTIBLE:不可中断的睡眠状态。处于该状态的进程仅当所需资源有效时被唤醒。​

TASK_ZOMBIE :僵尸状态。表示进程结束且已释放资源,但其task_struct仍未释放。​

TASK_STOPPED :暂停状态。处于该状态的进程通过其他进程的信号才能被唤醒。​

进程调度函数,让出CPU使用权:​

schedule(void)​

设置进程的状态(一般设置的状态为以上定义的五种):​

set_current_state(state);​

1.3 等待队列

在 Linux 中, 一个等待队列由一个"等待队列头"来管理, 一个 wait_queue_head_t 类型的结构, 定义在<linux/wait.h>中. 一个等待队列头可被定义和初始化。​

DECLARE_WAIT_QUEUE_HEAD(name);​

或者动态定义如下:​

wait_queue_head_t my_queue;​

init_waitqueue_head(&my_queue);​

等待队列可以用来同步对系统资源的访问,前面所讲述的信号量在内核中也依赖等待队列来实现。​

1.3.1 等待队列头休眠相关的API函数

1.3.1.1 静态初始化等待队列头

#define DECLARE_WAIT_QUEUE_HEAD(name) \​

wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name) ​

静态初始化数据。定义并且初始化一个等待队列头结构变量,名字为name。​

name:要定义的队列头变量名​

所在头文件​

include\linux\wait.h​

宏定义文件​

include\linux\wait.h ​

1.3.1.2 动态初始化等待队列头

/*动态初始化等待队列头*/​

#define init_waitqueue_head(q)\​

do {\​

static struct lock_class_key __key;\​

__init_waitqueue_head((q), #q, &__key);\​

} while (0)​

动态初始一个等待队列头​

q:要定义的队列头变量名指针​

所在头文件​

include\linux\wait.h​

宏定义文件​

include\linux\wait.h ​

wait_queue_head_t wq; 定义全局变量)​

init_waitqueue_head(&wq); 要在其他函数内调用初始化)​

上面这两行等效于:DECLARE_WAIT_QUEUE_HEAD(wq)​

1.3.1.3 休眠进程(不可中断)

#define wait_event(wq, condition) \​

do {\​

if (condition)\​

break;\​

__wait_event(wq, condition);\​

} while (0)​

让进程在等待队列头上休眠。在等待会列中睡眠直到condition为真。在等待的期间,进程会被置为TASK_UNINTERRUPTIBLE(不可中断)进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值。​

注意:这里的中断指的是信号(软中断)。​

比如:ctrl+c信号等。 信号会进行阻塞,如果在进程睡眠时发送了信号给进程,这时进程是收不到信号的,但是信号并没有消失,当进程被唤醒的时候就会收到信号!​

wq:要休眠的队列头 ; condition:休眠的条件,0时休眠,否则不进入休眠,不能直接传递数字,要传递变量。​

所在头文件​

include\linux\wait.h​

宏定义文件​

include\linux\wait.h ​

1.3.1.4 休眠进程(可中断)

#define wait_event_interruptible(wq, condition)\​

int __ret = 0;\​

if (!(condition))/*判断--->条件为假的时候进入休眠*/\​

__wait_event_interruptible(wq, condition, __ret);\​

__ret;\​

让进程在等待队列头上休眠。wait_event()的区别是调用该宏在等待的过程中当前进程会被设置为TASK_INTERRUPTIBLE休眠状态.在每次被唤醒的时候,首先检查condition是否为真,如果为真则返回,否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码.如果是condition为真,则返回0.​

wq:要休眠的队列头 ; condition:休眠的条件,0时休眠,否则不进入休眠​

所在头文件​

include\linux\wait.h​

宏定义文件​

include\linux\wait.h ​

1.3.1.5 休眠进程(可以指定时间)(不可中断)

#define wait_event_timeout(wq, condition, timeout)\​

long __ret = timeout;\​

if (!(condition)) \​

__wait_event_timeout(wq, condition, __ret);\​

__ret;\​

让进程在等待队列头上休眠。与wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0.​

wq:要休眠的队列头 ; ​

condition:休眠的条件,0时休眠,否则不进入休眠;​

timeout:超时时间,单位是时钟节拍,一般使用:1 * HZ (1秒)这样形式定义。​

所在头文件​

include\linux\wait.h​

宏定义文件​

include\linux\wait.h ​

1.3.1.6 休眠进程(可以设置超时时间)(可中断)

#define wait_event_interruptible_timeout(wq, condition, timeout)\​

long __ret = timeout;\​

if (!(condition))\​

__wait_event_interruptible_timeout(wq, condition, __ret); \​

__ret;\​

让进程在等待队列头上休眠。与wait_event_timeout()类似​

如果在睡眠期间被信号打断则返回-ERESTARTSYS错误码​

wq:要休眠的队列头 ; ​

condition:休眠的条件,0时休眠,否则不进入休眠;​

timeout:超时时间,单位是时钟节拍,一般使用:5 * HZ (5秒)这样形式定义。​

所在头文件​

include\linux\wait.h​

宏定义文件​

include\linux\wait.h ​

1.3.2唤醒进程相关的函数API

上面介绍的是让进程休眠函数,接下来就是唤醒进程的函数。​

1.3.2.1 唤醒休眠的进程(中断与非中断都可以)

#define wake_up(x)__wake_up(x, TASK_NORMAL, 1, NULL)​

可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE状态的进程,​

与wait_event/wait_event_timeout成对使用。​

x:要唤醒的队列头指针 ;​

所在头文件​

include\linux\wait.h​

宏定义文件​

include\linux\wait.h ​

示例:wake_up(&wq) ​

说明:如果驱动里阻塞了5个进程,只会唤醒第一个休眠的进程,按照先后顺序。​

1.3.2.2 唤醒休眠的进程(可中断类型)

#define wake_up_interruptible(x)__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)​

和wake_up()唯一的区别是它只能唤醒TASK_INTERRUPTIBLE状态的进程。​

wait_event_interruptible ​

wait_event_interruptible_timeout​

wait_event_interruptible_exclusive ​

成对使用.​

x:要唤醒的队列头指针 ; ​

所在头文件​

include\linux\wait.h​

宏定义文件​

include\linux\wait.h ​

1.3.2.3 唤醒所有休眠的进程(可中断的进程)

#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)​

唤醒队列中所有的进程(处于TASK_INTERRUPTIBLE(信号可中断的睡眠状态)状态)。​

x:要唤醒的队列头指针 ;​

所在头文件​

include\linux\wait.h​

宏定义文件​

include\linux\wait.h ​

1.3.2.4 唤醒所有休眠的进程(可中断和不可中断都可以)

#define wake_up_all(x)__wake_up(x, TASK_NORMAL, 0, NULL)​

唤醒队列头x上所有的进程。​

x:要唤醒的队列头指针​

所在头文件​

include\linux\wait.h​

宏定义文件​

include\linux\wait.h ​

1.3.2.5 唤醒进程(指定个数)

#define wake_up_nr(x, nr)__wake_up(x, TASK_NORMAL, nr, NULL)​

类似除了它们能够唤醒多达 nr 个独占等待者, 而不只是一个. 注意传递 0 被解释为请求所有的互斥等待者都被唤醒。​

x:要唤醒的队列头指针 ;nr:要唤醒的独占等待进程的个数。​

所在头文件​

include\linux\wait.h​

宏定义文件​

include\linux\wait.h ​

1.3.3等待队列的旧接口

功能与 上面讲的wait_event相关函数功能一样!也是将进程加入休眠队列中!​

extern void sleep_on(wait_queue_head_t *q);​

extern long sleep_on_timeout(wait_queue_head_t *q,​

signed long timeout);​

extern void interruptible_sleep_on(wait_queue_head_t *q);​

extern long interruptible_sleep_on_timeout(wait_queue_head_t *q,​

signed long timeout);​

1.3.4 其他函数

/*添加等待队列 */​

extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);​

//该函数也和add_wait_queue()函数功能基本一样,只不过它是将等待的进程(wait)设置为互斥进程​

extern void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);​

extern void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait); 卸载等待队列​

1.3.5 使用示例

/*2.1 静态初始化等待队列头*/​

static DECLARE_WAIT_QUEUE_HEAD(button_wait);​

/*2.2定义等待队列唤醒的条件*/​

static int wait_condition=0; ​

/*2.3 将当前进程加入到等待队列中*/​

wait_event(button_wait,wait_condition);​

/*2.4 唤醒睡眠的进程*/​

wait_condition=1;​

wake_up(&button_wait);​

1.4 poll机制

使用非阻塞 I/O 的应用程序常常使用 poll, select和 epoll 系统调用。Poll,select 和 epoll 本质上有相同的功能: 每个允许一个进程来决定它是否可读或者写一个或多个文件而不阻塞。这些调用也可阻塞进程直到任何一个给定集合的文件描述符可用来读或写。 因此,它们常常用在使用多输入输出流的应用程序。​

epoll机制、poll机制和select机制都可以同时监控读和写事件。

支持任何一个这些调用都需要来自设备驱动的支持. 这个支持(对所有3个调用)由驱动的 poll 方法调用. 这个方法由下列的原型:​

unsigned int (*poll) (struct file *filp, poll_table *wait);​

1.4.1 poll的核心结构体

(应用层)Linux系统里直接查看帮助。比如:man poll​

struct pollfd {​

int fd; /*文件描述符*/​

short events; /*将要检测的事件*/​

short revents; /*返回的事件,不需要用户赋值,该值由驱动层直接返回,存放检测到的事件*/​

1.4.2 poll支持的事件类型

POLLIN​

普通或优先级带数据可读​

POLLRDNORM​

普通数据可读​

POLLRDBAND​

优先级带数据可读​

POLLPRI​

高优先级数据可读​

POLLOUT​

普通数据可写​

POLLWRNORM​

普通数据可写​

POLLWRBAND​

优先级带数据可写​

POLLERR​

发生错误​

POLLHUP​

发生挂起​

POLLNVAL​

描述字不是一个打开的文件​

1.4.3 poll应用层的接口函数

#include <poll.h>​

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

struct pollfd *fds 将要监控的文件描述符。(一般定义为结构体数组)​

nfds_t nfds :监控的文件描述符个数​

int timeout :监控阻塞的时间--毫秒为单位​

注意:时间单位>0就是正常的超时时间。​

就是永不超时(阻塞)​

就是不进行休眠​

返回值:小于等于0表示没有任何事件发生,大于0表示有事件发生!​

Poll可以同时监控多个文件描述符,结构体参数一般定义为结构体数组的形式赋值,每一个结构体数组下标就保存了一个需要检测的文件描述符信息。​

  • fd_set本质是一个 unsigned long 类型。​

内核使用它的每个位表示一个文件描述符号。​

#define __NFDBITS(8 * sizeof(unsigned long))​

#define __FD_SETSIZE1024​

#define __FDSET_LONGS(__FD_SETSIZE/__NFDBITS)​

typedef struct {​

//定义一片连续的内存​

//每一个位有一个编号0,1,2,3,4…… 1023​

//有128个字节,8*128 = 1024​

unsigned long fds_bits [__FDSET_LONGS]; ​

} __kernel_fd_set;​

typedef __kernel_fd_setfd_set;​

示例:如果要查询文件描述符为 1 和5的文件是否可读:则readfds 设置为:0010 0010(二进制数量),设置方法:​

FD_SET(1, &readfds);​

FD_SET(5, &readfds);​

1.4.4 poll驱动端接口

该函数在struct file_operations文件操作集合中定义。​

unsigned int (*poll) (struct file *, struct poll_table_struct *);​

在驱动接口中调用的休眠函数(本质与等待队列一样):​

void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)​

1.4.5 poll机制唤醒函数

#define wake_up_poll(x, m)\​

__wake_up(x, TASK_NORMAL, 1, (void *) (m))​

#define wake_up_locked_poll(x, m)\​

__wake_up_locked_key((x), TASK_NORMAL, (void *) (m))​

#define wake_up_interruptible_poll(x, m)\​

__wake_up(x, TASK_INTERRUPTIBLE, 1, (void *) (m))​

#define wake_up_interruptible_sync_poll(x, m)\​

__wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))​

或者直接调用以下函数:​

#define wake_up(x)__wake_up(x, TASK_NORMAL, 1, NULL)​

1.4.6 poll使用步骤示例

1.4.6.1驱动端编写框架

#include <linux/init.h>​

#include <linux/module.h>​

#include <linux/wait.h> ​

#include <linux/sched.h> /*进程调度相关 __add_wait_queue()*/​

#include <linux/poll.h> ​

/*静态初始化等待队列头*/​

static DECLARE_WAIT_QUEUE_HEAD(button_wait);​

static int wait_condition=0; /*定义等待队列唤醒的条件*/​

//中断处理函数​

static irqreturn_t irq_handler(int irq, void *dev)​

...........................................................................​

将休眠的进程唤醒*/​

wait_condition=1;​

wake_up(&button_wait);​

return IRQ_NONE;​

//poll接口​

unsigned int button_poll(struct file *my_file, struct poll_table_struct *poll)​

将当前进程加入到等待队列*/​

poll_wait(my_file,&button_wait,poll);​

if(!wait_condition)​

没有可用的事件​

清除标志位*/​

wait_condition=0;​

返回可用的事件​

//虚拟文件操作集合​

struct file_operations button_fops=​

.open=button_open,​

.release=button_release,​

.read=button_read,​

.poll=button_poll​

.............................................................​

1.4.6.2 应用端编写框架

#include <stdio.h>​

#include <poll.h>​

int main(int argc,char **argv)​

int fd;​

/*1.1 定义poll机制的结构体*/​

struct pollfd fds[1];​

fd=open(argv[1],2);​

/*1.2 要监控的文件描述符*/​

fds[0].fd=fd; ​

/*1.3 设置将要检测的事件*/​

fds[0].events=POLLIN;​

while(1)​

/*1.4 使用poll机制检测事件*/​

poll(fds,1,5000);​

/*1.5 判断监测的事件是否产生*/​

if(fds[0].revents &POLLIN)​

执行的代码段........................​

return 0;​

1.5 select轮询机制

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:​

(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。​

(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。​

(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。​

(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。​

(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。​

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。​

select与poll机制一样都是轮询机制! ​

Select最大的文件描述符个数由FD_SETSIZE决定!​

#define FD_SETSIZE__FD_SETSIZE /*FD大小*/​

#define __FD_SETSIZE1024 //默认为1024​

1.5.1 select的应用层函数接口

1.5.1.1 相关头文件

#include <sys/select.h>​

#include <sys/time.h>​

#include <sys/types.h>​

#include <unistd.h>​

1.5.1.2 Select函数接口

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

  1. int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有打开的文件描述符中最大的那一个文件描述符的基础上加1(可以使用if比较大小,得到值最大的那一个文件描述符),不能错!​
  2. fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。 ​​
  3. fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 ​​
  4. fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。​
  5. struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态:​第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;​
    第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;​
    第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。​

    注意:struct timeval* timeout结构体填入正常时间后,struct timeval* timeout结构体中的时间值会递减。递减到0之后就表示超时时间到达,所以:没有调用完select函数后,需要重新给struct timeval* timeout结构体赋值,否则时间将会是错误的。
  6. 返回值:小于等于0表示没有任何事件发生,大于0表示有事件发生(表示发生的事件的个数)。​

1.5.1.3 设置状态

void FD_CLR(int fd, fd_set *set); //将fd从set结构中清除 ​

int FD_ISSET(int fd, fd_set *set); //检测事件,如果FD状态发生变化返回真​

void FD_SET(int fd, fd_set *set); //添加文件描述符,可以重复添加多个!​

void FD_ZERO(fd_set *set); //每次循环都要清空集合,否则不能检测描述符变化​

1.5.1.4 struct timeval结构

struct timeval {​

毫秒 */​

1.5.1.5 select使用示例

1. 应用层框架

#include <stdlib.h>​

#include <stdio.h>​

#include <sys/types.h>​

#include <sys/stat.h>​

#include <fcntl.h>​

#include <poll.h>​

#include <sys/select.h>​

#include <sys/time.h>​

#include <unistd.h>​

#define POLL_NUM 2​

int main(int argc,char**argv)​

if(argc!=3)​

格式:./app /dev/xxxx /dev/xxx\n");​

exit(-1);​

int fd[POLL_NUM];​

int i;​

int key_val;​

int cnt=0;​

int max_fd=0;//最大的文件描述符​

for(i=0;i<POLL_NUM;i++)​

fd[i]=open(argv[i+1],O_RDONLY);​

max_fd= fd[i]>max_fd ? fd[i] : max_fd;​

if(fd[i]<0)​

printf("%s:驱动打开失败!\n",argv[i+1]);​

exit(-1);​

fd_set readfds;​

fd_set writefds;​

fd_set errorfds;​

struct timeval timeout;​

while(1)​

添加需要监控的文件描述符​

FD_SET(fd[0],&readfds);​

FD_SET(fd[1],&readfds);​

FD_SET(fd[0],&writefds);​

FD_SET(fd[1],&writefds);​

FD_SET(fd[0],&errorfds);​

FD_SET(fd[1],&errorfds);​

timeout.tv_sec=2;​

timeout.tv_usec=1000;​

监控是否有事件发生​

if(select(max_fd+1,&readfds,&writefds,&errorfds,&timeout))​

for(i=0;i<POLL_NUM;i++)​

注意:errorfds保存的是异常事件集合。底层驱动返回-1即可得知​

if(FD_ISSET(fd[i],&errorfds))​

read(fd[i],&key_val,4);​

printf("错误事件: 0x%x\n",key_val);​

continue;​

if(FD_ISSET(fd[i],&readfds))​

read(fd[i],&key_val,4);​

printf("可读事件: 0x%x\n",key_val);​

if(FD_ISSET(fd[i],&writefds))​

read(fd[i],&key_val,4);​

printf("可写事件: 0x%x\n",key_val);​

清空集合里的所有文件描述符​

FD_ZERO(&readfds);​

FD_ZERO(&writefds);​

FD_ZERO(&errorfds);​

printf("cnt=%d\n",cnt++);​

for(i=0;i<POLL_NUM;i++)​

close(fd[i]);​

return 0;​

2. 驱动框架

同poll机制!​

1.6 epoll机制

poll机制的升级版!​

1.6.1 相关的数据结构体

查看帮助:man epoll_wait

typedef union epoll_data {​

void *ptr;​

int fd;​

uint32_t u32;​

uint64_t u64;​

} epoll_data_t;​

struct epoll_event {​

事件*/​

数据结构*/​

1.6.2 相关的API函数

头文件:#include <sys/epoll.h>

1.6.2.1 等待事件

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

  • epfd :由epoll_create 生成的epoll专用的文件描述符;​
  • epoll_event :用于回传已经检测成功的事件数组;​
  • maxevents :每次监控的最大事件个数;​
  • Timeout (单位是毫秒):-1相当于永久阻塞,0相当于非阻塞。一般用-1即可​
  • 返回值:产生的事件个数​

1.6.2.2 创建文件描述符

int epoll_create(int size);​

  • 函数功能:​

该函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你监控的描述符 fd上是否产生了什么事件。size就是你在这个epoll fd上能存放的最大文件描述符fd个数。只要你有空间,随便定。​

  • 返回值:​

epoll专用的文件描述符。​

1.6.2.3 注册epoll事件

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

  • 函数功能:该函数用于设置某个epoll文件描述符上的事件,可以注册事件,批改事件,删除事件。​

epfd:由 epoll_create 生成的epoll专用的文件描述符;​

op:要进行的事件操作,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除。​

  • 事件类型定义如下:​

#define EPOLL_CTL_ADD 1 //注册​

#define EPOLL_CTL_DEL 2 //删除​

#define EPOLL_CTL_MOD 3 //修改​

fd:将要监控的文件描述符;​

event:指向epoll_event的指针;​

  • 返回值:若是调用成功返回0,不成功返回-1​

1.6.2.4 填充事件结构体

ev.events = EPOLLIN; //监控发生的事件​

ev.data.fd = fd; //要监听的文件描述符​

常用的事件类型:​

  • EPOLLIN :默示对应的文件描述符可以读;​
  • EPOLLOUT:默示对应的文件描述符可以写;​
  • EPOLLPRI:默示对应的文件描述符有紧急的数据可读​
  • EPOLLERR:默示对应的文件描述符产生错误;​
  • EPOLLHUP:默示对应的文件描述符被挂断;​
  • EPOLLET :默示对应的文件描述符有事务产生;​

1.6.3 epoll使用框架

1.6.3.1 应用层

#include <stdio.h>​

#include <poll.h>​

#include <sys/types.h>​

#include <sys/stat.h>​

#include <fcntl.h>​

#include <sys/epoll.h>​

#include <errno.h>​

#include <stdlib.h>​

/*设置最大的事件个数*/​

#define MAX_EVENTS 10​

int main(int argc,char **argv)​

int fd;​

if(argc!=2)​

printf("用法: ./app /dev/xx\n");​

return -1;​

fd=open(argv[1],O_RDWR);​

if(fd<0)​

printf("%s 设备节点打开失败!\n",argv[1]);​

return -1;​

int key=0;​

int cnt=0;​

int epollfd,n;​

int nfds;​

struct epoll_event ev, events[MAX_EVENTS];​

创建一个epoll专用的文件描述符*/​

epollfd = epoll_create(10);​

if (epollfd == -1) ​

perror("epoll_create");​

exit(EXIT_FAILURE);​

填充结构体*/​

ev.events = EPOLLIN;​

ev.data.fd = fd; //要监听的描述符​

/*1.3 设置某个epoll文件描述符事件*/​

if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) ​

perror("epoll_ctl: listen_sock");​

exit(EXIT_FAILURE);​

for (;;) ​

/*1.4 阻塞的等待某个事件发生,1000毫秒之后自动返回*/​

nfds = epoll_wait(epollfd, events, MAX_EVENTS,1000);​

if (nfds == -1) ​

perror("epoll_pwait");​

exit(EXIT_FAILURE);​

判断具体发生的事件*/​

for (n = 0; n < nfds; ++n) ​

if (events[n].data.fd == fd) ​

读取按键值*/​

read(fd,&key,4);​

printf("APP->key: 0x%X\n",key);​

return 0;​

1.6.3.2 驱动层框架

参照poll机制!​


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK