Linux IO 概念(2)

 

        在上一篇IO底层的概念中杂合了不少模糊的概念,受知识水平的限制,只是从网上抄了不少过来.从linux一切皆文件的设计哲学,介绍了文件描述符,从进程的运行内存分配,进程的切换,介绍了进程的阻塞,以及引出了阻塞IO.linux

        在讲到阻塞IO的时,候受限于知识水平,也没有实际操做过,仍是没有理解进程和IO函数的调用关系,IO又是如何操做磁盘,文件描述符又是怎样工做,进程怎么去拷贝字节流,数组

        了解linuxIO的最终目的我是想知道JavaIO和JavaNIO在虚拟机中是如何调用的,虚拟机做为一个linux进程又是如何跟底层IO进行交互的.这些问题最终仍是要去图书馆查阅书籍才能理解的更清楚,缓存

        下面继续在网络上搬迁别人家的博客网络

        注:如下下文章整理自网络多线程

        

    阻塞IO并发

 

 

 

非阻塞IO异步

 

 

        

 

        多路复用IO,socket

        多路复用IO是为了处理多个IO问价句柄的数据操做,一个典型场景是当有不少socket服务监听不一样端口以接收数据时,若是采用阻塞IO则须要多线程,每一个线程和进程负责一个端口socket.可是,大量的线程和进程每每形成CPU的浪费函数

        linuxIO多路复用技术提供一个单进程,单线程内监听多个IO读写时间的机制,其基本原理是各个IO将句柄设置为非阻塞IO,而后将各个IO句柄注册到linux提供的IO复用函数上(select,poll或者epoll),若是某个句柄的IO数据就绪,则函数返回,因为开发者进行该IO数据处理.多路复用函数帮咱们进行了多个非阻塞IO数据是否就绪的轮询操做,只不过IO多路复用函数的轮询更有效率,由于函数一次性传递文件描述符到内核态,在内核态中进行轮询(epoll则是进行等待边缘事件的触发),没必要反复进行用户态和内核态的切换性能

 

 

        

 

linuxIO的多路复用技术主要的实现方式,select,poll,和epoll,过根据触发方式不一样,与是否须要轮询的的不一样

    

SELECT

        select是Linux最先支持的多路IO复用函数,其函数原型为:

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

        参数nfds是全部文件描述符的数量+1,而readfds、writefds和errorfds分别为等待读、写和错误IO操做的文件描述符的集合,而timeout是超时时间,超过timeout时间select将返回(0表示不阻塞,NULL则是没有超时时间)。

        select的返回值是有可用的IO操做的文件描述符数量,若是超时返回0,若是发生错误返回-1。

        select函数须要和四个宏配合使用:FD_SET()、FD_CLR()、FD_ZERO()和FD_ISSET()。具体使用再也不介绍,能够参考资料[7,8]的相关内容,下面介绍select函数的内部实现原理和主要流程:

一、使用copy_from_user从用户空间拷贝fd_set到内核空间;

二、遍历全部fd,调用其对应的poll函数,再由poll函数调用__pollwait函数;

三、poll函数会判断当前文件描述符上的IO操做是否就绪,并利用__pollwait的主要工做就是把当前进程挂到设备的等待队列中,但这并不表明进程会睡眠;

四、poll方法返回时会返回一个描述读写操做是否就绪的mask掩码,根据这个mask掩码给fd_set赋值;

五、若是遍历完全部的fd,尚未返回一个可读写的mask掩码,则会调用schedule_timeout使进程进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程,更新fd_set后select返回;

六、若是超过超时时间schedule_timeout,仍是没人唤醒,则调用select的进程会从新被唤醒得到CPU,进而从新遍历fd,判断有没有就绪的fd,流程如上;

七、把fd_set从内核空间拷贝到用户空间,select返回。

       

         从上面的select内部流程中能够看出,select操做既有阻塞等待,也有主动轮询,相比于纯粹的轮询操做,效率应该稍微高一些。可是其缺点仍然十分明显:

一、每次调用select,都须要把fd集合从用户态拷贝到内核态返回时还要从内核态拷贝到用户态,这个开销在fd不少时会很大;

二、每次调用select都须要在内核遍历传递进来的全部fd,这个开销在fd不少时也很大;

三、select返回后,用户不得不本身再遍历一遍fd集合,以找到哪些fd的IO操做可用;

四、再次调用select时,fd数组须要从新被初始化;

五、select支持的文件描述符数量过小了,默认是1024。

 

 

POLL

 

 

        poll的函数原型为int poll(struct pollfd *fds, nfds_t nfds, int timeout)。其实现和select很是类似,只是描述fd集合的方式不一样,poll经过一个pollfd数组向内核传递须要关注的事件,故没有描述符个数的限制。

        pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只须要被初始化一次。

        poll的实现机制与select相似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,而后对pollfd中的每一个描述符进行poll。

        poll返回后,一样须要对pollfd中的每一个元素检查其revents值,来得指事件是否发生。

        因而可知,poll除了没有文件描述个数限制和文件描述符数组只需初始化一次之外,select的其余缺点扔存在,而存在的缺点是select和poll性能低的主要缘由。

 

 

