Fd详解
Fd:是一个正整型数,实际是为文件描述符表(数组)的索引,文件描述符表中保存着已打开的文件的指针。
每一个进程在PCB(Process Control Block)即进程控制块中都保存着一分文件描述符表,文件描述符就是这个表的索引,文件描述符表中每个表项都有一个指向已打开文件的指针。现在我们明确一下:已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。
file结构体才是内核中用来描述文件属性的结构体。
系统调用详解
系统调用的基本概念:通常,在操作系统中都设置了一组用于实现各种系统功能的子程序,并将它们提供给应用程序调用。
程序接口是 OS 专门为用户程序设置的,也是用户程序取得 OS 服务的唯一途径。程序接口通常是由各种类型的系统调用所组成的,因而,也可以说,系统调用提供了用户程序和操作系统之间的接口,应用程序通过系统调用实现其与 OS 的通信,并可取得它的服务。
操作系统中提供了系统调用,使应用程序可以通过系统调用的方法,间接调用操作系统的相关过程,取得相应的服务。
软件中断实现系统调用:
- 软件中断:它是通过软件指令触发的中断。Linux系统内核响应软件中断,从用户态切换到内核态,执行相应的系统调用。
1.Select
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select允许应用程序监视一组文件描述符,等待一个或者多个文件描述符成为就绪状态,从而完成I/O操作。
-
fd_set 使用数组实现,数组大小使用 FD_SETSIZE 定义,所以只能监听少于 FD_SETSIZE 数量的描述符。有三种类型的描述符类型:readset、writeset、exceptset,分别对应读、写、异常条件的描述符集合。
-
timeout 为超时参数,调用 select 会一直阻塞直到有描述符的事件到达或者等待的时间超过 timeout。
-
成功调用返回结果大于 0,出错返回结果为 -1,超时返回结果为 0。
2.Poll
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
//当timeout为-1时,poll调用将会永远阻塞,直到某个事件发生;timeout为0时,则立即返回
poll 的功能与 select 类似,也是等待一组描述符中的一个成为就绪状态。
Select与Poll比较:
- select默认监听的文件描述符最大是1024,但可以修改,poll则没有文件描述符的限制。
- poll 提供了更多的事件类型,并且对描述符的重复利用上比 select 高。
3.Epoll
/*
1.epoll采用一组函数来完成任务,而不是一个函数
2.epoll把用户关心的文件描述符上的事件放在内核里的事件表中,从而无须像select和poll每次调用都要重复传入文件描述符集或事件集
*/
//创建一个指示epoll内核事件表的文件描述符,该描述符将用作其他epoll系统调用的第一个参数,size不起作用。
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);
-
epoll_ctl()用于向内核注册新的描述符或者改变某个文件描述符的状态。已注册的文件描述符在内核中会被维护在一棵红黑树上。
-
epoll比select和poll更灵活,没有文件描述符数量的限制。
-
工作模式:
epoll 的描述符事件有两种触发模式:LT(level trigger)和 ET(edge trigger)。
-
LT模式
当epoll_wait()检测fd上有事件发生并将此事件通知应用程序后,将此事会通知给进程,进程可以不立即处理该事件,当应用程序下一次调用epoll_wait()时,epoll_wait还会再次向应用程序通知此事件,直到此事件被处理。同时支持 Blocking 和 Non-Blocking。
-
ET模式
当epoll_wait()检测fd上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续epoll_wait()将不会再向应用程序通知该事件。
只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
ET模式在很大程度上降低了epoll事件被重复触发的次数,因此ET模式效率比LT模式高。
-
EPOLLONESHOT
为了避免多个多个线程同时操作一个Socket,可以注册EPOLLONESHOT事件。
我们期望的是一个socket连接在任一时刻都只被一个线程处理,通过epoll_ctl对该文件描述符注册epolloneshot事件,一个线程处理socket时,其他线程将无法处理,当该线程处理完后,需要通过epoll_ctl重置epolloneshot事件
-
同于异
- select和poll的文件描述符是在用户态加入文件描述符集合的,每次调用都需要将整个集合拷贝至内核态;而epoll的文件描述符维护在内核态,每次添加文件描述符的时候都需要执行一次系统调用。
- select使用线性表描述文件描述符集合,文件描述符有上限;poll用链表描述,无上限;epoll则用红黑树描述文件描述符集合,且也没有上限。
- select和poll需要通过遍历整个文件描述符集合,判断哪个文件描述符上有事件发生;而epoll会维护一个ready list,会将就绪事件添加至list,每次调用epol_wait的时候,仅观察list是否有数据即可。
应用场景
-
select应用场景
elect 的 timeout 参数精度为微秒,而 poll 和 epoll 为毫秒,因此 select 更加适用于实时性要求比较高的场景,比如核反应堆的控制。
select 可移植性更好,几乎被所有主流平台所支持。
-
poll应用场景
poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。
-
epoll应用场景
只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接,因为Epoll的文件的描述符都存在内核中,需要通过系统调用epoll_ctr来改变文件描述符状态,频繁操作会降低效率。
需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。