数据包从网络中到达网卡时候,会直接将数据通过DMA写入socket buffer(内核)中,这时候 网卡会给cpu发送一个中断告诉cpu来数据包这个事件,中断服务程序 即网卡的驱动程序执行 ,处理消费内核中的数据。(网卡收到数据包,DMA到内核内存,中断通知内核数据有了,内核按轮次处理消耗数据包,一轮处理完成后,开启硬中断(开硬中断的作用是可以接受后序的硬件中断请求)。
这个过程中 有 select poll 和epoll 等函数?你明白他们的作用吗?
这几个函数的主要作用就是从监控的文件描述符集合中找到有读写事件的文件描述符 返回。即对IO事件的监控。
select 函数是需要构造自己需要监控的读写fd_set,然后传入内核空间,注意用户空间初始构造的fd_set 其实就是一个long类型的数组,通过其中某一位的比特位表示某个fd是否被加入到监控(读 写 异常 )集合中,通常来说使用32个long(32)或者16个long(64)就可以表示1024个文件描述符。所以FD_SET(fd,&readfdset) 就是将fd 位置1,比如 fd 值为 67 则会将第2个long 的第3个置1。 之后经过select 操作 则内核会检查这些文件描述符是否有读写事件到来,从而返回readfdset给用户空间。用户空间拿到数据之后同样需要o(n)遍历之后才能找到哪个文件描述符对应的读写事件就就绪了。
它实际上是long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。
poll 函数 中 存放文件描述符的数据结构为
struct pollfd {
int fd; // the socket descriptor
short events; // bitmap of events we're interested in
short revents; // when poll() returns, bitmap of events that occurred
};
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
poll同样是需要将用户空间定义的数据结构链表传入内核中,当某些文件描述符对应的io事件到来的时候,会修改pollfd ,最后返回给用户空间,所以也存在内核空间到用户空间的数据拷贝,也存在o(n)的遍历时间。
有一个提升就是 这时候不在有监控的文件描述符的 数量限制。
epoll 是对poll函数的改进。
epoll 主要涉及三个函数 epoll_create 创建一个红黑树来存放要监听的socket 返回一个代表树结构的文件描述符。
在内核cache里建了个 红黑树 用于存储以后epoll_ctl传来的socket外,还会再建立一个list链表,用于存储准备就绪的事件.
当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。
epoll相比于select并不是在所有情况下都要高效,例如在如果有少于1024个文件描述符监听,且大多数socket都是出于活跃繁忙的状态,这种情况下,select要比epoll更为高效,因为epoll会有更多次的系统调用,用户态和内核态会有更加频繁的切换。
epoll_ctl 用来传入待监听的socket 文件描述符,及其感兴趣的io事件。对应的就是对红黑树的添加修改删除操作。
epoll_wait 时只需观察就绪列表是否有事件就绪。如果为空则没有 如果不为空则有。
int epoll_create(int size);//创建一个epoll的句柄,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 通过回调机制 可以准确知道 哪些文件描述符相关的文件缓冲区 有了io事件。
epoll 通过mmap 来减少内核空间到用户空间的拷贝
epoll 不会随着监控的文件描述符数量增多而性能下降。
个人总结:
epoll 高效的原因是 其加入了事件回调机制,当相关文件 读写缓冲区io事件就绪时,就会通过回调函数将红黑树上的结点加入到就绪链表中,之后epoll_wait就可以返回。