EPOLL(等下一个IO可读取才返回)

 

        Epoll是Linux 2.6版本以后才引入的一种新的多路IO复用技术,epoll解决了select技术的全部主要缺点,能够取代select方式成为推荐的多路IO复用技术。

        epoll经过epoll_create建立一个用于epoll轮询的描述符,经过epoll_ctl添加/修改/删除事件,经过epoll_wait等待IO就绪或者IO状态变化的事件发生,epoll_wait的第二个参数用于存放结果。

        epoll与select、poll不一样,首先,其不用每次调用都向内核拷贝事件描述信息,在第一次调用后,事件信息就会与对应的epoll描述符关联起来。另外epoll不是经过轮询,而是经过在等待的描述符上注册回调函数,当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间。

        epoll返回后,该参数指向的缓冲区中即为发生的事件,对缓冲区中每一个元素进行处理便可,而不须要像poll、select那样进行轮询检查。

        之因此epoll可以避免效率低下的主动轮询,而彻底采用效率更高的被动等待IO事件通知,是由于epoll在返回时机上支持被成为“边沿触发”(edge=triggered)的新思想,与此相对,select的触发时机被成为“水平触发”(level-triggered)。epoll同时支持这两种触发方式。

        边沿触发是指当有新的IO事件发生时,epoll才唤醒进程以后返回;而水平触发是指只要当前IO知足就绪态的要求,epoll或select就会检查到而后返回,即便在调用以后没有任何新的IO事件发生。

        举例来讲,一个管道内收到了数据,注册该管道描述符的epoll返回,可是用户只读取了一部分数据,而后再次调用了epoll。这时,若是是水平触发方式,epoll将马上返回,由于当前有数据可读,知足IO就绪的要求;可是若是是边沿触发方式,epoll不会返回,由于调用以后尚未新的IO事件发生,直到有新的数据到来,epoll才会返回,用户能够一并读到老的数据和新的数据。

        经过边沿触发方式,epoll能够注册回调函数,等待指望的IO事件发生,系统内核会在事件发生时通知,而没必要像水平触发那样去主动轮询检查状态。边沿触发和水平触发方式相似于电子信号中的电位高低变化,由此得名。

 

信号驱动IO

 

 

        信号驱动的IO是一种半异步的IO模型。使用信号驱动I/O时,当网络套接字可读后,内核经过发送SIGIO信号通知应用进程,因而应用能够开始读取数据。

        具体的说,程序首先容许套接字使用信号驱动I/O模式,而且经过sigaction系统调用注册一个SIGIO信号处理程序。当有数据到达后,系统向应用进程交付一个SIGIO信号,而后应用程序调用read函数从内核中读取数据到用户态的数据缓存中。这样应用进程都不会由于尚无数据达到而被阻塞,应用主循环逻辑能够继续执行其余功能,直到收到通知后去读取数据或者处理已经在信号处理程序中读取完毕的数据。

        设置套接字容许信号驱动IO的步骤以下:

1.注册SIGIO信号处理程序。(安装信号处理器)

2.使用fcntl的F_SETOWN命令,设置套接字全部者。(设置套接字的全部者)

3.使用fcntl的F_SETFL命令,置O_ASYNC标志,容许套接字信号驱动I/O。(容许这个套接字进行信号输入输出)

        信号驱动的IO内部时序流程以下所示:



 

 

 

之因此说信号驱动的IO是半异步的,是由于实际读取数据到应用进程缓存的工做仍然是由应用本身负责的,而这部分工做执行期间进程依然是阻塞的,如上图中的后半部分。而在下面介绍的异步IO则是彻底的异步

 

