前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >IO模型梳理-从操作系统到应用层

IO模型梳理-从操作系统到应用层

作者头像
春哥大魔王
发布2019-08-06 10:33:24
1.1K0
发布2019-08-06 10:33:24
举报

写在前面

IO模型是编程语言和软件开发中重要的知识。本篇从IO模型这个切入点横向梳理了从操作系统到应用层IO模型相关知识。考虑到技术本身具有横向迁移的特点,也可以帮助大家在宏观与微观,具体与细节,底层与应用多角度串联技术,本篇是第一篇从IO模型说起。

Linux IO模型

操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也具有访问底层硬件设备的所有权限。

为了保证用户进程不能直接操作内核,保证内核安全,操作系统将虚拟空间划分为两部分:

  • 一部分为内核空间
  • 一部分为用户空间

获得CPU的进程可以将自己转换为阻塞状态,进程进入阻塞状态,是不占用CPU资源的。

为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进行执行,这个过程称为继承切换。

进程切换的过程:

    1. 保存处理机上下文,包括程序计数器和其他寄存器。
    1. 更新PCB信息。
    1. 把进程的PCB移入相应队列,如就绪,在某个事件阻塞等队列。
    1. 选择另一个进程执行,并更新其PCB。
    1. 更新内存管理的数据结构。
    1. 恢复处理机上下文。

文件描述符用于表述指向文件的引用的抽象化概念,指向内核为每一个进程所维护的该进程打开文件的记录表。

当程序打开一个现有文件或创建一个新文件时,内核向进程返回文件描述符,在程序设计中,一些涉及底层的程序编写往往围绕文件描述符展开。

在linux的缓存io机制中,操作系统将io的数据缓存在文件系统的页缓存中,就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

网络IO的本质是socket读取,socket在linux系统被抽象为流,io可以理解为对流的操作。

对于一次io访问,数据会先被拷贝到操作系统内核缓冲区,然后从操作系统内核缓冲区拷贝到应用程序地址空间。

Linux系统IO分为内核准备数据和将数据从内核拷贝到用户空间两个阶段。

用户空间(进程)->内核空间->调用磁盘控制器->写入磁盘

应用程序不能直接和硬件互操作,必须借助于操作系统,网络IO的本质是socket的读取,socket在linux系统被抽象成流,IO可以理解为对流的操作。

read操作:

  • 等待数据准备;
  • 将数据从内核拷贝到操作系统内核缓冲区;
  • 从操作系统内核缓冲区拷贝到应用程序地址空间中;

socket操作:

  • 等待网络上数据分组到达,复制到内核到某个缓冲区;
  • 把数据从内核缓冲区复制到进程缓冲区;

网络IO模型:

  • 同步阻塞IO;
  • 同步非阻塞IO;
  • 同步多路复用IO;
  • 信号驱动IO;
  • 异步IO;
  • 同步和异步描述的是用户线程与内核交互方式,前四种都是同步,只有最后一种是异步。
  • 同步需要用户线程发起IO请求,主动等待或轮询获取消息通知。
  • 异步是用户线程发起IO请求后,仍继续执行,当内核IO操作完成后,用户线程被动接受消息通知,通过回调,通知,状态等方式被动获取消息。
  • 多路复用是在阻塞到select阶段,用户进程是主动等待并调用select函数来获取就绪到状态消息,并且进程状态为阻塞,所以多路复用是同步阻塞模式。

同步阻塞IO

linux中默认所有socket都是blocking。阻塞就是进程被“休息”,cpu处理其他进程去了。

用户空间的应用程序执行一个系统调用,会导致应用程序阻塞,什么也不干,直到数据准备好,并且将数据从内核复制到用户进程,最后进程再处理数据,等待数据到处理数据两个阶段,整个进程被阻塞,不能处理别的网络IO。

阻塞期间不会存在cpu计算。

同步非阻塞IO

同步非阻塞,就是“每隔一会瞄一眼进度”的轮询方式。

这种模型中,设备是以非阻塞形式打开的,意味着IO操作不会立即完成,read操作可能会返回一个错误代码,说明这个命令不能立即满足。

