select poll epoll都是IO多路复用机制。这里的复用其实可以理解为复用的线程,即一个(或者较少的)线程完成多个IO的读写。这里总结下这三个函数的区别。

1 select

1.1 select原理分析

1 select的函数原型是

int select(int nfds,
fd_set *restrict readfds,
fd_set *restrict writefds,
fd_set *restrict errorfds,
struct timeval *restrict timeout);

使用的时候需要将fd_set从用户空间copy到内核空间。select的使用方式类似如下

while true {
select(streams[])
for i in streams[] { //需要遍历所有的fd_set
if i has data // 判断是否有数据
read until unavailable
}
}

2 select的核心是do_select()。do_select首先会注册回调函数__pollwait,__pollwait会在被调用的时候将当前进程添加到设备的等待队列里。

do_select会在一个for循环里调用设备的f_op->poll。而该函数有两个作用,一个是调用poll_wait()函数,一个是检测设备当前状态。而poll_wait会调用回调函数__pollwait,将当前进程加入到设备等待队列里。

设备自己实现了当有读写的时候会唤醒等待队列里的进程。如果当前没有设备可读写,那么do_select()就将当前进程睡眠。设备会在有读写的时候唤醒进程。唤醒后设备必须重新轮询一遍所有的设备,调用poll来检测设备当前的状态以确定哪些可写可读。

int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
{
struct poll_wqueues table;
poll_table *wait;
poll_initwait(&table); // 注册回调函数__pollwait
wait = &table.pt;
// …
for (;;) {
// …
for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
// …
struct fd f;
f = fdget(i);
if (f.file) {
const struct file_operations *f_op; // 重要
f_op = f.file->f_op; // 重要
mask = DEFAULT_POLLMASK;
if (f_op->poll) {
wait_key_set(wait, in, out,
bit, busy_flag);
// 对每个fd进行I/O事件检测
mask = (*f_op->poll)(f.file, wait); // 函数指针,每个设备自定义自己的poll。每个设备拥有一个struct file_operations结构体,这个结构体里定义了各种用于操作设备的函数指针,具体怎么操作是设备自己定义的
}
fdput(f);
// …
}
}
// 退出循环体
if (retval || timed_out || signal_pending(current))
break;
// 没有可读写,让进程进入休眠
if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
to, slack))
timed_out = 1;
}
}

3 file 结构

struct file {
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op; // … } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */

4 file_operations结构

struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
// select()轮询设备fd的操作函数, poll调用poll_wait, 而poll_wait会调用回调函数__pollwait, __pollwait将当前进程加到等待队列里
unsigned int (*poll) (struct file *, struct poll_table_struct *);
// …
};

5 简单总结来讲,select会遍历fd_set,调用f_op->poll(此poll非select/poll的poll),如果有可读/写的fd则返回可读/写的fd,如果没有则在每个fd的等待队列中加入当前进程,当前进程进入睡眠。当有fd可读/写的时候会唤醒当前进程,当前进行重新遍历fd_set,返回可读/写的所有fd。

6 从中也可以看出select的几大缺点:

  1. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  2. 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  3. select支持的文件描述符数量太小了,默认是1024

2 poll

poll的实现原理和select类似,只是接口的方式不同。

3 epoll

1 epoll的函数原型是

int epoll_create(int size); // 创建一个epoll对象,一般epollfd = epoll_create()

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件比如epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//有缓冲区内有数据时epoll_wait返回epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//缓冲区可写入时epoll_wait返回

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);  // 等待直到注册的事件发生

epoll的使用方式类似如下:

while true {
active_stream[] = epoll_wait(epollfd) // 只返回可读/写的fd,而不是像select一样,返回所有的fd
for i in active_stream[] {
read or write till unavailable
}
}

2 epoll_create。epoll会向内核注册一个文件系统,调用epoll_create时:

  1. 就会在这个虚拟的epoll文件系统里创建一个file结点;
  2. 并且在初始化的时候开辟epoll自己的内核cache,用于存储epoll_ctl传来的socket,这些socket以红黑树的方式组织放在cache里,用于支持快速的查找、插入、删除操作
  3. 还会再建立一个list链表,用于存储准备就绪的事件

3 epoll_ctl。 当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到list里了

4 epoll_wait。 epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep

5 可见,

  1. select在醒着的时候要遍历整个fd_set,而epoll只需要判断一下list是否为空就可以了,这节约了大量的cpu时间;
  2. select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次。这也能节省不少的开销

参考

  1. http://janfan.cn/chinese/2015/01/05/select-poll-impl-inside-the-kernel.html
  2. https://blog.csdn.net/wangfeng2500/article/details/9127421
  3. https://www.cnblogs.com/Anker/p/3265058.html
  4. https://www.zhihu.com/question/20122137

