【Sofice小司笔记】4 操作系统,包含中断机制、进程管理、内存管理等常备面试考点

操作系统是管理硬件资源的软件,它负责进程管理,包括进程的创建、终止、同步和互斥。进程有五种状态,通过系统调用实现状态转换。进程间通信包括管道、消息队列、共享内存、信号量和信号,以及套接字。内存管理涉及连续分配和非连续分配,虚拟内存技术如分页和分段解决了内存不足的问题,通过局部性原理和页面置换算法实现内存扩展。
摘要由CSDN通过智能技术生成

操作系统概述

img

操作系统本质上是运行在计算机上的软件/程序,作为硬件基础上的第一层软件,操作系统是硬件和各种软件沟通的桥梁。其功能大致可分为两个部分:

  • 管理计算机硬件与软件资源
  • 向用户提供一个与系统交互的操作界面

四个特征

操作系统拥有 4 个鲜明的特征:并发、共享、虚拟和异步。其中,并发和共享是操作系统的最基本特征,没有并发和共享,就谈不上虚拟和异步。

① 并发

  • 并发:并发是指宏观上在一段时间内能同时运行多个程序。当然,这些程序宏观上是同时发生的,但微观上是交替发生的。操作系统通过引入进程和线程,使得程序能够并发运行。

  • 并行:并行则指同一时刻能运行多个指令,指两个或多个事件在同一时刻同时发生。并行需要硬件支持,如多流水线、多核处理器或者分布式计算系统。

② 共享

共享即资源共享,是指系统中的资源可供内存中多个并发执行的进程共同使用。

1)互斥共享:所谓互斥共享,就是说虽然这个资源是共享的,所有进程都能够使用,但是同一个资源在某一时刻只允许一个进程访问,也称为互斥访问,需要用同步机制来实现互斥访问。互斥共享/访问的资源称为临界资源

2)同时共享:同时共享与互斥共享相反,允许一个时间段内多个进程 “同时” 对系统中的某些资源进行访问。当然,所谓的 “同时” 往往是宏观上的,而在微观上,这些进程可能是交替地对该资源进行访问(即分时共享)

③ 虚拟

虚拟是指把一个物理上的实体变为若干个逻辑上的对应物

④ 异步

在多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一贯到底的, 而是走走停停,以不可预知的速度向前推进,这就是进程的异步性。

操作系统的核心

① 进程管理

进程就是程序的一次执行过程,它是暂时的。不仅包含正在运行的程序实体,并且包括这个运行的程序中占据的所有系统资源,比如说 CPU、内存、网络资源等。一个进程中可以有多个线程,它们共享进程资源。

进程管理其实主要包含两个内容:进程通信,进程调度

② 内存管理

把使用频繁的部分程序放入内存,当内存满的时候,替换掉内存中的某些部分

③ 文件系统管理

文件同样是受操作系统管理的,有关文件的构造、命名、存取、使用、保护、实现和管理方法都是操作系统设计的内容

④ I/O 设备管理

操作系统必须高效的管理 I/O 设备,它需要向 I/O 设备发送命令,捕捉中断,并处理设备的各种错误,它还应该在设备和系统的其他部分之间提供简单且易用的接口。

内核态和用户态

内核就是操作系统中的一组程序模块,作为可信软件来提供支持进程并发执行的基本功能和基本操作,具有访问硬件设备和所有内存空间的权限

CPU 上会运行两种程序,一种是操作系统的内核程序(也称为系统程序),一种是应用程序。前者完成系统任务,后者实现应用任务。两者之间有控制和被控制的关系,前者有权管理和分配资源,而后者只能向系统申请使用资源。

  • 内核态(kernel mode):当 CPU 处于内核态时,这是操作系统管理程序(也就是内核)运行时所处的状态。运行在内核态的程序可以访问计算机的任何资源,不受限制,为所欲为,例如协调 CPU 资源,分配内存资源,提供稳定的环境供应用程序运行等。
  • 用户态(user mode):应用程序基本都是运行在用户态的,或者说用户态就是提供应用程序运行的空间。运行在用户态的程序只能访问当前 CPU 上执行程序所在的地址空间,这样有效地防止了操作系统程序受到应用程序的侵害。

内核->用户态:系统调用返回

用户态->内核态:系统调用

中断机制

在合适的情况下,操作系统的内核会把 CPU 的使用权主动让给应用程序,也就是使 CPU 从内核态转换到用户态。而 CPU 要想从用户态回到内核态,只能通过中断机制完成,如果没有中断机制,那么一旦应用程序上 CPU 运行(用户态),CPU 就会一直运行这个应用程序。也就是说,中断是让操作系统内核夺回 CPU 使用权的唯一途径。可以说,操作系统是由中断驱动的

用户态转换到内核态有三种手段:

  • 1)程序请求操作系统服务,执行系统调用
  • 2)程序运行时产生外中断事件(比如 I/O 操作完成),运行程序被中断,转向中断程序处理
  • 3)在程序运行时发生内中断(异常)事件,运行程序被打断,转向异常处理程序工作

