文档章节

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

春哥大魔王的博客
 春哥大魔王的博客
发布于 08/04 16:30
字数 3726
阅读 2
收藏 0

写在前面

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。

© 著作权归作者所有

春哥大魔王的博客
粉丝 40
博文 306
码字总数 281324
作品 0
海淀
程序员
私信 提问
从操作系统内核看Java非阻塞IO事件检测

非阻塞服务器模型最重要的一个特点是,在调用读取或写入接口后立即返回,而不会进入阻塞状态。在探讨单线程非阻塞IO模型前必须要先了解非阻塞情况下Socket事件的检测机制,因为对于非阻塞模式...

wangyangzhizhou
2016/09/18
0
0
架构设计:系统间通信(4)——IO通信模型和JAVA实践 中篇

4、多路复用IO模型 在“上篇”文章中,我们已经提到了使用多线程解决高并发场景的问题所在,这篇文章我们开始 4-1、现实场景 我们试想一下这样的现实场景: 一个餐厅同时有100位客人到店,当...

旋转木马-千里马
2016/03/22
62
0
openssl框架闲谈--总论

接触openssl已经有一段时间了,我读过很多源码,感觉不错的也就那么几个,linux内核是其中之 一,openssl也是其中之一。openssl说白了不是什么功能性的东西,而是提供了一个支撑性的底层框架...

晨曦之光
2012/04/10
276
0
Linux IO模型与Java NIO

概述 看Java NIO一篇文章的时候又看到了“异步非阻塞”这个概念,一直处于似懂非懂的状态,想解释下到底什么是异步 什么是非阻塞,感觉抓不住重点。决定仔细研究一下。 本文试图研究以下问题...

yingtju
2018/06/29
0
0
服务器模型——从单线程阻塞到多线程非阻塞(中)

前言的前言 服务器模型涉及到线程模式和IO模式,搞清楚这些就能针对各种场景有的放矢。该系列分成三部分: 单线程/多线程阻塞I/O模型 单线程非阻塞I/O模型 多线程非阻塞I/O模型,Reactor及其...

2017/12/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

服务器性能监控之New Relic 入门教程

New Relic 是一个很强大的服务器性能监控工具,New Relic目前专注于SaaS和App性能管理业务,它支持支持agent和API传送数据,能够对部署在本地或在云中的web应用程序进行监控、故障修复、诊断...

xiaolyuh
23分钟前
4
0
SpringBoot 集成ElasticSearch

一、ElasticSearch介绍 ElasticSearch 是一个开源的搜索引擎,建立在一个全文搜索引擎库 Apache Lucene™ 基础之上。 Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库——无论是开源...

zw965
48分钟前
6
0
【JVM学习】2.Java虚拟机运行时数据区

来源: 公众号: 猿人谷 这里我们先说句题外话,相信大家在面试中经常被问到介绍Java内存模型,我在面试别人时也会经常问这个问题。但是,往往都会令我比较尴尬,我还话音未落,面试者就会“...

物种起源-达尔文
56分钟前
4
0
dart datetime

var date = DateTime.now().toUtc(); //格式化输出 String timestamp = "${date.year.toString()}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, ......

zdglf
今天
21
0
如何在Linux中复制文档

在办公室里复印文档过去需要专门的员工与机器。如今,复制是电脑用户无需多加思考的任务。在电脑里复制数据是如此微不足道的事,以致于你还没有意识到复制就发生了,例如当拖动文档到外部硬盘...

老孟的Linux私房菜
今天
50
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部