概念说明
- 内核态(内核空间)和用户态(用户空间)的区别和联系
- 用户空间是用户进程所在的内存区域,系统空间是操作系统所在的内存区域
- 为了保证内核安全,处于用户态的程序只能访问用户空间,而处于内核态的程序可以访问用户空间和内核空间。
2.文件描述符fd
- linux将所有设备都当做文件来处理,文件描述符来表示每个文件对象。
- 当程序打开一个现有文件或创建一个新文件时, 内核想进城返回一个文件描述符。
3.缓存IO
- Linux的缓存IO机制中,操作系统会将IO的数据缓存在文件系统的页缓存中,也就是说数据会先被拷贝到操作系统内核的缓冲区,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
IO模式
对于一次IO访问(以read为例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,会经历两个阶段:
1.等待数据准备(Waiting for the data to be ready)
2.将数据从内核拷贝到进程中(Copy the data from kernel to the process)
linux系统产生了下面五种网络模式的方案:
- 1.阻塞IO (blocking IO)
- 2.非阻塞IO (nonblocking IO)
- 3.IO多路复用 (IO multiplexing)
- 4.信号驱动IO (signal driven IO) 不常用
- 5.异步IO (asynchonous IO)
1.阻塞模式
- 1.当用户进程调用了系统函数read,kernel就开始了IO的第一个阶段,准备数据(此时有可能还没有到一个完整的包,这个时候kernel就需要等待足够的数据到来),这时用户进程是阻塞的(当然是进程自己选择的阻塞)。
- 2.当kernel一直等到数据准备好,他就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才接触block的状态,重新运行起来。
所以blockingIO的特点就是在IO执行的两个阶段都被block了
2.非阻塞IO
Linux下。可以通过设置socket使其变为non-block。当对一个non-blocking socket执行读操作时:
- 1.当用户进程发出read操作时,如果kernel中还没有准备好数据,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲,它发起一个read操作后,并不需要等待。而是马上就得到了一个结果。
- 2.用户进程判断结果是一个error时 ,它就知道数据还没有准备好,那么它可以再次发送read操作。
- 3.一旦kernel中的数据准备好了,并且又再次收到了用户进程的系统调用,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,nonblockingIO的特点是用户进程需要不断的主动询问kernel数据好了没有。
3.IO多路复用
IO多路复用就是我们说的select,poll,epoll有些地方也成这种IO方式为事件驱动IO,这种好处就在于单个process就可以同时处理多个网络连接的IO.它的基本原理就是select、poll、epoll这个function会不断的轮询所负责的所有socket,只要其中一个输数据到达,就通知用户线程。
- 1.当用户进程调用了select,那么整个用户进程就会被block ,而同时kernel会监视所有select负责的socket,当任何一个socket中的数据准备好,select就会返回,这个时候用户进程在调用read操作,将数据从kernel拷贝到用户进程。
- 2.这里需要使用两个系统调用(select和read),而blocking IO 只调用了一个系统调用(read)。但是用多路复用的优势在于它可以同时处理多个connection。
- 3.如果处理的连接说不是很高的话,使用IO多路复用不一定比多线程+阻塞IO的性能好,可能延迟还更大。IO多路复用的优势在于它可以处理更多的连接数,而不是对单个链接处理的更好。
所以,IO多路复用的特点是通过一种机制一个进程能同时等待多个文件秒数据,而这些文件描述符(socket描述符),其中的任意一个进入就绪状态,select()函数就可以返回。
4.异步IO
1.用户发起read操作之后,立刻就开始做其他事情。另一方面从kernel的角度,当它收到一个asynchronous read之后,首先会立刻返回,所以不会对用户进程产生任何block。
2.kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它操作已经完成了。
select、poll、epoll区别
select:IO多路复用 ---它仅仅知道有IO时间发生,却不知道是哪个流,只能无差别轮询所有流找出能读写数据的流,对他们进行操作 时间复杂度为O(n).
poll:IO多路复用 --- 本质上和select无区别,但是它没有最大连接数限制,原因是用链表来存储 时间复杂度为O(n)
epoll:IO多路复用 --- event poll,不同于轮询,epoll会把哪个流发生了怎样的IO事件通知我们。所以我们说epoll实际上是基于事件驱动的(每个事件关联上资源描述符fd),此时我们对这些流的操作都是有意义的。时间复杂度O(1)
总结:
最大连接数:
select:单个进程所能打开的最大连接数有限,当然可以进行修改,然后重新编译内核。但是性能可能会收到影响。
poll:本质上和select没有区别,但是没有最大连接数限制。原因是用链表来存储。
epoll:虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右连接,2G内存的机器可以20万。
FD剧增后带来的IO效率问题
select:因为每次调用都会进行线性遍历,所以随着FD增加会造成遍历速度线性下降。
poll:同select
epoll:因为使用事件触发回调机制,只有活跃的socket才会主动调用callback,所以没有线性下降的问题,但是如果所有socket都很活跃,会比select和poll效率低。
消息传递方式
select:内核需要将消息传递到用户空间,需要内核拷贝动作
poll:内核需要将消息拷贝到用户空间,需要内核拷贝动作
epoll:通过内核和用户空间共享一块内存来实现(直接内存)不需要拷贝动作。
1.表面上看epoll的性能最好,但是在连接数少并且链接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多回调函数。
2.select低效是因为每次都需要轮询,但低效也是相对的,视情况而定。可以通过良好的设计改善。