eoll poll select总结

一、基础概念

1.I/O多路复用(multiplexing)

就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

2.用户空间 / 内核空间

现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。
操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。

3.进程切换
为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的,并且进程切换是非常耗费资源的。

4.进程阻塞/非阻塞
阻塞:正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得了CPU资源),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。

非阻塞:例如linux下,可以通过设置socket使其变为non-blockingv(默认阻塞),当用户进程发出recvfrom这个系统调用后,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个结果(no datagram ready)。从用户进程角度讲 ,它发起一个操作后,并没有等待,而是马上就得到了一个结果。

5.文件描述符
文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

6.缓存I/O
缓存I/O又称为标准I/O,大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中,操作系统会将I/O的数据缓存在文件系统的页缓存中,即数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

二、select

1.函数原型

int select(int nfds,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

1)nfds:select监视的文件句柄数,视进程中打开的文件数而定,一般设为你要监视各文件中的最大文件号加一

2)fd_set:可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄。中间的三个参数指定我们要让内核测试读、写和异常条件的文件描述符集合。如果对某一个的条件不感兴趣,就可以把它设为空指针。

readfds:select监视的可读文件句柄集合。

writefds: select监视的可写文件句柄集合。

exceptfds:select监视的异常文件句柄集合。

3)timeout:本次select()的超时结束时间。当readfds或writefds中映象的文件可读或可写或超时,本次select()就结束返回。

4)返回值:若有就绪描述符返回其数目,若超时则为0,若出错则为-1

2.原理

select()的机制中提供一种fd_set的数据结构,实际上是一个long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一Socket或文件可读。

3.优点

使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

4.缺点

1)每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销也很大

2)同时每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,那这个开销也很大

3)为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024)

三、poll

1.函数原型

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

1)struct pollfd *fds 是一个struct pollfd类型的数组,用于存放需要检测其状态的socket描述符

2)nfds_t nfds 记录数组fds中描述符的总数量

3)timeout:超时结束时间

4)返回值:函数返回fds集合中就绪的读、写,或出错的描述符数量,返回0表示超时,返回-1表示出错;

2.原理

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理

3.优点

poll没有最大文件描述符数量的限制

4.缺点

还是没有解决性能开销问题

四、epoll(epoll在2.6以后的内核才支持

1.函数原型

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

1)epoll_create 函数创建一个epoll句柄,参数size表明内核要监听的描述符数量。调用成功时返回一个epoll句柄描述符,失败时返回-1

2)epoll_ctl 函数注册要监听的事件类型。四个参数解释如下:

  • epfd 表示epoll句柄
  • op 表示fd操作类型,有如下3种
    • EPOLL_CTL_ADD 注册新的fd到epfd中
    • EPOLL_CTL_MOD 修改已注册的fd的监听事件
    • EPOLL_CTL_DEL 从epfd中删除一个fd
  • fd 是要监听的描述符
  • event 表示要监听的事件
struct epoll_event {
    __uint32_t events;  /* Epoll events */
    epoll_data_t data;  /* User data variable */
};

3)epoll_wait 函数等待事件的就绪,成功时返回就绪的事件数目,调用失败时返回 -1,等待超时返回 0。

  • epfd 是epoll句柄
  • events 表示从内核得到的就绪事件集合
  • maxevents 告诉内核events的大小
  • timeout 表示等待的超时事件

2.原理

调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个list链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。

水平触发(LT):默认工作模式,只要有数据就不停通知,直到没数据就不再通知了

边缘触发(ET): 只通知一次,ET模式很大程度上减少了epoll事件的触发次数,因此效率比LT模式下高

3.优点

1)调用epoll_ctl时把fd拷贝进内核并保存,之后每次epoll_wait不拷贝,就只拷贝了一次

2)它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行

五、参考资料

https://www.jianshu.com/p/397449cadc9a

https://www.jianshu.com/p/b8203d46895c

https://blog.csdn.net/HDUTigerkin/article/details/7517390

https://blog.csdn.net/linkedin_38454662/article/details/73337208

https://blog.csdn.net/wangpeihuixyz/article/details/41732127

你可能感兴趣的:(linux)