非阻塞将大的整片时间的阻塞分割成N个小的阻塞,所以进程不断有机会被CPU光顾。

  • 进程调用内核时,内核会立马返回给进程,如果数据还没准备好,此时返回一个error。
  • 进程返回后,可以干点别的事情,然后在发起内核系统调用,重复上面流程,称为轮询。
  • 轮询检查内核数据,直到数据准备好,在拷贝数据到进程,进行数据处理,到了拷贝数据的过程时进程仍然是属于阻塞状态的。

多路复用IO

由于同步非阻塞方式需要轮询不断主动轮询,轮询占据很大一部分过程,轮询会消耗大量CPU时间,所以可以轮询多个任务的完成状态,只要有其中一个任务完成,就去处理它。

如果这个轮询工作不是进程自己执行就好了,所以就有了IO多路复用。

Linux下的select,poll,epoll就是干这个的。

IO多路复用有两个特别的系统调用select,poll,epoll函数。

  • select调用是内核级别的,select轮询和非阻塞轮询的区别是可以等待多个socket,能同时实现对多个io端口进行监听,当其中一个socket数据准备好,就能返回进行可读,然后进行系统调用,将数据由内核拷贝到进程,这个过程仍是阻塞的。
  • select或poll调用之后会阻塞进程,有一部分数据进来就调用用户进程进程处理。
  • 内核负责数据监视。

多路复用会同时阻塞多个IO操作,可以同时对多个读操作,多个写操作的IO进行检测,直到有数据可读或可写,才真正调用IO操作函数。

select

select和poll本质没有区别,将用户传入的数据拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪在设备等待队列中加入一项继续遍历,整个过程会有很多次遍历。它没有最大连接数限制,原因是基于链表存储的,大量的fd数组被整体拷贝到用户态和内核态之间,不管复制是否有意义。

epoll

epoll会用一个文件描述符管理多个描述符,将用户关系文件描述符事件存放到内核一个事件表中,这样在用户空间和内核空间copy只需要一次。epoll没有最大并发连接限制,能打开fd的上限远大于1024,只有活跃的fd才会调用callback,只管理活着的连接。

信号驱动IO

应用程序执行read请求,调用system call,然后内核开始处理响应到IO操作,程序并不等待内核响应就开始处理其他操作,内核执行完毕,返回read响应,同时产生信号或者执行一个基于线程到回调函数完成这次IO处理。

异步非阻塞IO

异步IO不是顺序执行的,用户进程进行系统调用后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情,等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知,两个IO阶段,进程都是非阻塞的。

异步IO并不十分常见,不少高性能并发服务程序,使用IO多复路模型+多线程任务处理的架构,基本可以满足需求,考虑到当前操作系统对于异步IO支持并不完善,更多的采用IO多复路模型。

了解完了操作系统层面的IO模型,在应用层讨论IO模型往往是在Nginx,Netty这种角度去讨论了,讨论最多的也是多路复用这种模式,先从Reactor模型说起。

Reactor模型

Apache服务器首先会创建多个进程,每个进程里面再创建多个线程,主要考虑稳定性,即使某个子进程里面的某个线程异常导致整个子进程退出,还会有其他子进程继续服务,不会导致整个服务器挂掉。

为了提升IO能力,可以采用单线程多连接模式,只有当连接有事件时才去处理,这就是IO多路复用的实现来源。

  • 当多条连接阻塞在一个对象上时,线程无需循环所有连接,而是关新这个阻塞对象的事件就可以,比如select,epoll,kqueue等。
  • 当某条连接有新数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理。

IO多路复用结合线程池,就是Reactor模型。Reactor包括监听和分配事件,资源处理交给线程池。C语言使用线程和进程都可以,Java的Netty则是线程,Nginx使用进程。select,accept,read,send都是标准的网络编程API。

单Reactor单进程

单Reactor优点是简单,没有进程间通信,没有进程竞争,全部在一个进程内完成,缺点是无法利用多核CPU性能,只适合处理业务非常快的场景,比如redis就是单Reactor。

