操作系统
什么是操作系统(先不看)
- 操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机的基石。
- 操作系统本质上是一个运行在计算机上的软件程序 ,用于管理计算机硬件和软件资源。 举例:运行在你电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。
- 操作系统存在屏蔽了硬件层的复杂性。 操作系统就像是硬件使用的负责人,统筹着各种相关事项。
- 操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。
进程和线程协程区别
线程与进程的⽐较如下:
- 进程是资源(包括内存、打开的⽂件等)分配的单位,线程是 CPU 调度的单位;
- 进程拥有⼀个完整的资源平台,⽽线程只独享必不可少的资源,如寄存器和栈;线程同样具有就绪、阻塞、执⾏三种基本状态,同样具有状态之间的转换关系;
- 线程能减少并发执⾏的时间和空间开销;
对于,线程相⽐进程能减少开销,体现在:
- 线程的创建时间⽐进程快,因为进程在创建的过程中,还需要资源管理信息,⽐如内存管理信息、⽂件管理信息,⽽线程在创建的过程中,不会涉及这些资源管理信息,⽽是共享它们;
- 线程的终⽌时间⽐进程快,因为线程释放的资源相⽐进程少很多;
- 同⼀个进程内的线程切换⽐进程切换快,因为线程具有相同的地址空间(虚拟内存共享),这意味着同⼀个进程的线程都具有同⼀个⻚表,那么在切换的时候不需要切换⻚表。⽽对于进程之间的切换,切换的时候要把⻚表给切换掉,⽽⻚表的切换过程开销是⽐较⼤的;
- 由于同⼀进程的各线程间共享内存和⽂件资源,那么在线程之间数据传递的时候,就不需要经过内核了,这就使得线程之间的数据交互效率更⾼了;所以,不管是时间效率,还是空间效率线程⽐进程都要⾼。
对于协程 :
协程,是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。
协程有何优势?
-
极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;
-
不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
线程共享资源和独占资源问题(一定要看 找找其他帖子)
什么是进程间通信
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)
进程通信 很多种方式
按照顺序:管道-匿名管道-消息队列-共享内存-信号量-信号-socket
1.管道(匿名管道)
-
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。
-
只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
2.命名管道
- 匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO)。
- 通过有名管道不相关的进程也能交换数据。因为命令管道,提前创建了⼀个类型为管道的设备⽂件,在进程⾥只要使⽤这个设备⽂件,就可以相互通信。
对于管道:发送方发送信息后,管道里的内容只有被读取,命令才可以正常退出
3.消息队列:因为管道通信方式效率低,不适合频繁交互数据。。然而消息队列的通信模式就可以解决。⽐如, A 进程要给 B 进程发送消息, A 进程把数据放在对应的消息队列后就可以正常返回了, B 进程需要的时候再去读取数据就可以了。同理, B 进程要给 A 进程发送消息也是如此。
缺陷:通信不及时,附件大小有限制,不适合比较大数据的传输,而且拷贝开销比较大。 消息队列通信过程中,存在⽤户态与内核态之间的数据拷⻉开销,因为进程写⼊数据到内核中的消息队列时,会发⽣从⽤户态拷⻉数据到内核态的过程,同理另⼀进程读取内核中的消息数据时,会发⽣从内核态拷⻉数据到⽤户态的过程。
4.共享内存
消息队列的读取和写⼊的过程,都会有发⽣⽤户态与内核态之间的消息拷⻉过程。那共享内存的⽅式,就很好的解决了这⼀问题。
共享内存的机制,就是拿出⼀块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写⼊的东⻄,另外⼀个进程⻢上就能看到了,都不需要拷⻉来拷⻉去,传来传去,⼤⼤提⾼了进程间通信的速度。每个进程都可以直接访问,就像访问进程⾃⼰的空间⼀样快捷⽅便,不需要陷⼊内核态或者系统调⽤,⼤⼤提⾼了通信的速度, 这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
共享内存的缺点是存在并发问题,有可能出现多个进程修改同一块内存,因此共享内存一般与信号量结合使用。
5.信号量
共享内存产生了同步问题,需要用信号量。为了防⽌多进程竞争共享资源,⽽造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被⼀个进程访问。正好, 信号量就实现了这⼀保护机制。 信号量其实是⼀个整型的计数器,主要用于实现进程间的互斥与同步
信号量是一个计数器,信号量是一种特殊的变量,对它的操作都是原子的,有两种操作:V(signal()
)和 P(wait()
)。V 操作会增加信号量 S 的数值,P 操作会减少它。P操作是在进入共享资源之前,V操作是在离开共享资源只有,这两个操作必须是成对出现。
如果P操作后,信号量<0,说明资源已经被占用,V操作后,如果信号量<=0,表明当前有阻塞中的进程,需要吧该进程唤醒运行。
6.信号:上⾯说的进程间通信,都是常规状态下的⼯作模式。 对于异常情况下的⼯作模式,就需要⽤「信号」的⽅式来通知进程。 信号是进程间通信机制中唯⼀的异步通信机制,因为可以在任何时候发送信号给某⼀进程
7.套接字
前⾯提到的管道、消息队列、共享内存、信号量和信号都是在同⼀台主机上进⾏进程间通信,那要想跨⽹络与不同主机上的进程之间通信,就需要 Socket 通信了。 当然也可以在同主机上进程间通信。
进程间通信方式总结
由于每个进程的⽤户空间都是独⽴的,不能相互访问,这时就需要借助内核空间来实现进程间通信,原因很简单,每个进程都是共享⼀个内核空间。
Linux 内核提供了不少进程间通信的⽅式,其中最简单的⽅式就是管道,管道分为「匿名管道」和「命名管道」。
匿名管道顾名思义,它没有名字标识,匿名管道是特殊⽂件只存在于内存,没有存在于⽂件系统中, 通信的数据是无格式的流并且大小受限,通信的⽅式是单向的,数据只能在⼀个⽅向上流动,如果要双向通信,需要创建两个管道,再来匿名管道是只能⽤于存在⽗⼦关系的进程间通信,匿名管道的⽣命周期随着进程创建⽽建⽴,随着进程终⽌⽽消失。
命名管道突破了匿名管道只能在亲缘关系进程间的通信限制,因为使⽤命名管道的前提,需要在⽂件系统创建⼀个类型为 p 的设备⽂件,那么毫⽆关系的进程就可以通过这个设备⽂件进⾏通信。另外,不管是匿名管道还是命名管道,进程写⼊的数据都是缓存在内核中,另⼀个进程读取数据时候⾃然也是从内核中获取,同时通信数据都遵循先进先出原则,不⽀持 lseek 之类的⽂件定位操作。
消息队列克服了管道通信的数据是⽆格式的字节流的问题,消息队列实际上是保存在内核的「消息链表」,消息队列的消息体是可以⽤户⾃定义的数据类型,消息队列通信的速度不是最及时的,毕竟每次数据的写⼊和读取都需要经过⽤户态与内核态之间的拷⻉过程。
共享内存可以解决消息队列通信中⽤户态与内核态之间数据拷⻉过程带来的开销, 它直接分配⼀个共享空间,每个进程都可以直接访问,就像访问进程⾃⼰的空间⼀样快捷⽅便,不需要陷⼊内核态或者系统调⽤,⼤⼤提⾼了通信的速度,享有最快的进程间通信⽅式之名。但是便捷⾼效的共享内存通信, 带来新的问题,多进程竞争同个共享资源会造成数据的错乱。
那么,就需要信号量来保护共享资源,以确保任何时刻只能有⼀个进程访问共享资源,这种⽅式就是互斥访问。 信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是⼀个计数器,表示的是资源个数,其值可以通过两个原⼦操作来控制,分别是 P 操作和 V 操作。
信号是进程间通信机制中唯⼀的异步通信机制,因为可以在任何时候发送信号给某⼀进程,信号可以在应⽤进程和内核之间直接交互,⼀旦有信号发⽣, 进程有三种⽅式响应信号 1. 执⾏默认操作、 2. 捕捉信号、 3. 忽略信号。有两个信号是应⽤进程⽆法捕捉和忽略的,即 SIGKILL 和 SEGSTOP ,这是为了⽅便我们能在任何时候结束或停⽌某个进程。、
前⾯说到的通信机制,都是⼯作于同⼀台主机,如果要与不同主机的进程间通信,那么就需要 Socket 通信了。 Socket 实际上不仅⽤于不同的主机进程间通信,还可以⽤于本地主机进程间通信,可根据创建Socket 的类型不同,分为三种常⻅的通信⽅式,⼀个是基于 TCP 协议的通信⽅式,⼀个是基于 UDP 协议的通信⽅式,⼀个是本地进程间通信⽅式。
进程间同步与互斥的区别。
互斥: 指某一个资源同时只允许一个访问者对其进行访问, 具有唯一性和排他性。 但是互斥无法限制访问者对资源的访问顺序, 即访问是无序的。
同步: 是指在互斥的基础上(大多数情况下) , 通过其它机制实现访问者对资源的有序访问。
线程通信
全局变量,volatile,等待同步机制(wait, notify),threadlocal
进程/线程同步
临界区,互斥量,信号量,事件
并发、并行、异步区别
并发:在一个时间段中同时有多个程序在运行,但其实任一时刻,只有一个程序在CPU上运行,宏观上的并发是通过不断的切换实现的;
异步(和同步相比):同步是顺序执行,异步是在等待某个资源的时候继续做自己的事
多线程:并发运行的一段代码。是实现异步的手段
并行(和串行相比):在多CPU系统中,多个程序无论宏观还是微观上都是同时执行的
进程上下文切换
一个进程切换到另一个进程。
进程的上下⽂切换不仅包含了虚拟内存、栈、全局变量等⽤户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。
进程上下文切换有哪些场景
-
为了保证所有进程可以得到公平调度, CPU 时间被划分为⼀段的时间⽚,这些时间⽚再被轮流分配给各个进程。这样,当某个进程的时间⽚耗尽了,进程就从运⾏状态变为就绪状态,系统从就绪队列选择另外⼀个进程运⾏;
-
进程在系统资源不⾜(⽐如内存不⾜)时,要等到资源满⾜后才可以运⾏,这个时候进程也会被挂起,并由系统调度其他进程运⾏;
-
当进程通过睡眠函数 sleep 这样的⽅法将⾃⼰主动挂起时,⾃然也会重新调度;
-
当有优先级更⾼的进程运⾏时,为了保证⾼优先级进程的运⾏,当前进程会被挂起,由⾼优先级进程来运⾏;
-
发⽣硬件中断时, CPU 上的进程会被中断挂起,转⽽执⾏内核中的中断服务程序;
以上,就是发⽣进程上下⽂切换的常⻅场景了。
CPU上下文切换
CPU 寄存器和程序计数是 CPU 在运⾏任何任务前,所必须依赖的环境,这些环境就叫做 CPU上下⽂。
CPU 上下⽂切换就是先把前⼀个任务的 CPU 上下⽂(CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下⽂到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运⾏新任务。
逻辑地址VS物理地址VS虚拟地址
虚拟地址就是逻辑地址
-
所谓的逻辑地址,是指计算机用户(例如程序开发者),看到的地址。例如,当创建一个长度为100的整型数组时,操作系统返回一个逻辑上的连续空间:指针指向数组第一个元素的内存地址。由于整型元素的大小为4个字节,故第二个元素的地址时起始地址加4,以此类推。
-
事实上,逻辑地址并不一定是元素存储的真实地址,即数组元素的物理地址(在内存条中所处的位置),并非是连续的,只是操作系统通过地址映射,将逻辑地址映射成连续的,这样更符合人们的直观思维。
我们程序所使⽤的内存地址叫做虚拟内存地址(Virtual Memory Address)
实际存在硬件⾥⾯的空间地址叫物理内存地址(Physical Memory Address)。
操作系统引入了虚拟内存,进程持有的虚拟地址会通过 CPU 芯⽚中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存,
操作系统是如何管理虚拟地址与物理地址之间的关系?(先不看)
主要有两种⽅式,分别是内存分段和内存分⻚
存储器管理-分页存管理方式和分段存储管理方式 分页系统 分段系统 注意跟虚拟存储里面的分页请求系统和分段请求系统(先不看)
共同点 :
分页机制和分段机制都是为了提高内存利用率,较少内存碎片。
页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。
区别 :
页的大小是固定的,由操作系统决定;而段的大小不固定,取决于我们当前运行的程序。
分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要。
关于虚拟内存(先不看)
定义:
简单来说,虚拟存储器就是指,具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充的一种存储器系统。逻辑容量由内存容量和外存容量之和所决定,运行速度接近于内存速度,而成本又接近于外存。
三个主要特征:
- 多次性,是指无需在作业运行时一次性地全部装入内存,而是允许被分成多次调入内存运行。相对于传统的存储器管理方式的一次性而言
- 对换性,是指无需在作业运行时一直常驻内存,而是允许在作业的运行过程中,进行换进和换出。相对于传统存储器管理方式的一次性而言
- 虚拟性,是指从逻辑上扩充内存的容量,使用户所看到的内存容量,远大于实际的内存容量。
虚拟存储器的实现有以下两种方式:(先不看)
建立在离散分配存储管理方式的基础上
- 分页请求系统:分页系统上增加了请求调页功能和页面置换功能所形成的页式虚拟存储系统。允许用户程序装入少数页面程序就可以启动运行,之后通过调页功能和页面置换功能陆续的把将要运行的页面调入内存。、
- 分段请求系统。见上
页面置换算法
-
在程序运行过程中,如果要访问的页面不在内存中,就发生缺页中断从而将该页调入内存中。此时如果内存已无空闲空间,系统必须从内存中调出一个页面到磁盘中来腾出空间。页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低)。
-
最佳页面置换算法OPT(Optimal replacement algorithm):所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。
-
先进先出FIFO:选择换出的页面是最先进入的页面。该算法会将那些经常被访问的页面换出,导致缺页率升高。
-
第二次机会算法SCR:FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改:
当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。
-
时钟算法 Clock:第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面连接起来,再使用一个指针指向最老的页面。
-
最近最少使用算法LRU(Least Recently Used):需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。
因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。
-
进程调度算法
批处理操作系统:
-
先来先服务 first-come first-served(FCFS)
-
最短作业优先 shortest job first(SJF) 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。有利于提高系统吞吐量的,但是最长作业不利,会导致饥饿,
-
最高响应比优先 Highest Response Ratio Next(HRRN) 可用于作业和进程调度;只有当前运行的作业或进程主动放弃处理机时,才需要调度,才需要计算响应比=1+等待时间/要求服务时间。
交互式系统:
时间片轮转: 每个进程被分配⼀个时间段,称为时间⽚(Quantum),即允许该进程在该时间段中运⾏ ,如果时间⽚设得太短会导致过多的进程上下⽂切换,降低了 CPU 效率;
- 如果时间⽚⽤完,进程还在运⾏,那么将会把此进程从 CPU 释放出来,并把 CPU 分配另外⼀个进程;
- 如果该进程在时间⽚结束前阻塞或结束,则 CPU ⽴即进⾏切换;
另外,时间⽚的⻓度就是⼀个很关键的点:
- 如果时间⽚设得太短会导致过多的进程上下⽂切换,降低了 CPU 效率;
- 如果设得太⻓⼜可能引起对短作业进程的响应时间变⻓。
优先级调度:希望调度程序能从就绪队列中选择最⾼优先级的进程进⾏运⾏,这称为最⾼优先级(Highest Priority First, HPF)调度算法。
多级反馈队列调度算法
多级反馈队列(Multilevel Feedback Queue)调度算法是「时间⽚轮转算法」和「最⾼优先级算法」的综合和发展。
顾名思义:
「多级」表示有多个队列,每个队列优先级从⾼到低,同时优先级越⾼时间⽚越短。
「反馈」表示如果有新的进程加⼊优先级⾼的队列时,⽴刻停⽌当前正在运⾏的进程,转⽽去运⾏优先级⾼的队列
磁盘调度算法
磁盘调度算法的⽬的很简单,就是为了提⾼磁盘的访问性能,⼀般是通过优化磁盘的访问请求顺序来做到的。寻道的时间是磁盘访问最耗时的部分,如果请求顺序优化的得当,必然可以节省⼀些不必要的寻道时间,从⽽提⾼磁盘的访问性能。
先来先服务算法
最短寻道时间优先算法
扫描算法算法
循环扫描算法
LOOK 与 C-LOOK 算法
先来先服务算法
先来先服务(First-Come, First-Served, FCFS),顾名思义,先到来的请求,先被服务
如果请求访问的磁道可能会很分散,那先来先服务算法在性能上就会显得很差
最短寻道时间优先算法
最短寻道时间优先(Shortest Seek First, SSF)算法的⼯作⽅式是,优先选择从当前磁头位置所需寻道时间最短的请求。
这个算法可能存在某些请求的饥饿, 产⽣饥饿的原因是磁头在⼀⼩块区域来回移动。
扫描算法(SCAN)
最短寻道时间优先算法会产⽣饥饿的原因在于:磁头有可能再⼀个⼩区域内来回得移动。 为了防⽌这个问题,可以规定: 磁头在⼀个⽅向上移动,访问所有未完成的请求,直到磁头到达该⽅向上的最后的磁道,才调换⽅向,这就是扫描(Scan)算法 。也叫电梯算法。(即使系统没有访问边界,他也要走到边界才回头))
扫描调度算法性能较好,不会产⽣饥饿现象,但是存在这样的问题,中间部分的磁道会⽐较占便宜,中间部分相⽐其他部分响应的频率会⽐较多,也就是说每个磁道的响应频率存在差异
循环扫描算法(也要走到最边界)
可以总是按相同的⽅向进⾏扫描,使得每个磁道的响应频率基本⼀致。
循环扫描(Circular Scan, CSCAN )规定:只有磁头朝某个特定⽅向移动时,才处理磁道访问请求,⽽返回时直接快速移动⾄最靠边缘的磁道,也就是复位磁头,这个过程是很快的,并且返回中途不处理任何请求,该算法的特点,就是磁道只响应⼀个⽅向上的请求。
LOOK 与 C-LOOK 算法
说到的扫描算法和循环扫描算法,都是磁头移动到磁盘最始端或最末端才开始调换⽅向。 那这其实是可以优化的,优化的思路就是磁头在移动到「最远的请求」位置,然后⽴即反向移动。
LOOK-扫描算法
C-LOOK-循环扫描算法
那针对 SCAN 算法的优化则叫 LOOK 算法,它的⼯作⽅式,磁头在每个⽅向上仅仅移动到最远的请求位置,然后⽴即反向移动,⽽不需要移动到磁盘的最始端或最末端, 反向移动的途中会响应请求。
⽽针 C-SCAN 算法的优化则叫 C-LOOK,它的⼯作⽅式,磁头在每个⽅向上仅仅移动到最远的请求位置,然后⽴即反向移动,⽽不需要移动到磁盘的最始端或最末端, 反向移动的途中不会响应请求。
文件I/O
⽂件的读写⽅式各有千秋,对于⽂件的 I/O 分类也⾮常多,常⻅的有
- 缓冲与⾮缓冲 I/O
- 直接与⾮直接 I/O
- 阻塞与⾮阻塞 I/O VS 同步与异步 I/O
缓冲与⾮缓冲 I/O
⽂件操作的标准库是可以实现数据的缓存,那么根据「是否利⽤标准库缓冲,可以把⽂件 I/O 分为缓冲I/O 和⾮缓冲 I/O:
- 缓冲 I/O,利⽤的是标准库的缓存实现⽂件的加速访问,⽽标准库再通过系统调⽤访问⽂件。
- ⾮缓冲 I/O,直接通过系统调⽤访问⽂件,不经过标准库缓存。
这⾥所说的「缓冲」特指标准库内部实现的缓冲。
⽐⽅说,很多程序遇到换行时才真正输出,而换行前的内容,其实就是被标准库暂时缓存了起来,这样做的目的是,减少系统调用的次数,毕竟系统调用是有 CPU 上下⽂切换的开销的。
直接与⾮直接 I/O
我们都知道磁盘 I/O 是⾮常慢的,所以 Linux 内核为了减少磁盘 I/O 次数,在系统调用后,会把用户数据拷贝到内核中缓存起来,这个内核缓存空间也就是「⻚缓存」,只有当缓存满⾜某些条件的时候,才发起磁盘 I/O 的请求。
那么, 根据是「否利⽤操作系统的缓存」,可以把⽂件 I/O 分为直接 I/O 与非直接 I/O:
- 直接 I/O,不会发⽣内核缓存和⽤户程序之间数据复制,⽽是直接经过⽂件系统访问磁盘。
- 非直接 I/O,读操作时,数据从内核缓存中拷⻉给⽤户程序,写操作时,数据从⽤户程序拷⻉给内核缓存,再由内核决定什么时候写⼊数据到磁盘。
阻塞与⾮阻塞 I/O VS 同步与异步 I/O
记住两个过程:内核数据准备好和数据从内核态拷贝到用户态
阻塞I/O:当⽤户程序执⾏ read ,线程会被阻塞,⼀直等到内核数据准备好,并把数据从内核缓冲区拷⻉到应⽤程序的缓冲区中,当拷⻉过程完成, read 才会返回。
非阻塞I/O:⾮阻塞的 read 请求在数据未准备好的情况下⽴即返回,可以继续往下执⾏,此时应⽤程序不断轮询内核,直到数据准备好,内核将数据拷⻉到应⽤程序缓冲区, read 调⽤才可以获取到结果。最后一步,数据准备好后拷贝过程是同步的,也就是需要等待的过程
I/O多路复用:上面的方式,需要应用程序轮询内核I/O是否准备好,太傻了,所以就有了I/O多路复用技术,如 select、 poll,它是通过 I/O 事件分发,当内核数据准备好时,再以事件通知应⽤程序进⾏操作。
实际上,⽆论是阻塞 I/O、⾮阻塞 I/O,还是基于⾮阻塞 I/O 的多路复⽤都是同步调⽤。因为它们在 read调⽤时,内核将数据从内核空间拷⻉到应⽤程序空间,过程都是需要等待的,也就是说这个过程是同步的,如果内核实现的拷⻉效率不⾼, read 调⽤就会在这个同步过程中等待⽐较⻓的时间。
异步 I/O:⽽真正的异步 I/O 是「内核数据准备好」和「数据从内核态拷⻉到⽤户态」这两个过程都不⽤等待。
发起read以后,立即返回,内核会自动把数据从内核空间拷贝到应用程序空间,这个拷贝过程同样是异步的,内核自动完成,跟前面不同操作一样,应用程序不需要主动发起拷贝动作。
I/O控制方式
BIO,NIO,AIO 有什么区别?
那么同步阻塞、同步非阻塞和异步非阻塞又代表什么意思呢?
举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在哪里傻等着水开(同步阻塞)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(同步非阻塞)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(异步非阻塞)。
- BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。
- NIO (Non-blocking/New I/O): NIO 是一种同步非阻塞的 I/O 模型。
IO流式阻塞的,NIO是不阻塞的;
阻塞:当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取,这个线程在此期间不能干其他事情。
非阻塞:线程请求读取数据到通道当中,期间可以去做其他事情。
IO面向流,NIO面向缓冲区。,IO是吧数据读到或者写到steam对象中,NIO是把数据读到Buffer中进行操作。 - AIO (Asynchronous I/O,异步非阻塞): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞,基于事件和回调机制实现,应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。