中断类型分为外中断和内中断:

  • 外中断 (也称中断,狭义上的中断)

    外中断与当前执行的指令无关, 中断信号来源于 CPU 外部。如 I/O 完成中断,表示设备输入/输出处理已经完成,CPU 能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。

  • 内中断(也称 异常、例外)

    内中断与当前执行的指令有关, 中断信号来源于 CPU 内部。如非法操作码、地址越界、算术溢出,除数为 0 等。

这里简单解释一下中断机制的基本原理:不同的中断信号,肯定是需要用不同的中断处理程序来处理的。那么当 CPU 检测到中断信号后,就会根据中断信号的类型去查询中断向量表,以此来找到相应的中断处理程序在内存中的存放位置。

系统调用

操作系统内核对共享资源进行统一的管理,并向上层提供系统调用

系统调用是通过陷入指令完成的,该指令会引发内中断。

操作系统作为计算机硬件之上的第一层软件,需要向上层提供一些简单易用的服务,这个上层包括用户和应用程序:

凡是与共享资源有关的操作(比如内存分配、I/O 操作、文件管理等),都必须通过系统调用的方式向操作系统内核提出请求,由操作系统内核代为完成。这样可以保证系统的稳定性和安全性,防止用户进行非法操作。

进程管理

进程是程序在某个数据集合上的一次运行活动,也是操作系统进行资源分配和保护的基本单位。

通俗来说,进程就是程序的一次执行过程,程序是静态的,它作为系统中的一种资源是永远存在的。而进程是动态的,它是动态的产生,变化和消亡的,拥有其自己的生命周期。

进程的组成

进程主要由三个部分组成:

1)进程控制块 PCB

2)数据段。即进程运行过程中各种数据(比如程序中定义的变量)

3)程序段。就是程序的代码(指令序列)

进程控制块 PCB

每个进程有且仅有一个进程控制块(Process Control Block,PCB),或称进程描述符,它是进程存在的唯一标识,是操作系统用来记录和刻画进程状态及环境信息的数据结构,也是操作系统掌握进程的唯一资料结构和管理进程的主要依据。所以说 PCB 是提供给操作系统使用的。

一般来说,PCB 会包含如下四类信息:

1)进程描述信息:用来让操作系统区分各个进程

  • 当进程被创建时,操作系统会为该进程分配一个唯一的、不重复的 “身份证号”— PID(ProcessID,进程 ID)
  • 另外,进程描述信息还包含进程所属的用户 ID(UID

2)进程控制和管理信息:记录进程的运行情况。比如 CPU 的使用时间、磁盘使用情况、网络流量使用情况等。

3)资源分配清单:记录给进程分配了哪些资源。比如分配了多少内存、正在使用哪些 I/O 设备、正在使用哪些文件等。

4)CPU 相关信息:进程在让出 CPU 时,必须保存该进程在 CPU 中的各种信息,比如各种寄存器的值。用于实现进程切换,确保这个进程再次运行的时候恢复 CPU 现场,从断点处继续执行。这就是所谓的保存现场信息

img

进程的状态

进程五态模型如下:

  • 运行态(running):进程占有 CPU 正在运行。

  • 就绪态(ready):进程具备运行条件,等待系统分配 CPU 以便运行。

  • 阻塞态 / 等待态(wait):进程不具备运行条件,正在等待某个事件的完成。

  • 新建态(new):进程正在被创建时的状态

  • 终止态(exit):进程正在从系统中消失时的状态

img

阻塞态是由于缺少需要的资源从而由运行态转换而来,但是该资源不包括 CPU 时间片,缺少 CPU 时间片会从运行态转换为就绪态

进程控制

对系统中的所有进程实施有效的管理,实现进程状态转换功能。包括创建进程、阻塞进程、唤醒进程、终止进程等,这些功能均由原语来实现。

原语是一种特殊的程序,它的执行具有原子性。操作系统通过原语来完成进程原理,包括进程的同步和互斥、进程的通信和管理。

进程的创建

创建进程的过程,也就是创建原语包含的内容如下:

  • 在进程列表中增加一项,从 PCB 池中申请一个空闲的 PCB(PCB 是有限的,若申请失败则创建失败),为新进程分配一个唯一的进程标识符;
  • 为新进程分配地址空间,由进程管理程序确定加载至进程地址空间中的程序;
  • 为新进程分配各种资源;
  • 初始化 PCB,如进程标识符、CPU 初始状态等;
  • 把新进程的状态设置为就绪态,并将其移入就绪队列,等待被调度运行。

引起进程创建的事件四种:

  • 用户登录:分时系统中,用户登录成功,系统会为其建立一个新的进程
  • 作业调度:多道批处理系统中,有新的作业放入内存中,会为其建立一个新的进程
  • 提供服务:用户向操作系统提出某些请求时,会新建一个进程处理该请求
  • 应用请求:由用户进程主动请求创建一个子进程