select poll和epoll的更多相关文章

  1. Linux下select, poll和epoll IO模型的详解

    http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll 介绍 Epoll 可是当前在 Linux 下开发大规模并发网络程序的热 ...

  2. I/O复用中的 select poll 和 epoll

    I/O复用中的 select poll 和 epoll: 这里有一些不错的资料: I/O多路复用技术之select模型: http://blog.csdn.net/nk_test/article/de ...

  3. (转)Linux下select, poll和epoll IO模型的详解

    Linux下select, poll和epoll IO模型的详解 原文:http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll ...

  4. linux select poll and epoll

    这里以socket文件来阐述它们之间的区别,假设现在服务器端有100 000个连接,即已经创建了100 000个socket. 1 select和poll 在我们的线程中,我们会弄一个死循环,在循环里 ...

  5. Select,poll,epoll复用

    Select,poll,epoll复用 1)select模块以列表的形式接受四个参数,分别是可读对象,可写对象,产生异常的对象,和超时设置.当监控符对象发生变化时,select会返回发生变化的对象列表 ...

  6. 聊聊select, poll 和 epoll

    聊聊select, poll 和 epoll 假设项目上需要实现一个TCP的客户端和服务器从而进行跨机器的数据收发,我们很可能翻阅一些资料,然后写出如下的代码. 服务端 void func(int s ...

  7. [转载] select, poll和epoll的区别

    源地址:http://sheepxxyz.blog.163.com/blog/static/61116213201022003513530/ 随着2.6内核对epoll的完全支持,网络上很多的文章和示 ...

  8. Linux中select poll和epoll的区别

    在Linux Socket服务器短编程时,为了处理大量客户的连接请求,需要使用非阻塞I/O和复用,select.poll和epoll是Linux API提供的I/O复用方式,自从Linux 2.6中加 ...

  9. Linux select/poll和epoll实现机制对比

    关于这个话题,网上已经介绍的比较多,这里只是以流程图形式做一个简单明了的对比,方便区分. 一.select/poll实现机制 特点: 1.select/poll每次都需要重复传递全部的监听fd进来,涉 ...

  10. select,poll 和 epoll ??

    其实所有的 I/O 都是轮询的方法,只不过实现的层面不同罢了. 其中 tornado 使用的就是 epoll 的. selec,poll 和 epoll 区别总结 基本上 select 有 3 个缺点 ...

随机推荐

  1. embarrass的writeup

    大家好,这次我要为大家带来都是攻防世界misc部分embarrass的writeup.     首先下载附件,是一个压缩包,解压后找到一个流量包.用wireshark打开,直接在搜索框中输入flag{ ...

  2. 基于隐私保护技术的DNS通信协议介绍

    本文提出了一种基于用户数据报协议的DNS传输中用户隐私保护的加密方法:DNSDEA.该方法采用PKI加密体系与DNS协议相融合,不仅解决了域名隐私保护问题,而且与传统DNS体系相兼容,保持了DNS系统 ...

  3. 【C# IO 操作 】内存包装类 Memory <T>和 Span<T> 相关类型

    简介 .NET 包含多个相互关联的类型,它们表示任意内存的连续的强类型区域. 这些方法包括: System.Span<T> 用于访问连续的内存区域 得到该类型的实例: 1个T类型的数组 1 ...

  4. 范围运算符和索引的最终运算符 ^ 在string 和数组中的应用

    //范围运算符在string 和数组中的应用 static void Main(string[] args) { string examplestring = "123456789" ...

  5. JZ-065-矩阵中的路径

    矩阵中的路径 题目描述 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径.路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子.如果一条路 ...

  6. LeetCode-023-合并K个升序链表

    合并K个升序链表 题目描述:给你一个链表数组,每个链表都已经按升序排列. 请你将所有链表合并到一个升序链表中,返回合并后的链表. 示例说明请见LeetCode官网. 来源:力扣(LeetCode) 链 ...

  7. Win10系统设置开机自启动

    有时候,我们想设置某些软件开机时自动启动,操作步骤如下: 1. win+R 同时按住键盘上的win和R键打开运行窗口 2. shell:startup 输入shell:startup后回车 3. 添加 ...

  8. Win10系统使用Gitblit搭建局域网Git服务器

    一.安装配置jdk 1.下载 下载地址:https://www.oracle.com/java/technologies/javase-jdk14-downloads.html 2.安装jdk 3.配 ...

  9. linux作业--第二周

    1.显示/etc目录下,以非字母开头,后面跟了一个字母以及其它任意长度任意字符的文件或目录 ls /etc/ | grep ^[^[:alpha:]][[:alpha:]].* 2.复制/etc目录下 ...

  10. java几种数据的默认扩容机制

    当底层实现涉及到扩容时,容器或重新分配一段更大的连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),要将容器原来的数据全部复制到新的内存上, 这无疑使效率大大降低.加载因 ...