什么是操作系统

  1. 操作系统是管理计算机硬件与软件资源的程序,是计算机的基石
  2. 操作系统本质上是一个运行在计算机上的软件程序,用于管理计算机硬件和软件资源
  3. 操作系统存在屏蔽了硬件层的复杂性。操作系统就像是硬件使用的负责人,统筹着各种相关事项。
  4. 操作系统的内核「Kernal」是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。

介绍一下系统调用

根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:

  • 用户态:用户态运行的进程可以直接读取用户程序的数据
  • 系统态:几乎可以不受限制地访问计算机的任何资源

我们运行的程序基本都是运行在用户态,如果调用系统态级别的功能就需要系统调用。也就是说,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。

按功能可大致分为如下几类:

  • 设备管理。完成设备的请求或释放,以及设备启动等功能。
  • 文件管理。完成文件的读、写、创建及删除等功能。
  • 进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
  • 进程通信。完成进程之间的消息传递或信号传递等功能。
  • 内存管理。完成内存的分配、回收以及获取作业占用内存区大小及地址等功能

进程和线程的区别

从JVM的角度:一个进程中可以有多个线程,多个线程共享进程的方法区(1.8之后的元空间)资源,但是每个线程拥有自己的程序计数器、虚拟机栈、本地方法栈

总结:线程是进程划分成更小的执行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程则相反。

进程有哪几种状态

我们一般把进程大致分为5种状态。

  • 创建状态「new」:进程正在被创建,尚未到就绪状态。
  • 就绪状态「ready」:进程已处于准备运行状态,即进程获得了除了处理器之外一切的所需资源,一旦得到处理器资源「处理器分配的时间片」即可运行
  • 运行状态「running」:进程正在处理器上运行
  • 阻塞状态「waiting」:又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
  • 结束状态「terminated」:进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。

进程间的通信方式

大概有7种常见的进程间的通信方式。

  1. 管道/匿名管道「Pipes」:用于具有亲缘关系的父子进程间或者兄弟进程之间的通信
  2. 有名管道「Names Pipes」:有名管道严格遵循先进先出,有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信
  3. 信号「Signal」:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生
  4. 消息队列「Message Queueing」:消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。比 FIFO 更有优势。 消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺点。
  5. 信号量「Semaphores」:信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
  6. 共享内存「Shared memory」:使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
  7. 套接字「Sockets」:此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

线程间的同步的方式

线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。操作系统一般由下面三种线程同步的方式:

  1. 互斥量「Mutex」:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
  2. 信号量「Semaphore」:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量
  3. 事件「Event」:Wait/Notify,通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作

进程的调度算法

  1. 先到先服务调度算法「FCFS」:从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度
  2. 短作业优先的调度算法「SJF」:从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  3. 时间片轮转调度算法「RR」:每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间
  4. 多级反馈队列调度算法:前面介绍的几种进程调度的算法都有一定的局限性。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
  5. 优先级调度:为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。

什么是死锁

死锁描述的是这样一种情况:多个进程/线程同时被阻塞,它们中的 一个或者全部都在等待某个资源被释放。由于进程/线程被无限期地阻塞,因此程序不可能正常中止。

死锁的四个必要条件

  1. 互斥。资源必须处于非共享模式,即一次只有一个进程可以使用。如果另一进程申请该资源,那么必须等待直到该资源被释放为止。
  2. 占有并等待:一个进程至少应该占有一个资源,并等待另一资源,而该资源被其他进程所占有。
  3. 非抢占:资源不能被抢占。只能在持有资源的进程完成任务后,该资源才会被释放。
  4. 循环等待。

解决死锁的方法

解决死锁的方法可以从多个角度去分析,一般的情况下,有预防,避免,检测和解除四种

  • 预防 是采用某种策略,限制并发进程对资源的请求,从而使得死锁的必要条件在系统执行的任何时间上都不满足。
  • 避免则是系统在分配资源时,根据资源的使用情况提前做出预测,从而避免死锁的发生
  • 检测是指系统设有专门的机构,当死锁发生时,该机构能够检测死锁的发生,并精确地确定与死锁有关的进程和资源。
  • 解除 是与检测相配套的一种措施,用于将进程从死锁状态下解脱出来

死锁的预防