进程的终止

进程的终止也称为撤销,进程完成特定工作或出现严重错误后必须被终止。

终止(撤销)进程的过程,也就是撤销原语包含的内容如下:

  • 从 PCB 集合中找到终止进程的 PCB;
  • 若进程处于运行态,则立即剥夺其 CPU,终止该进程的执行,然后将 CPU 资源分配给其他进程;
  • 如果其还有子进程,则应将其所有子进程终止;
  • 将该进程所拥有的全部资源都归还给父进程或操作系统;
  • 回收 PCB 并将其归还至 PCB 池。

引起进程终止的事件有三种:

  • 正常结束:进程自己请求终止(exit 系统调用)
  • 异常结束:比如整数除 0,非法使用特权指令,然后被操作系统强行终止
  • 外界干预:Ctrl + Alt + delete 打开进程管理器,用户手动杀死进程

进程的阻塞和唤醒

进程的阻塞步骤,也就是阻塞原语的内容为:

  • 找到将要被阻塞的进程对应的 PCB;
  • 保护进程运行现场,将 PCB 状态信息设置为阻塞态,暂时停止进程运行;
  • 将该 PCB 插入相应事件的阻塞队列(等待队列)。

进程的唤醒步骤,也就是唤醒原语的内容为:

  • 在该事件的阻塞队列中找到相应进程的 PCB;
  • 将该 PCB 从阻塞队列中移出,并将进程的状态设置为就绪态;
  • 把该 PCB 插入到就绪队列中,等待被调度程序调度。

进程上下文切换

所谓进程的上下文切换,就是说各个进程之间是共享 CPU 资源的,不可能一个进程永远占用着 CPU 资源,不同的时候进程之间需要切换,使得不同的进程被分配 CPU 资源,这个过程就是进程的上下文切换,一个进程切换到另一个进程运行进程的上下文切换一定发生在内核态

进程上下文的切换也是一个原语操作,称为切换原语,其内容如下:

  • 首先,将进程 A 的运行环境信息存入 PCB,这个运行环境信息就是进程的上下文(Context)
  • 然后,将 PCB 移入相应的进程队列;
  • 选择另一个进程 B 进行执行,并更新其 PCB 中的状态为运行态
  • 当进程 A 被恢复运行的时候,根据它的 PCB 恢复进程 A 所需的运行环境

引起进程上下文切换的事件,也就是某个占用 CPU 资源运行的当前进程被赶出 CPU 的原因有如下:

  • 当前进程的时间片到
  • 有更高优先级的进程到达
  • 当前进程主动阻塞
  • 当前进程终止

线程

一个进程中可以有多个线程,它们共享这个进程的资源。进程还是作为资源分配的基本单位,但线程成为cpu时间片独立调度的基本单位

❓ 为什么要引入线程

线程又称为迷你进程,但是它比进程更容易创建,也更容易撤销

进程调度算法

img

非抢占式进程调度算法

所谓非抢占式的意思就是,当进程正在运行时,它就会一直运行,直到该进程完成或发生某个事件发生而被阻塞时,才会把 CPU 让给其他进程。

① 先到先服务 FCFS

先来先服务调度算法(First Come First Serve,FCFS):按照进程到达的先后顺序进行调度,先到的进程就先被调度,也就是说,等待时间越久的越优先得到服务。

优点:公平、算法实现简单

缺点:对短进程不利。排在长进程后面的短进程需要等待很长时间,短进程的响应时间太长了,用户交互体验会变差。

② 最短作业优先 SJF

最短作业/进程优先调度算法(Shortest Job First,SJF):每次调度时选择当前已到达的、且运行时间最短 的进程

最短作业优先算法和先到先服务恰好相反,先到先服务对短进程不利,而最短作业优先算法对长程不利。因为如果一直有短进程到来,那么长进程永远得不到调度,长进程有可能会饿死,处于一直等待短作业执行完毕的状态。

③ 高响应比优先 HRRN

高响应比优先算法(Highest Response Ratio Next,HRRN):只有当前运行的进程主动放弃 CPU 时(正常/异常完成,或主动阻塞),才需要进行调度,调度时计算所有就绪进程的响应比,为响应比最高的进程分配 CPU

响应比 = (进程的等待时间 + 进程需要的运行时间) / 进程需要的运行时间

抢占式进程调度算法

抢占就是指当进程正在运行的时,可以被打断,把 CPU 让给其他进程。抢占的原则一般有三种,分别是时间片原则、优先权原则、短作业优先原则。

① 最短剩余时间优先 SRTN

最短剩余时间优先(Shortest Remaining Time Next,SRTN)算法是最短作业优先的抢占式版本

当一个新的进程到达时,把它所需要的整个运行时间与当前进程的剩余运行时间作比较。如果新的进程需要的时间更少,则挂起当前进程,运行新的进程,否则新的进程等待。