单Reactor多线程

主线程通过select监控连接事件,收到事件后进行事件分发。

单Reactor多线程可以充分利用多核多CPU处理能力,但是多线程中子线程处理完毕后需要将结果返回主线程,涉及到数据共享和保护机制,Java的Nio中,selector是线程安全的,但selector.selectKeys()返回但键的集合是非线程安全的,对selected keys的处理必须采用单线程或同步措施进行保护。

多Reactor多线程

Nginx采用的是多Reactor多进程,多Reactor多线程实现有Memcache,Netty。

Nginx IO模型

nginx由一个master进程和多个worker进程组成,master负责管理worker进程。推荐worker数量和cpu数量相等。

包含:接收外界信号,向各个worker发送信号,监控worker进程运行状态,worker进程异常退出,会自动重新启动worker进程。基本的网络事件,在worker进程中处理。

每个worker进程都是相互独立的,不需要加锁,互相之间不受影响,一个进程异常退出,其他进程还在工作,服务不会中断。

nginx采用多路复用IO模型,支持epoll,poll,select。

  • select,poll:主动查询,可以同时查多个文件句柄的状态,select有文件句柄限制,poll没有限制。select创建的是读,写,异常三个集合,poll在一个集合内设定三种描述,poll的事件更少,性能上好一些。
  • epoll:基于回调函数的,无轮询。如果套接字比较多的时候,每次select都需要便利所有的文件描述符,会浪费好多cpu,所以epoll为每个套接字注册来回调函数,当某个套接字活跃时,自动完成相关操作,避免来轮询。

nginx和apache的区别:

  • nginx是基于事件模型,适合于IO密集型任务,比如反向代理。
  • apache是基于多进程/多线程模式的,适合于运行长时间计算任务的任务。

所以异步IO和避免线程切换,也是nginx性能很好的原因。

nignx如何做到几十万并发连接,答案是epoll机制,如果100w用户与一个进程保持tcp连接,虽然连接数巨大,但是某个时刻只有一小部分连接是活跃的,所以进程只要处理好这100w连接中的小部分足矣。

如果把100w中活跃的连接交给操作系统,会存在大量的用户态到内核态到拷贝,查找过程也造成巨大到资源浪费,缺点明显。

nginx会在内存中申请一颗红黑树,用于存放所有事件。同时申请双向链表,用于存放活跃事件,所有红黑树中事件都会与网卡驱动建立回调关系,当网卡有事件发生时候,回调函数将事件放入双向链表。所有发生事件的链表复制到内存中。采用红黑树有利于事件到查找和删除。

IO优化

了解了操作系统和应用层层面的IO模型和原因,针对于IO密集型程序存在哪些优化原则呢?

  • 增加缓存,减少磁盘的访问次数。
  • 优化磁盘的管理系统,设计最优的磁盘访问策略,及磁盘寻址策略,是底层操作系统层面考虑。
  • 设置合理的磁盘数据库访问策略,比如给存放数据设计索引,通过寻址索引来加快寻址,减少磁盘的访问量,可以采用异步和非阻塞方式加快磁盘访问速度。
  • 合理的RAID策略提升磁盘IO。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 春哥talk 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Linux IO模型
  • 同步阻塞IO
  • 同步非阻塞IO
  • 多路复用IO
  • 信号驱动IO
  • 异步非阻塞IO
  • Reactor模型
    • 单Reactor单进程
      • 单Reactor多线程
        • 多Reactor多线程
        • Nginx IO模型
        • IO优化
        相关产品与服务
        轻量应用服务器
        轻量应用服务器(TencentCloud Lighthouse)是新一代开箱即用、面向轻量应用场景的云服务器产品,助力中小企业和开发者便捷高效的在云端构建网站、Web应用、小程序/小游戏、游戏服、电商应用、云盘/图床和开发测试环境,相比普通云服务器更加简单易用且更贴近应用,以套餐形式整体售卖云资源并提供高带宽流量包,将热门开源软件打包实现一键构建应用,提供极简上云体验。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档