异步IO

 

        异步I/O模型是一种处理与I/O重叠进行的模型。读请求会当即返回,说明read 请求已经成功发起了。在后台完成读操做时,应用程序而后会执行其余处理操做。当read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成此次I/O 处理过程。

        在一个进程中为了执行多个I/O请求而对计算操做和I/O 处理进行重叠处理的能力利用了处理速度与I/O速度之间的差别。当一个或多个I/O 请求挂起时,CPU能够执行其余任务;或者更为常见的是,在发起其余I/O的同时对已经完成的I/O 进行操做。

        在传统的I/O模型中,有一个使用唯一句柄标识的I/O 通道。在 UNIX® 中,这些句柄是文件描述符(这等同于文件、管道、套接字等等)。在阻塞I/O中,咱们发起了一次传输操做,当传输操做完成或发生错误时,系统调用就会返回。

        在异步非阻塞I/O中,咱们能够同时发起多个传输操做。这须要每一个传输操做都有唯一的上下文,这样咱们才能在它们完成时区分究竟是哪一个传输操做完成了。在AIO中,这是一个aiocb(AIO I/O Control Block)结构。这个结构包含了有关传输的全部信息,包括为数据准备的用户缓冲区。在产生I/O(称为完成)通知时,aiocb结构就被用来唯一标识所完成的I/O操做。

        以read操做为例,一个异步IO操做的时序流程以下图所示:

 

 

        从上图中能够看出,比起信号驱动的IO那种半异步模式,异步IO中从内核拷贝数据到用户缓存空间的工做也是有系统完成的异步过程,用户程序只须要在指定的数组中引用数据便可。

        数据接收后的处理程序是一个回调函数,Linux提供了两种机制实现异步IO的回调函数:

        一种是信号回调函数机制,这种机制跟信号驱动的IO相似,利用信号触发回调函数的执行以处理接收的数据,这回中断正在执行的代码,而不会产生新的进程和线程;

        另外一种是线程回调函数机制,在这种机制下也须要编写相同的回调函数,可是这个函数将注册到异步IO的事件回调结构体对象中,当数据接收完成后将建立新的线程,在新的线程中调用回调函数进行数据处理。

 

各个IO模型的比较和应用场景

 

        为了比较各个IO模型的性能,这里设计了三种最主要的应用场景,分别是单个用户链接的频繁IO操做、少许用户链接的并发频繁IO操做、大量用户链接的并发频繁IO操做。在进行性能比较时,主要考虑的是总的IO等待、系统调用状况和CPU调度切换,IO等待越少、系统调用越少、CPU调度切换越少意味着IO操做的高效率。

 

 

        在单个用户链接频繁的IO操做中,能够采用单线程单进程的方式,这样能够不用考虑进程内部的CPU调度,只需关注IO等待和系统调用的频率。从上面各个IO模型的流程时序图来看,AIO的用户程序在执行Io操做时没有任何Io等待,并且只须要调用IO操做时一次系统调用,因为是异步操做,信号操做的回传不须要进行系统调用,连由内核返回用户态的系统调用都省了,所以效率最高。

        在信号驱动的IO模型中,IO等待时间要比基本的阻塞式IO和多路复用IO要少,只须要等待数据从内核到用户缓存的操做。可是信号驱动的IO模型和多路复用IO的系统调用次数同样,须要两次系统调用,共四次上下文切换,而基本的阻塞模式只须要一次系统调用。在IO频繁的场景下,仍是基本阻塞IO效率最高,其次为信号驱动IO,而后是多路复用IO。

 

        基本非阻塞IO的性能最差,由于在IO等待期间不只不交出CPU控制权,还一遍又一遍进行昂贵的系统调用操做进行主动轮询,而主动轮询对于IO操做和业务操做都没有实际的意义,所以CPU计算资源浪费最严重。

        

        在单个用户链接的频繁IO操做中,性能排名有好到差为:AIO>基本阻塞IO>信号IO>epoll>poll>select>基本非阻塞IO。

 

        在少许用户下的频繁IO操做中,基本阻塞IO通常要使用多线程操做,所以要产生额外的线程调度工做。虽然因为线程较少,远少于系统的总进程数,可是因为IO操做频繁,CPU切换仍是会集中在IO操做的各个线程内。

        对于基本阻塞IO和多路复用IO来说,虽然多路IO复用一次系统调用能够完成更多的IO操做,可是在IO操做完成后对于每一个IO操做仍是要系统调用将内核中的数据取回到用户缓存中,所以系统调用次数仍然比阻塞IO略多,但线程切换的开销更大。特别对于select来讲,因为select内部采用半轮询方式,效率不如阻塞方式,所以在这种少许用户链接的IO场景下,还不能只经过理论判断基本阻塞IO和select方式孰优孰劣。

        其余的IO模型相似于单用户下,再也不分析,由此得出在少许用户链接IO操做下的IO模型性能,由好到坏依次为AIO>信号IO>epoll>基本阻塞IO?poll>select>基本非阻塞IO。

 

        在大量,甚至海量用户的并发频繁IO操做下,多路IO复用技术的性能会全面超越简单的多线程阻塞IO,由于这时大量的CPU切换操做将显著减小CPU效率,而多路复用一次完成大量的IO操做的优点更加明显。对于AIO和信号IO,在这种场景下依然有着更少的IO等待和更少的系统调用操做,性能依然最好。

        因而可知,在大量用户的并发频繁IO操做下,IO性能由好到差依次为AIO>信号IO>epoll>poll>select>基本阻塞IO>基本非阻塞IO。

    

https://mp.weixin.qq.com/s?__biz=MzI4NTEzMjc5Mw==&mid=2650554708&idx=1&sn=4fa4e599c5028825fda5ead907ec86a6&chksm=f3f833c2c48fbad49fda347833f14f553f764fc0e46ae71073d0b31028f7ec4f85b60d448e9a#rd

相关文章
相关标签/搜索