② 时间片轮转调度算法 RR

轮转调度算法(Round Robin,RR)也称时间片调度算法:调度程序每次把 CPU 分配给就绪队列首进程使用规定的时间间隔,称为时间片,通常为 10ms ~ 200ms,就绪队列中的每个进程轮流地运行一个时间片,当时间片耗尽时就强迫当前运行进程让出 CPU 资源,转而排到就绪队列尾部,等待下一轮调度

最高优先级调度算法 HPF

在操作系统中,内核进程是比用户进程重要的多的,毕竟它关乎整个系统的稳定性。

最高优先级调度算法(Highest Priority First,HPF)就是从就绪队列中选择最高优先级的进程进行运行

进程的优先级分为:

  • 静态优先级:创建进程时候,就预先规定优先级,并且整个运行过程中该进程的优先级都不会发生变化。一般来说,内核进程的优先级都是高于用户进程的。
  • 动态优先级:根据进程的动态变化调整优先级。比如随着进程的运行时间增加,适当的降低其优先级;随着就绪队列中进程的等待时间增加,适当的升高其优先级。

另外,需要注意的是,最高优先级算法并非是固定的抢占式策略或非抢占式,系统可预先规定使用哪种策略:

  • 非抢占式:当就绪队列中出现优先级高的进程,则运行完当前进程后,再选择该优先级高的进程。
  • 抢占式:当就绪队列中出现优先级高的进程,则立即强制剥夺当前运行进程的 CPU 资源,分配给优先级更高的进程运行。

进程同步与互斥

**进程同步:**协调这些完成某个共同任务的并发线程,在某些位置上指定线程的先后执行次序、传递信号或消息。

**进程互斥:**一种特殊的进程同步,控制并发进程访问临界资源。

常见的进程同步与互斥机制有两种:

  • 信号量与 PV 操作
  • 管程

信号量与 PV 操作

用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现进程互斥或同步。这一对原语就是 PV 操作:

1)P 操作:将信号量值减 1,表示申请占用一个资源。如果结果小于 0,表示已经没有可用资源,则执行 P 操作的进程被阻塞。如果结果大于等于 0,表示现有的资源足够你使用,则执行 P 操作的进程继续执行。

可以这么理解,当信号量的值为 2 的时候,表示有 2 个资源可以使用,当信号量的值为 -2 的时候,表示有两个进程正在等待使用这个资源。不看这句话真的无法理解 V 操作,看完顿时如梦初醒。

2)V 操作:将信号量值加 1,表示释放一个资源,即使用完资源后归还资源。若加完后信号量的值小于等于 0,表示有某些进程正在等待该资源,由于我们已经释放出一个资源了,因此需要唤醒一个等待使用该资源(就绪态)的进程,使之运行下去。

img
实现进程互斥

两步走即可实现进程的互斥:

  • 定义一个互斥信号量,并初始化为 1
  • 把对于临界资源的访问置于 P 操作和 V 操作之间

P 操作和 V 操作必须成对出现。缺少 P 操作就不能保证对临界资源的互斥访问,缺少 V 操作就会导致临界资源永远得不到释放、处于等待态的进程永远得不到唤醒。

实现进程同步

必须保证 “代码4” 一定是在 “代码2” 之后才会执行。

img
生产者和消费者问题

下面我们利用信号量和 PV 操作来解决经典的进程同步和互斥问题:生产者和消费者问题。

【问题描述】:系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。任何时刻,只能有一个生产者或消费者可以访问缓冲区。

由题可知,生产者、消费者共享一个初始为空、大小为 n 的缓冲区,我们从题目中提炼出同步与互斥关系:

  • 同步关系 1:只有缓冲区没满时(优先级高),生产者才能把产品放入缓冲区(优先级低),否则必须等待
  • 同步关系 2:只有缓冲区不空时(优先级高),消费者才能从中取出产品(优先级低),否则必须等待
  • 互斥关系:缓冲区是临界资源,各进程必须互斥地访问。

既然这个题目有两个同步关系和一个互斥关系,那么我们就需要两个同步信号量和一个互斥信号量:

  • empty:同步信号量(对应同步关系 1),表示生产者还能生产多少,即还能放入缓冲区多少产品,该数量小于等于 0,则生产者不能进行生产。 初始化为 n。
  • full:同步信号量(对应同步关系 2),表示消费者还能从缓冲区取出多少,即当前缓冲区已有产品的数量,该数量小于等于 0,则消费者不能进行读取。初始化为 0。
  • mutex:互斥信号量,实现对缓冲区的互斥访问。初始化为 1。

代码如下,注意各个 PV 操作的配对:

管程

管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程将永远不能使用管程。也就是说管程天生支持进程互斥

其实使用管程是能够实现信号量的,并且也能用信号量实现管程。但是管程封装的比较好,相比起信号量来需要我们编写的代码更少,更加易用,这也就是 Java 采用管程机制的原因,synchronized 关键字及 wait()notify()notifyAll() 这三个方法都是管程的组成部分。把管程翻译为 Java 领域的语言,就是管理类的成员变量和成员方法,让这个类是线程安全的。