破坏「互斥」和「非抢占」是不现实的,所以主要有两个策略。

  1. 静态分配策略。所谓静态分配策略,就是指一个进程必须在执行前就申请到它所需要的全部资源,并且知道它所要的资源都得到满足之后才开始执行。进程要么占有所有的资源然后开始执行,要么不占有资源,不会出现占有一些资源等待一些资源的情况。这种策略严重地降低了资源利用率。
  2. 层次分配策略。所有的资源被分成了多个层次,一个进程得到某一层的一个资源后,它只能再申请较高一层的资源;当一个进程要释放某层的一个资源时,必须先释放所占用的较高层的资源。按这种策略,是不可能出现循环等待链的,因为那样的话,就出现了已经申请了较高层的资源,反而去申请了较低层的资源,不符合层次分配策略。

死锁的避免

死锁预防指破坏死锁产生的条件,这会导致低效的进程运行资源使用率的降低。死锁的避免允许同时存在四个必要条件,只要os做出明智选择,仍然可以避免死锁。

我们将系统的状态分为 安全状态不安全状态 ,每当在未申请者分配资源前先测试系统状态,若把系统资源分配给申请者会产生死锁,则拒绝分配,否则接受申请,并为它分配资源。

最具有代表性的,避免死锁算法就是银行家算法。银行家算法改善了资源使用率低的问题,但是它要不断地检测每个进程对各类资源的占用和申请情况,以及做 安全性检查 ,需要花费较多的时间。

死锁的检测

解决死锁问题的另一条途径是死锁检测和解除

用一个方框表示每一个资源类,方框中的黑点表示该资源类中的各个资源,每个键进程用一个圆圈表示,用 有向边 来表示进程申请资源和资源被分配的情况