进程通信

进程通信( InterProcess Communication,IPC)就是指进程之间的信息交换。实际上,进程的同步与互斥本质上也是一种进程通信,只不过它传输的仅仅是信号量,通过修改信号量,使得进程之间建立联系,相互协调和协同工作,但是它缺乏传递数据的能力

进程之间想要进行信息交换就必须通过内核

Linux 内核提供的常见的进程通信机制:

  • 管道(也称作共享文件)
  • 消息队列(也称作消息传递)
  • 共享内存(也称作共享存储)
  • 信号量和 PV 操作
  • 信号
  • 套接字(Socket)

管道

匿名管道

各位如果学过 Linux 命令,那对管道肯定不陌生,Linux 管道使用竖线 | 连接多个命令,这被称为管道符。

$ command1 | command2

以上这行代码就组成了一个管道,它的功能是将前一个命令(command1)的输出,作为后一个命令(command2)的输入,从这个功能描述中,我们可以看出管道中的数据只能单向流动,也就是半双工通信,如果想实现相互通信(全双工通信),我们需要创建两个管道才行。

另外,通过管道符 | 创建的管道是匿名管道,用完了就会被自动销毁。并且,匿名管道只能在具有亲缘关系(父子进程)的进程间使用,。也就是说,匿名管道只能用于父子进程之间的通信

在 Linux 的实际编码中,是通过 pipe 函数来创建匿名管道的,若创建成功则返回 0,创建失败就返回 -1:

int pipe (int fd[2]);

该函数拥有一个存储空间为 2 的文件描述符数组:

  • fd[0] 指向管道的读端,fd[1] 指向管道的写端
  • fd[1] 的输出是 fd[0] 的输入

粗略的解释一下通过匿名管道实现进程间通信的步骤:

1)父进程创建两个匿名管道,管道 1(fd1[0]fd1[1])和管道 2(fd2[0]fd2[1]);

因为管道的数据是单向流动的,所以要想实现数据双向通信,就需要两个管道,每个方向一个。

2)父进程 fork 出子进程,于是对于这两个匿名管道,子进程也分别有两个文件描述符指向匿名管道的读写两端;

3)父进程关闭管道 1 的读端 fd1[0] 和 管道 2 的写端 fd2[1],子进程关闭管道 1 的写端 fd1[1] 和 管道 2 的读端 fd2[0],这样,管道 1 只能用于父进程写、子进程读;管道 2 只能用于父进程读、子进程写。管道是用环形队列实现的,数据从写端流入从读端流出,这就实现了父子进程之间的双向通信。

看完上面这些讲述,我们来理解下管道的本质是什么:对于管道两端的进程而言,管道就是一个文件(这也就是为啥管道也被称为共享文件机制的原因了),但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中。

简单来说,管道的本质就是内核在内存中开辟了一个缓冲区,这个缓冲区与管道文件相关联,对管道文件的操作,被内核转换成对这块缓冲区的操作

有名管道

匿名管道由于没有名字,只能用于父子进程间的通信。为了克服这个缺点,提出了有名管道,也称做 FIFO,因为数据是先进先出的传输方式。

所谓有名管道也就是提供一个路径名与之关联,这样,即使与创建有名管道的进程不存在亲缘关系的进程,只要可以访问该路径,就能够通过这个有名管道进行相互通信。

使用 Linux 命令 mkfifo 来创建有名管道:

$ mkfifo myPipe

myPipe 就是这个管道的名称,接下来,我们往 myPipe 这个有名管道中写入数据:

$ echo "hello" > myPipe

执行这行命令后,你会发现它就停在这了,这是因为管道里的内容没有被读取,只有当管道里的数据被读完后,命令才可以正常退出。于是,我们执行另外一个命令来读取这个有名管道里的数据:

$ cat < myPipe
hello

消息队列

可以看出,管道这种进程通信方式虽然使用简单,但是效率比较低,不适合进程间频繁地交换数据,并且管道只能传输无格式的字节流。为此,消息传递机制(Linux 中称消息队列)应用而生。比如,A 进程要给 B 进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B 进程在需要的时候自行去消息队列中读取数据就可以了。同样的,B 进程要给 A 进程发送消息也是如此。

消息队列的本质就是存放在内存中的消息的链表,而消息本质上是用户自定义的数据结构。如果进程从消息队列中读取了某个消息,这个消息就会被从消息队列中删除。对比一下管道机制:

  • 消息队列允许一个或多个进程向它写入或读取消息。
  • 消息队列可以实现消息的随机查询,不一定非要以先进先出的次序读取消息,也可以按消息的类型读取。比有名管道的先进先出原则更有优势。
  • 对于消息队列来说,在某个进程往一个队列写入消息之前,并不需要另一个进程在该消息队列上等待消息的到达。而对于管道来说,除非读进程已存在,否则先有写进程进行写入操作是没有意义的。
  • 消息队列的生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列就会一直存在。而匿名管道随进程的创建而建立,随进程的结束而销毁。

需要注意的是,消息队列对于交换较少数量的数据很有用,因为无需避免冲突。但是,由于用户进程写入数据到内存中的消息队列时,会发生从用户态拷贝数据到内核态的过程;同样的,另一个用户进程读取内存中的消息数据时,会发生从内核态拷贝数据到用户态的过程。因此,如果数据量较大,使用消息队列就会造成频繁的系统调用,也就是需要消耗更多的时间以便内核介入

共享内存

为了避免像消息队列那样频繁的拷贝消息、进行系统调用,共享内存机制出现了。

顾名思义,共享内存就是允许不相干的进程将同一段物理内存连接到它们各自的地址空间中,使得这些进程可以访问同一个物理内存,这个物理内存就成为共享内存。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

集合内存管理的内容,我们来深入理解下共享内存的原理。首先,每个进程都有属于自己的进程控制块(PCB)和逻辑地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的逻辑地址(虚拟地址)与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同进程的逻辑地址通过页表映射到物理空间的同一区域,它们所共同指向的这块区域就是共享内存

不同于消息队列频繁的系统调用,对于共享内存机制来说,仅在建立共享内存区域时需要系统调用,一旦建立共享内存,所有的访问都可作为常规内存访问,无需借助内核。这样,数据就不需要在进程之间来回拷贝,所以这是最快的一种进程通信方式。

信号量和 PV 操作

见进程同步与互斥中

信号

注意!信号和信号量是完全不同的两个概念

信号是进程通信机制中唯一的异步通信机制,它可以在任何时候发送信号给某个进程。通过发送指定信号来通知进程某个异步事件的发送,以迫使进程执行信号处理程序。信号处理完毕后,被中断进程将恢复执行。用户、内核和进程都能生成和发送信号。

信号事件的来源主要有硬件来源和软件来源。所谓硬件来源就是说我们可以通过键盘输入某些组合键给进程发送信号,比如常见的组合键 Ctrl+C 产生 SIGINT 信号,表示终止该进程;而软件来源就是通过 kill 系列的命令给进程发送信号,比如 kill -9 1111 ,表示给 PID 为 1111 的进程发送 SIGKILL 信号,让其立即结束。我们来查看一下 Linux 中有哪些信号:

Socket

至此,上面介绍的 5 种方法都是用于同一台主机上的进程之间进行通信的,如果想要跨网络与不同主机上的进程进行通信,那该怎么做呢?这就是 Socket 通信做的事情了(当然,Socket 也能完成同主机上的进程通信)。

Socket 起源于 Unix,原意是插座,在计算机通信领域,Socket 被翻译为套接字,它是计算机之间进行通信的一种约定或一种方式。通过 Socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

从计算机网络层面来说,Socket 套接字是网络通信的基石,是支持 TCP/IP 协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的 IP 地址,本地进程的协议端口,远地主机的 IP 地址,远地进程的协议端口

Socket 的本质其实是一个编程接口(API),是应用层与 TCP/IP 协议族通信的中间软件抽象层,它对 TCP/IP 进行了封装。它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面。对用户来说,只要通过一组简单的 API 就可以实现网络的连接。

总结

简单总结一下上面六种 Linux 内核提供的进程通信机制:

1)首先,最简单的方式就是管道,管道的本质是存放在内存中的特殊的文件。也就是说,内核在内存中开辟了一个缓冲区,这个缓冲区与管道文件相关联,对管道文件的操作,被内核转换成对这块缓冲区的操作。管道分为匿名管道和有名管道,匿名管道只能在父子进程之间进行通信,而有名管道没有限制。

2)虽然管道使用简单,但是效率比较低,不适合进程间频繁地交换数据,并且管道只能传输无格式的字节流。为此消息队列应用而生。消息队列的本质就是存放在内存中的消息的链表,而消息本质上是用户自定义的数据结构。如果进程从消息队列中读取了某个消息,这个消息就会被从消息队列中删除。

3)消息队列的速度比较慢,因为每次数据的写入和读取都需要经过用户态与内核态之间数据的拷贝过程,共享内存可以解决这个问题。所谓共享内存就是:两个不同进程的逻辑地址通过页表映射到物理空间的同一区域,它们所共同指向的这块区域就是共享内存。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

对于共享内存机制来说,仅在建立共享内存区域时需要系统调用,一旦建立共享内存,所有的访问都可作为常规内存访问,无需借助内核。这样,数据就不需要在进程之间来回拷贝,所以这是最快的一种进程通信方式。

4)共享内存速度虽然非常快,但是存在冲突问题,为此,我们可以使用信号量和 PV 操作来实现对共享内存的互斥访问,并且还可以实现进程同步。