process-resource-allocation-diagram

  1. 如果进程 - 资源分配图中无环路,则此时系统没有发生死锁
  2. 如果进程 - 资源分配图中有环路,且每个资源类仅有一个资源,则系统中已经发生了死锁。
  3. 如果进程 - 资源分配图中有环路,且涉及到的资源类有多个资源,此时系统未必会发生死锁。如果能在进程 - 资源分配图中找出一个 既不阻塞又非独立的进程「比如图2中的P2和P4」 ,该进程能够在有限的时间内归还占有的资源,也就是把边给消除掉了,重复此过程,直到能在有限的时间内 消除所有的边 ,则不会发生死锁,否则会发生死锁。(消除边的过程类似于 拓扑排序

死锁的解除

当死锁检测程序检测到存在死锁发生时,应设法让其解除,让系统从死锁状态中恢复过来,常用的解除死锁的方法有以下四种:

  1. 立即结束所有进程的执行,重新启动操作系统
  2. 撤销涉及死锁的所有进程,解除死锁后继续运行。
  3. 逐个撤销涉及死锁的进程,回收其资源直至死锁解除
  4. 抢占资源:从涉及死锁的一个或几个进程中抢占资源,把夺得的资源再分配给涉及死锁的进程直至死锁解除

什么是内存管理

操作系统的内存管理主要负责内存的分配与回收(malloc 函数:申请内存,free 函数:释放内存),另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事情。

简单分为连续分配管理方式非连续分配管理方式这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,常见的如 块式管理 。同样地,非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如页式管理段式管理

  1. 块式管理 : 远古时代的计算机操作系统的内存管理方式。将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,我们称之为碎片。
  2. 页式管理 :把主存分为大小相等且固定的一页一页的形式,页较小,相比于块式管理的划分粒度更小,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。
  3. 段式管理 : 页式管理虽然提高了内存利用率,但是页式管理其中的页并无任何实际意义。 段式管理把主存分为一段段的,段是有实际意义的,每个段定义了一组逻辑信息,例如,有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。 段式管理通过段表对应逻辑地址和物理地址。
  4. 段页式管理:段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段,每个段又分成若干页,也就是说 段页式管理机制 中段与段之间以及段的内部的都是离散的。

简单来说,页是物理单位,段是逻辑单位。分页可以有效提高内存利用率,分段可以更好满足用户需求。

快表和多级页表

在分页内存管理中,很重要的两点是:

  1. 虚拟地址到物理地址到转换要快
  2. 解决虚拟地址空间大,页表也会很大的问题

快表「TLB」

为了提高虚拟地址到物理地址的转换速度,操作系统在 页表方案 基础之上引入了 快表 来加速虚拟地址到物理地址的转换。我们可以把快表理解为一种特殊的高速缓冲存储器(Cache),其中的内容是页表的一部分或者全部内容。作为页表的 Cache,它的作用与页表相似,但是提高了访问速率。由于采用页表做地址转换,读写内存数据时 CPU 要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可加速查找并提高指令执行速度。

使用快表之后的地址转换流程是这样的:

  1. 根据虚拟地址中的页号查快表;
  2. 如果该页在快表中,直接从快表中读取相应的物理地址;
  3. 如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;
  4. 当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。

多级页表

引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间。

为了提高内存的空间性能,提出了多级页表的概念;但是提到空间性能是以浪费时间性能为基础的,因此为了补充损失的时间性能,提出了快表(即 TLB)的概念。 不论是快表还是多级页表实际上都利用到了程序的局部性原理。

分页机制和分段机制的共同点和区别

  • 共同点
    • 分页机制和分段机制都是为了提高内存利用率,减少内存碎片
    • 页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的
  • 区别
    • 页的大小是固定的,由操作系统决定;而段的大小不固定,取决于我们当前运行的程序
    • 分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要

逻辑(虚拟)地址和物理地址

我们编程一般只有可能和逻辑地址打交道,比如在 C 语言中,指针里面存储的数值就可以理解成为内存里的一个地址,这个地址也就是我们说的逻辑地址,逻辑地址由操作系统决定。物理地址指的是真实物理内存中地址,更具体一点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址

什么是CPU寻址

现代处理器使用的是一种称为 虚拟寻址 (Virtual Addressing) 的寻址方式。使用虚拟寻址,CPU 需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。 实际上完成虚拟地址转换为物理地址的硬件是 CPU 中含有一个被称为 内存管理单元(Memory Management Unit, MMU) 的硬件。

为什么要有虚拟地址空间

没有虚拟地址空间的时候,程序直接访问和操作的都是物理内存 。但是这样有什么问题呢?

  • 用户程序可以访问任意内存,寻址内存的每个字节,这样就很容易(有意或者无意)破坏操作系统,造成操作系统崩溃。
  • 想要同时运行多个程序特别困难,比如你想同时运行一个微信和一个 QQ 音乐都不行。为什么呢?举个简单的例子:微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就造成微信这个程序会崩溃。

总结来说:如果直接把物理地址暴露出来的话会带来严重问题,比如可能对操作系统造成伤害以及给同时运行多个程序造成困难

通过虚拟地址访问内存有以下优势:

  • 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区
  • 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
  • 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。

什么是虚拟内存

正是因为 虚拟内存 的存在,通过 虚拟内存 可以让程序拥有超过系统物理内存大小的可用内存空间。另外,虚拟内存为每个进程提供了一个一致的、私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉(每个进程拥有一片连续完整的内存空间)。这样会更加有效地管理内存并减少出错。

虚拟内存是计算机系统内存管理的一种技术,我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是 “使用硬盘空间来扩展内存 “的技术。虚拟内存的重要意义是它定义了一个连续的虚拟地址空间,并且 把内存扩展到硬盘空间

局部性原理

局部性原理是虚拟内存技术的基础,正是因为程序运行具有局部性原理,才可以只装入部分程序到内存就开始运行。

局部性原理主要表现在以下两个方面:

  1. 时间局部性:如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。
  2. 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,这是因为指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储

间局部性是通过将近来使用的指令和数据保存到高速缓存存储器中,并使用高速缓存的层次结构实现。空间局部性通常是使用较大的高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了 “内存一外存” 的两级存储器的结构,利用局部性原理实现髙速缓存。

虚拟内存的技术实现

虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。主要有三种方式:

  1. 请求分页存储管理:建立在分页管理之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。请求分页存储管理系统中,在作业开始运行之前,仅装入当前要执行的部分段即可运行。
  2. 请求分段存储管理:请求调段功能、分段置换功能。请求分段储存管理方式就如同请求分页储存管理方式一样,在作业开始运行之前,仅装入当前要执行的部分段即可运行;在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的程序段。
  3. 请求段页式存储管理

页面置换算法

地址映射过程中,若在页面中发现所要访问的页面不在内存中,则发生缺页中断。

当发生缺页中断时,如果当前内存中并没有空闲的页面,操作系统就必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。用来选择淘汰哪一页的规则叫做页面置换算法,我们可以把页面置换算法看成是淘汰页面的规则。

  • OPT页面置换算法「最佳页面置换算法」:淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的界面。一般作为衡量其他置换算法的方法。
  • 先进先出置换算法:总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰
  • 最近最久未使用页面置换算法:LRU 算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T。当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。
  • LFU页面置换算法「最少使用页面置换算法」:该置换算法选择在之前时期使用最少的页面作为淘汰页。

参考文章:JavaGuide