5)信号和信号量是完全不同的两个概念!信号是进程通信机制中唯一的异步通信机制,它可以在任何时候发送信号给某个进程。通过发送指定信号来通知进程某个异步事件的发送,以迫使进程执行信号处理程序。信号处理完毕后,被中断进程将恢复执行。用户、内核和进程都能生成和发送信号。

6)上面介绍的 5 种方法都是用于同一台主机上的进程之间进行通信的,如果想要跨网络与不同主机上的进程进行通信,就需要使用 Socket 通信。另外,Socket 也能完成同主机上的进程通信。

内存管理

内存管理的任务就是有效地管理内存,即记录哪些内存是正确使用的,哪些内存是空闲的,在进程需要时为其分配内存,在进程使用完后释放内存。

通俗来说,内存管理所研究的内容无外乎以下这三个方面:

  • Fetch
  • Placement
  • 替换 Replacement

目前 “放” 的技术可归结成两类:

  • 连续分配,即运行的程序和数据必须放在内存的一片连续空间中。

  • 不连续分配,即运行的程序和数据可以放在内存的多个不相邻的块中。

内存的逻辑扩充技术:

  • 覆盖技术
  • 交换技术
  • 虚拟内存

物理内存管理

连续分配管理方式

其实在早期的操作系统中,采用的都是连续内存空间分配的策略。那时还没有引入进程概念,内存分配还是以作业(相当于进程)为单位,而所谓连续分配呢就是将作业分配到一段连续的内存空间

单一连续分配

仅将内存空间分成两块:系统区(用于存放操作系统相关数据)和用户区(用于存放用户进程相关数据)。

单一连续分配的管理方式确实有点过于简单了,内存中只能有一道用户程序,用户程序独占整个用户区空间。

缺点自然是显而易见:只能用于单用户、单任务的操作系统中;有内部碎片(分配给某进程的内存区域 中,如果有些部分没有用上,就是“内部碎片”);内存利用率极低。

固定分区分配

为了能在内存中装入多道程序,且这些程序之间又不会相互干扰, 于是考虑将整个用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业,这样就形成了最早的、最简单的一种可运行多道程序的内存管理方式。

遗憾的是,虽然固定分区分配的方式支持了多道程序,但是仍然会产生内部碎片,内存利用率依然比较低。

动态分区分配

动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时, 根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。

非连续分配管理方式

由于其要求把作业(进程)放在内存的一片连续区域中,很容易出现大段的连续内存空间因为不足够容纳作业或进程而不可用。因此,为了充分利用内存空间资源而引入了非连续分配策略。

基本分页管理

页,页框

将内存空间分为一个个大小相等的分区,每个分区就称为一个 “页框(page frame)”。每个页框有一个编号,即“页框号”(也成为物理页框号、内存块号),页框号从 0 开 始 。

将进程的虚拟地址空间也分为与页框大小相等的一个个分区, 每个分区就称为一个 “页(page)” 或 “页面” 。每个页面也有一个编号, 即“页号”(也称为虚拟页号),页号也是从 0 开始。

操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系。 各个页面不必连续存放,可以放到不相邻(离散)的各个页框中

图片来源《现代操作系统 - 第 3 版》

虚拟地址(页面)和物理地址(页框)的映射

操作系统为每个进程建立了一张页表,页表通常存在进程控制块(PCB)中。进程的每个页面对应一个页表项,每个页表项由页号和块号(页框号)组成,记录着进程页面和实际存放的内存块之间的映射关系。

img

快表

保证虚拟地址到物理地址的转换足够快

计算机设置了一个小型的硬件设备(内置在 CPU 的 MMU(内存管理单元) 中),将虚拟地址直接映射成物理地址,而不必再访问页表。这个设备就是转换检测缓冲区(Translation Lookaside Buffer,TLB),也被称为快表

多级页表

解决虚拟地址空间大,页表也会很大的问题(页表项多了,页表自然也就大了)

把页表再分页并离散存储,然后再建立一张页表记录页表各个部分的存放位置,称为 “页目录表”(或称外层页表、顶层页表)。

多级页表技术不但突破了页表必须连续存放的限制,同时当有大片虚拟地址空间未使用时,可以不分配对应页表空间,因此可节省内存。另外,多级页表增加了访存次数,因此外层页表的页表项应该尽可能保持在 TLB 中,以减少访存开销。

图片来源《操作系统 - 第 3 版》
基本分段管理

虽然页式管理提高了内存利用率,但是页式管理划分出来的页并无任何实际意义。

段式系统是按照用户作业(进程)中的自然段来划分逻辑空间的。比如说,用户作业(进程)由主程序、两个子程序、栈和一段数据组成,于是可将这个用户作业(进程)划分成 5 段,显然,页面是定长的而段不是

段与段之间可以不连续存储,但是段的内部仍然是连续的。

和基本分页管理一样,基本分段管理也需要一个数据结构来记录虚拟地址和物理地址之间的映射,这个数据结构就是段表

基本段页管理

如果一个段比较大,把它整个保存在内存中可能很不方便甚至不可能的,因此对它产生了分页的想法。

对虚拟地址空间先进行段的划分,然后在每一段内再进行页的划分。例如,若用户进程由主程序、子程序和数据段组成,则通过段、页划分后如图所示:

虚拟内存管理

为了解决内存不足的情况,缓和大程序与小内存之间的矛盾,扩充内存容量势在必行。

覆盖技术(Overlay)的基本思想就是:程序运行时并非任何时候都要访问程序及数据的所有部分(尤其是大程序),因此可以把用户空间(内存)分成一个固定区和一个或多个覆盖区。

覆盖技术的缺点显而易见并且可以说是让人无法接受的,那就是覆盖技术是把解决内存空间不足的问题交给了用户。操作系统仅仅为用户提供将覆盖段调入内存的系统调用,但是必须由用户自己来说明覆盖哪个段、调入哪个段。

交换技术(Swapping)的基本思想是:空闲进程/作业主要存储在外存(磁盘)上,当其中某个进程/作业需要运行的时候,就将其从磁盘中完整地调入内存,使该进程运行一段时间,然后再把它返回磁盘。所以说当进程/作业不运行的时候它们是不会占用内存的。

事实上,覆盖和交换技术分别解决了传统存储管理(物理内存管理)中存在的某个问题:

  • 覆盖技术打破了作业/进程必须一次性全部装入内存后才能开始运行(一次性)的限制
  • 交换技术打破了一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束(驻留性)的限制

虚拟内存

虚拟内存技术基于一个非常重要的原理,局部性原理

1)时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环)

2)空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问(因为很多数据在内存中都是连续存放的,并且程序的指令也是顺序地在内存中存放的)

虚拟内存技术的实现是建立在不连续分配管理方式之上的。传统的基本分页管理、基本分段管理、基本段页式管理和虚拟内存技术结合,分别称为请求分页管理(页式虚存系统)、请求分段管理(段式虚存系统)、请求段页式管理(段页式虚存系统)。

在页式虚存系统中,每当 CPU 要访问的页面不在内存时,就会产生一个缺页中断,然后由操作系统的缺页中断处理程序来处理中断。此时,缺页的这个进程/作业就会被阻塞住,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。

  • 如果内存中有空闲块,则为该进程分配一个空闲块,将所缺的页面装入这个块中,并修改页表中相应的页表项。
  • 如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存,未修改过的页面不用写回外存。

请求分页管理的页表自然是会复杂不少的:

1)为了实现 “请求调页” 功能,操作系统需要知道每个页面是否已经调入内存,如果还没调入,那么也需要知道该页面在磁盘中存放的位置。

2)而当内存空间不够时,要实现 “页面置换” 功能,操作系统需要通过某些指标来决定到底换出哪个页面,有的页面没有被修改过,就不用浪费时间写回磁盘;有的页面修改过,就需要将磁盘中的旧数据覆盖。因此,操作系统也需要记录各个页面是否被修改的信息。

为此,请求分页管理的页表中添加了 4 个字段:

  • 状态位:该页面是否已调入内存
  • 访问字段:可记录该页面最近被访问过几次,或记录上次访问该页面的时间,供页面置换算法换出页面时参考
  • 修改位:该页面调入内存后是否被修改过
  • 外存地址:该页面在外存中的存放地址

基本分段管理、基本段页式管理和虚拟内存技术结合,分别称为请求分页管理(页式虚存系统)、请求分段管理(段式虚存系统)、请求段页式管理(段页式虚存系统)。

在页式虚存系统中,每当 CPU 要访问的页面不在内存时,就会产生一个缺页中断,然后由操作系统的缺页中断处理程序来处理中断。此时,缺页的这个进程/作业就会被阻塞住,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。

  • 如果内存中有空闲块,则为该进程分配一个空闲块,将所缺的页面装入这个块中,并修改页表中相应的页表项。
  • 如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存,未修改过的页面不用写回外存。

请求分页管理的页表自然是会复杂不少的:

1)为了实现 “请求调页” 功能,操作系统需要知道每个页面是否已经调入内存,如果还没调入,那么也需要知道该页面在磁盘中存放的位置。

2)而当内存空间不够时,要实现 “页面置换” 功能,操作系统需要通过某些指标来决定到底换出哪个页面,有的页面没有被修改过,就不用浪费时间写回磁盘;有的页面修改过,就需要将磁盘中的旧数据覆盖。因此,操作系统也需要记录各个页面是否被修改的信息。

为此,请求分页管理的页表中添加了 4 个字段:

  • 状态位:该页面是否已调入内存
  • 访问字段:可记录该页面最近被访问过几次,或记录上次访问该页面的时间,供页面置换算法换出页面时参考
  • 修改位:该页面调入内存后是否被修改过
  • 外存地址:该页面在外存中的存放地址
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值