【uCore 操作系统】0. 操作系统概述

  • 本文所讲的内容适用于所有操作系统。

【 1. 什么是操作系统 】

  • 操作系统(OS)是一个软件,它帮助用户和应用程序 使用和管理计算机(更通用的讲,有嵌入式设备、手机、服务器等)的资源

1.1 系统软件

  • 系统软件 是为计算机系统 提供基本功能,并在计算机系统范围内使用的软件,其作用可涉及到整个计算机系统。
  • 系统软件包括操作系统内核、驱动程序、工具软件、用户界面、软件库等。
    • 操作系统内核 是系统软件的核心部分,负责 控制计算机的硬件资源 并为用户和应用程序提供服务。
    • 驱动程序操作系统用于控制硬件设备 的软件,如显卡驱动、声卡驱动和打印机驱动等。一般情况下,驱动程序是操作系统内核的一部分
    • 工具软件 是操作系统提供的用于 维护、调试和优化计算机系统 的软件,如磁盘碎片整理工具、系统信息工具和病毒查杀工具等。
    • 用户界面 可以是 图形用户界面 (GUI) 命令行界面 (CLI)。图形用户界面是操作系统的一种常见用户界面,它使用图形元素(如图标、菜单和按钮)来帮助用户使用操作系统。通常,图形用户界面提供了一个桌面环境,其中包含可以打开和关闭的窗口,用户可以在其中运行应用程序和执行其他操作。
    • C 语言 标准库 libc(类似的有 Rust 标准库 等)提供了与 OS 交互的系统调用接口,其功能覆盖了整个计算机系统,会被许多不同的软件访问和调用。
  • 因此,从系统软件的角度看, 操作系统是一种系统软件

1.2 执行环境

  • 站在应用程序的角度来看,我们可以发现常见的 应用程序其实是运行在由硬件、操作系统内核、运行时库、图形界面支持库等所包起来的一个 执行环境 (Execution Environment) 中,如下图所示。
    在这里插入图片描述
  • 执行环境提供了运行应用软件所需的运行时服务,包括内存管理、文件系统访问、网络连接等。而这些 执行环境中的服务大部分是由操作系统来提供的 应用程序只需根据与系统软件约定好的 应用程序二进制接口 (ABI, Application Binary Interface) 来请求执行环境提供的各种服务或功能,从而完成应用程序自己的功能。
  • 基于这样的观察,我们可以把 操作系统的定义简化为: 应用程序的软件执行环境 。这种概括性描述可以适用于操作系统发展的不同历史时期。从这个角度出发,操作系统可以包括运行时库、图形界面支持库等系统软件。(在 后续小节“执行环境” 中会对执行环境相关含义进行进一步的阐述)

1.3 操作系统的定义与组成

1.3.1 操作系统的定义

  • 操作系统这个系统软件干的事主要有两件:一是向下管理并控制计算机硬件和各种外设,二是向上管理应用软件并提供各种服务。我们可对其进一步定义为: 操作系统是一种系统软件,主要功能是向下管理CPU、内存和各种外设等硬件资源,并形成软件执行环境来向上管理和服务应用软件。这样的描述也符合大多数操作系统教材上对操作系统的定义。
  • 为了完成上述工作,操作系统需要知道如何与硬件打交道,如何给应用软件提供服务。这就有一系列与操作系统相关的理论、抽象、设计等来支持如何做和做得好这两件事情。

1.3.2 操作系统的组成

  • 在一些嵌入式环境中,操作系统以运行时库的形式与应用程序紧密结合在一起,形成一个可在嵌入式硬件上执行的嵌入式应用。所以,在不同的应用场景下,操作系统的边界也是不同的,我们 可以把运行时库、图形界面支持库等这些可支持不同应用的系统软件 (System Software) 也看成是操作系统的一部分
  • 在一般情况下, 操作系统的主要组成 包括:
    • 操作系统内核:操作系统的核心部分,负责 控制计算机的硬件资源并为用户和应用程序提供服务
    • 系统工具和软件库:为操作系统 提供基本功能的软件,包括工具软件和系统软件库等。
    • 用户接口:是操作系统的外壳,是用户与操作系统交互的方式。用户接口包括图形用户界面(GUI)和命令行界面(CLI)等。
  • 操作系统内核的主要组成 部分包括:
    • 进程/线程管理:内核负责管理系统中的进程或线程,创建、销毁、调度和切换进程或线程。
    • 内存管理:内核负责管理系统的内存,分配和回收内存空间,并保证进程之间的内存隔离。
    • 文件系统:内核提供文件系统接口,负责 管理存储设备上的文件和目录,并允许应用访问文件系统。
    • 网络通信:内核提供网络通信接口,负责 管理网络连接并允许应用进行网络通信
    • 设备驱动:内核提供设备驱动接口,负责 管理硬件设备并允许应用和内核其他部分访问设备
    • 同步互斥:内核负责 协调多个进程或线程之间对共享资源的访问同步 功能主要用于解决进程或线程之间的 协作问题互斥功能主要用于解决进程或线程之间的 竞争问题
    • 系统调用接口:内核 提供给应用程序访问系统服务的入口,应用程序通过系统调用接口调用操作系统提供的服务,如文件系统、网络通信、进程管理等。
  • 一个典型的UNIX操作系统的组成示意图:
    在这里插入图片描述

1.4 操作系统的历史

  • 操作系统也是从计算机诞生大约十年后,从无到有地逐步发展起来的。如今在二十一世纪初期的大众眼中,操作系统就是他们的手机/终端上的软件系统,包括各种应用程序集合,图形界面和网络浏览器是其中重要的组成部分。
  • 操作系统的内涵和外延随着历史的发展也一直在变化,但无论操作系统的内在实现和具体目标如何变化,其 管理计算机硬件,给应用提供服务的核心定位 没有变化。

【 2. 操作系统的系统调用接口 】

2.1 ABI 应用程序二进制接口

  • 背景
    操作系统内核是一个提供各种服务的软件,其服务对象是应用程序,而 用户(这里可以理解为一般使用计算机的人)是通过应用程序的服务间接获得操作系统的服务的,因此操作系统内核藏在一般用户看不到的地方。但 应用程序需要访问操作系统获得操作系统的服务,这就需要通过操作系统的 ABI 接口才能完成
  • 操作系统与运行在用户态软件之间的接口形式就是上一节提到的 应用程序二进制接口 (ABI, Application Binary Interface)
  • 应用程序二进制接口 ABI 是不同二进制代码片段的连接纽带。ABI 定义了二进制机器代码级别的规则,主要包括基本数据类型、通用寄存器的使用、参数的传递规则、以及堆栈的使用等等。 ABI 与处理器和内存地址等硬件架构相关,是用来约束链接器 (Linker) 和汇编器 (Assembler) 的。在同一处理器下,基于不同高级语言编写的应用程序、库和操作系统,如果遵循同样的 ABI 定义,那么它们就能正确链接和执行

2.2 API 应用程序编程接口

  • 应用程序编程接口 API 是不同源代码片段的连接纽带。API 定义了一个源码级(如 C 语言)函数的参数,参数的类型,函数的返回值等。因此 API 是用来约束编译器 (Compiler) 的:一个 API 是给编译器的一些指令,它规定了源代码可以做以及不可以做哪些事。 API 与编程语言相关,如 libc 是基于 C 语言编写的标准库,那么基于 C 的应用程序就可以通过编译器建立与 libc 的联系,并能在运行中正确访问 libc 中的函数。
  • 操作系统不能只提供 面向单一编程语言的函数库编程接口 (API, Application Programming Interface) ,它的接口需要考虑对基于各种编程语言的应用支持,以及访问安全等因素,使得应用软件不能像访问函数库一样的直接访问操作系统内部函数,更不能直接读写操作系统内部的地址空间。

2.3 系统调用接口

  • 为此,操作系统设计了一套安全可靠的 二进制接口,我们称为 系统调用接口 (System Call Interface)系统调用接口通常面向应用程序提供了 API 的描述,但在具体实现上,还需要提供 ABI 的接口描述规范
  • 在现代处理器的安全支持(特权级隔离,内存空间隔离等)下,应用程序就不能直接以函数调用的方式访问操作系统的函数,以及直接读写操作系统的数据变量 不同类型的应用程序 可以通过符合操作系统规定的系统调用接口,发出系统调用请求,来获得操作系统的服务,操作系统提供完服务后,返回应用程序继续执行。

2.4 系统调用接口与功能

  • 对于通用的应用程序,一般需要关注如下问题,并希望得到操作系统的支持:
    • 一个运行的程序如何能输出字符信息?如何能获得输入字符信息?
    • 一个运行的程序可以要求更多(或更少)的内存空间吗?
    • 一个运行的程序如何持久地存储用户数据?
    • 一个运行的程序如何与连接到计算机的设备通信并通过它们与物理世界通信?
    • 多个运行的程序如何同步互斥地对共享资源进行访问?
    • 一个运行的程序可以创建另一个程序的实例吗?需要等待另外一个程序执行完成吗?一个运行的程序能暂停或恢复另一个正在运行的程序吗?
  • 操作系统主要通过基于 ABI 的系统调用接口来给应用程序提供上述服务,以支持应用程序的各种需求。通用操作系统为支持各种应用的服务需求,需要有相对多的系统调用服务接口,比如目前 Linux 有超过三百个的系统调用接口。下面列出了一些相对比较重要的操作系统接口或抽象,以及它们的大致功能:
    • 进程(即程序运行过程)管理:复制创建进程 fork 、退出进程 exit 、执行进程 exec 等。
    • 线程管理:线程(即程序的一个执行流)的创建、执行、调度切换等。
    • 线程同步互斥的并发控制:互斥锁 mutex 、信号量 semaphore 、管程 monitor 、条件变量 condition variable 等。
    • 进程间通信:管道 pipe 、信号 signal 、事件 event 等。
    • 虚存管理:内存空间映射 mmap 、改变数据段地址空间大小 sbrk 、共享内存 shm 等。
    • 文件 I/O 操作:对存储设备中的文件进行读 read 、写 write 、打开 open 、关闭 close 等操作。
    • 外设 I/O 操作:外设包括键盘、显示器、串口、磁盘、时钟 … ,主要采用文件 I/O 操作接口。
  • 上述表述在某种程度上说明了操作系统对计算机硬件重要组成的抽象和虚拟化,这样会有助于应用程序开发。应用程序员只需访问统一的抽象概念(如文件、进程等),就可以使用各种复杂的计算机物理资源(处理器、内存、外设等)
    • 文件 (File) 是外设的一种抽象和虚拟化。特别对于存储外设而言,文件是持久存储的抽象。
    • 地址空间 (Address Space) 是对内存的抽象和虚拟化
    • 进程 (Process) 是对计算机资源的抽象和虚拟化。而其中最核心的部分是对 CPU 的抽象与虚拟化。
      在这里插入图片描述
  • 有了这些系统调用接口,简单的应用程序就不用考虑底层硬件细节,可以在操作系统的服务支持和管理下简洁地完成其应用功能了。值得注意的是,我们设计的各种操作系统总共只用到三十个左右系统调用功能接口(如下表所示),就可以支持应用需要的上述功能。而且这些调用与最初的 UNIX 的系统调用接口类似,几乎没有变化。尽管UNIX 的系统调用最早是在 1970 年左右设计和实现的,但这些调用中的大多数仍然在今天的系统中广泛使用。
编号系统调用功能描述
1sys_exit结束执行
2sys_write(2)输出字符串/(6)写文件
3sys_yield暂时放弃执行
4sys_get_time获取当前时间
5sys_getpid获取进程id
6sys_fork创建子进程
7sys_exec执行新程序
8sys_waitpid等待子进程结束
9sys_read(5)读取字符串/(6)读文件
10sys_open打开/创建文件
11sys_exit结束执行
12sys_close关闭文件
13sys_dup复制文件描述符
14sys_kill发送信号给某进程
15sys_sigaction设立信号处理例程
16sys_sigprocmask设置要阻止的信号
17sys_sigreturn从信号处理例程返回
18sys_sleep进程休眠一段时间
19sys_thread_create创建线程
20sys_gettid获取线程id
21sys_waittid等待线程结束
22sys_mutex_create创建锁
23sys_mutex_lock获取锁
24sys_mutex_unlock释放锁
25sys_semaphore_create创建信号量
26sys_semaphore_up减少信号量的计数
27sys_semaphore_down增加信号量的计数
28sys_condvar_create创建条件变量
29sys_condvar_signal唤醒阻塞在条件变量上的线程
30sys_condvar_wait阻塞与此条件变量关联的当前线程

2.5 系统调用接口举例

  • 以rCore-Tutorial中的例子,一个应用程序显示一个字符串,来看看系统调用的具体内容。应用程序的代码如下:
    这个程序的功能就是显示一行字符串。注意,这里的 println! 一个宏。
// user/src/bin/hello_world.rs
...
pub fn main() -> i32 {
   println!("Hello world from user mode program!");
   0
}
  • 进一步跟踪源代码 (位于 user/src/console.rs ),可以看到 println! 会进一步展开为 write 函数:
// user/src/console.rs
...
impl Write for Stdout {
   fn write_str(&mut self, s: &str) -> fmt::Result {
      write(STDOUT, s.as_bytes());
      Ok(())
   }
}
  • 这个write函数就是对系统调用 sys_write 的封装:
    sys_write 用户库函数封装了 sys_write 系统调用的API接口,这个系统调用API的参数和返回值的含义如下:
    • SYSCALL_WRITE 表示 sys_write 的系统调用号。
    • fd 表示待写入文件的文件描述符;
    • buf 表示内存中缓冲区的起始地址;
    • len 表示内存中缓冲区的长度;
    • 返回值:返回成功写入的长度或错误值。
// user/src/lib.rs
...
pub fn write(fd: usize, buf: &[u8]) -> isize {
 sys_write(fd, buf)
}

// user/src/syscall.rs
...
pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
   syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
}
  • sys_write 系统调用的ABI接口描述了具体用哪些寄存器来保存参数和返回值:
    这里我们看到,API中的各个参数和返回值分别被RISC-V通用寄存器 x17 (即存放系统调用号)、 x10 (存放 fd ,也保存返回值) 、 x11 (存放 buf )和 x12 (存放 len )保存。
// user/src/syscall.rs
...
fn syscall(id: usize, args: [usize; 3]) -> isize {
   let mut ret: isize;
   unsafe {
      asm!(
            "ecall",
            inlateout("x10") args[0] => ret,
            in("x11") args[1],
            in("x12") args[2],
            in("x17") id
      );
   }
   ret
}

【 3. 操作系统抽象 】

3.1 执行环境

  • 执行环境 (Execution Environment) 是一个内涵很丰富且有一定变化的术语,它主要 负责给在其上执行的软件提供相应的功能与资源,并可在计算机系统中形成多层次的执行环境

3.1.1 执行环境的演变

  • 1.对于现在直接运行在裸机硬件 (Bare-Metal) 上的操作系统,其执行环境是 计算机的硬件。计算机刚刚诞生时,还没有操作系统的概念,对于直接运行在裸机硬件上的应用程序而言,其执行环境也是 计算机的硬件 。
  • 2.随着计算机技术的发展,应用程序下面形成了一层比较通用的函数库,这使得应用程序不需要直接访问硬件了,它所需要的功能(比如显示字符串)和资源(比如一块内存)都可以通过函数库的函数来帮助完成。在第二个阶段,应用程序的执行环境就变成了 函数库 -> 计算机硬件 ,而这时函数库的执行环境就是计算机的硬件。
  • 3.再进一步,操作系统取代了函数库来访问硬件,函数库通过访问操作系统的系统调用服务来进一步给应用程序提供丰富的功能和资源。在第三个阶段,应用程序的执行环境就变成了 函数库 -> 操作系统内核 -> 计算机硬件 。在后面又出现了基于 Java 语言的应用程序,在函数库和操作系统之间,多了一层 Java 虚拟机,此时 Java 应用程序的执行环境就变成了 函数库 -> Java 虚拟机 -> 操作系统内核 -> 计算机硬件 。
  • 4.在云计算时代,在传统操作系统与计算机硬件之间多了一层 Hypervisor/VMM ,此时应用程序的执行环境变成了 函数库 -> Java 虚拟机 -> 操作系统内核 -> Hypervisor/VMM -> 计算机硬件 。这里可以看到,随着软件需求的多样化和复杂化, 执行环境的层次 也越来越多。
    在这里插入图片描述

3.1.2 执行环境的切换

  • 此外,CPU在执行过程中,可以在不同层次的执行环境之间切换,这称为 执行环境切换 。执行环境切换主要是通过特定的 API 或 ABI 来完成的,这样 不同执行环境的软件就能实现数据交换与互操作,而且还保证了彼此之间有清晰的隔离
    在这里插入图片描述

3.1.3 应用程序与执行环境

  • 对于应用程序的执行环境而言,应用程序只能看到执行环境直接提供给它的接口(API 或 ABI),这使得应用程序所能得到的服务取决于执行环境提供给它的访问接口。所以,操作系统可以看成是应用程序执行环境,其形态可以是一个库,也可以是一个虚拟机等,或者它们的某种组合形式

比如, 手机上的 Android操作系统 是 Android 应用程序的执行环境,它包括了库形态的 Framework 层、执行 Java 程序的虚拟机层,与操作系统交互的系统类 C 库和 Linux kernel。
运行在服务器上的 麒麟服务器操作系统 、 openEuler 服务器操作系统 、 龙蜥服务器操作系统 、 RHEL-RedHat 服务器操作系统 是各种服务器应用软件, 如Web Server 应用、数据库等的执行环境,这些操作系统都包含了与操作系统交互的系统类 C 库、Java 虚拟机、容器系统、VMM 虚拟机系统等和Linux kernel。

  • 基于上面的介绍,我们可以给应用程序的执行环境一个基本的定义:执行环境是应用程序正确运行所需的服务与管理环境,用来完成应用程序在运行时的数据与资源管理、应用程序的生存期等方面的处理,它定义了应用程序有权访问的其他数据或资源,并决定了应用程序的行为限制范围

3.2 控制流

3.2.1 普通控制流

  • 各种应用程序在执行环境中执行其功能,而具体如何执行,取决于 程序的控制流 。
  • 程序的控制流
    回顾一下编译原理课上的知识,程序的控制流 (Flow of Control or Control Flow) 是指 以一个程序的指令、语句或基本块为单位的执行序列
  • 处理器的控制流
    再回顾一下计算机组成原理课上的知识,处理器的控制流 是指 处理器中程序计数器的控制转移序列。最简单的一种控制流(没有异常或中断产生的前提下)是一个“平滑的”序列,其中每个要执行的指令地址在内存中都是相邻的。
  • 程序员眼中的控制流
    如果站在程序员的角度来看控制流,会发现控制流是程序员编写的 程序的执行序列,这些序列是程序员预设好的。程序运行时能以多种简单的控制流(顺序、分支、循环结构和多层嵌套函数调用)组合的方式,来一行一行的执行源代码(以编程语言级的视角),也是一条一条的执行汇编指令(以汇编语言级的视角)。
  • 普通控制流
    对于上述的不同描述,我们可以统称其为 普通控制流 (CCF,Common Control Flow,简称 控制流) 在应用程序视角下,它只能接触到它所在的执行环境, 不会跳到其他执行环境,所以应用程序执行基本上是以普通控制流的形式完成整个运行的过程。

3.2.2 异常控制流

  • 应用程序在执行过程中,如果发出系统调用请求,或出现外设中断、CPU 异常等情况, 处理器执行的前一条指令和后一条指令将会位于两个完全不同的位置,即不同的执行环境,我们把这种“突变”的控制流称为 异常控制流 (ECF, Exceptional Control Flow)
    比如,前一条指令还在应用程序的代码段中,后一条指令就跑到操作系统的代码段中去了,这就是一种控制流的“突变”,即控制流脱离了其所在的执行环境,并产生 执行环境的切换。
    简单地说, 异常控制流 是处理器在执行过程中的突变,其主要作用是通过硬件和操作系统的协同工作来响应处理器状态中的特殊变化
    比如当应用程序正在执行时,产生了时钟外设中断,导致操作系统打断当前应用程序的执行,转而进入 操作系统 执行环境去处理时钟外设中断。处理完毕后,再回到应用程序中被打断的地方继续执行。

  • 应用程序 “感知” 不到这种异常的控制流情况的原因
    这主要是由于操作系统把这种情况 透明 地进行了执行环境的切换和对各种异常情况的处理,让应用程序从始至终地 认为 没有这些异常控制流的产生

  • 异常控制流的分类

异常控制流
外设中断 (Device Interrupt)
陷入 (Trap)
异常 (Exception , 也称Fault Interrupt)

我们是从操作系统的角度来给出的异常控制流的定义,我们这里的异常控制流不涉及C++/Java等编程语言级的exception机制。

3.2.3 控制流上下文(执行环境的状态)

  • 站在硬件的角度来看普通控制流或异常控制流的具体执行过程,我们会发现 从控制流起始的某条指令执行开始,指令可访问的所有物理资源的内容,包括自带的所有通用寄存器、特权级相关特殊寄存器、以及指令访问的内存等,会随着指令的执行而逐渐发生变化。这里我们把 控制流在执行完某指令时的物理资源内容,即确保下一时刻能继续 正确 执行控制流指令的物理资源内容 称为 控制流的 上下文 (Context) ,也可称为 控制流所在执行环境的状态。我们这里说的控制流的上下文是指 仅会影响控制流正确执行的有限的物理/虚拟资源内容
    物理资源:即计算机硬件资源,如CPU的寄存器、可访问的物理内存等。
    虚拟资源:即操作系统提供的资源,如文件,网络端口号,网络地址,信号等。
  • 程序中控制流的上下文对程序正确执行的影响
    如果在某时刻,由于某种有意或无意的原因,控制流的上下文发生了变化(比如某个寄存器的值变了),但并不是由于程序的控制流本身的指令导致的,这就会使得接下来的程序指令执行出现偏差,并最终导致执行过程或执行结果不符合预期,这种情形称为 程序执行错误 。 而操作系统有责任来保护应用程序中控制流的上下文,以让应用程序得以正确执行。
  • 控制流上下文的分类
定义
函数调用上下文如果一个控制流属于某个函数,那么这个控制流的上下文简称为 函数调用上下文
应用程序上下文如果一个控制流属于某个应用程序,那么这个控制流的上下文简称为 应用程序上下文。
某进程上下文如果把某 进程 看做是运行的应用程序,那么这个属于某个应用程序的控制流可简称为某进程上下文。
操作系统上下文如果一个控制流属于操作系统,那么这个控制流的上下文简称为 操作系统上下文。
中断/异常/陷入的上下文如果一个控制流是属于操作系统中处理中断/异常/陷入的那段代码,那么这个控制流的上下文简称为 中断/异常/陷入的上下文 。
  • 控制流上下文的作用
    在 CPU 不断执行指令的过程中,各种前缀的上下文(执行环境的状态)会不断地变化。如果出现了处理器在执行过程中的突变(即异常控制流)或转移(如多层函数调用),需要由维持执行环境的软硬件协同起来,保存发生突变或转移前的控制流上下文,即当前执行环境的状态(比如突变或函数调用前一刻的指令寄存器,栈寄存器和其他一些通用寄存器等内容),并在完成突变处理或被调用函数执行完毕后,恢复突变或转移前的控制流上下文。这是由于完成与突变相关的执行会破坏突变前的控制流上下文(比如上述各种寄存器的内容),导致如果不保存之前的控制流上下文,就无法恢复到突变前正确的执行环境,继续正常的普通控制流的执行。
    • 对于异常控制流的上下文保存与恢复,主要是通过 CPU 和操作系统(手动编写在栈上保存与恢复寄存器的指令)来协同完成;
    • 对于函数转移控制流的上下文保存与恢复,主要是通过编译器(自动生成在栈上保存与恢复寄存器的指令)来帮助完成的。

3.2.4 异常控制流:中断

  • 外设中断 (Interrupt) 是指由外部设备引起的外部 I/O 事件,如时钟中断、控制台中断等。 外设中断是 异步产生的,与处理器的执行无关产生中断后,操作系统需要进行中断处理来响应中断请求,这会破坏被打断前应用程序的控制流上下文,所以操作系统要保存与恢复被打断前应用程序的控制流上下文。
    在这里插入图片描述

3.2.5 异常控制流:异常

  • 异常 (Exception) 是在处理器执行指令期间检测到 不正常的或非法的内部事件(如 x86 平台上的除零错、地址访问越界)。产生异常后,操作系统需要进行异常处理,这会破坏被打断前应用程序的控制流上下文,所以操作系统要保存与恢复被打断前应用程序的控制流上下文。
  • 如果是应用程序产生的不可恢复的异常,操作系统有权直接终止该应用程序的执行。
    在这里插入图片描述

3.2.6 异常控制流:陷入

  • 陷入 (Trap) 是程序在执行过程中由于要 通过系统调用请求操作系统服务而 有意 引发 的事件。产生陷入后,操作系统需要执行系统调用服务来响应系统调用请求,这会破坏陷入前应用程序的控制流上下文,所以操作系统要保存与恢复陷入前应用程序的控制流上下文。
    在这里插入图片描述

3.2.7 risc-v 指令集的 中断与异常

  • 在 RISC-V 的特权级规范文档中,中断和异常统称为 陷入
  • 异常 指的是由于 CPU 当前指令执行而产生的异常控制流。在操作系统意义上的陷入,在 RISC-V 的语境下属于异常的一部分。
  • 中断 指的是与 CPU 当前指令执行无关的异常控制流。
  • 当中断或异常触发时,我们首先进行统一的陷入处理流程,随即根据 mcause/scause 等寄存器的内容判定目前触发的是中断还是异常,再对应进行处理。另外,在 x86 架构下的“软件中断”(也即指令 int 0x80 )可以理解为操作系统意义上的陷入,但在 RISC-V 语境下软件中断表示一种特殊的处理核间中断。

3.3 进程

  • 站在应用程序自身的角度来看,进程 (Process) 的一个经典定义是 一个正在进行运行的程序实例
  • 当程序运行在操作系统中的时候,从程序的视角来看,它会产生一种“幻觉”:即该程序是整个计算机系统中当前运行的唯一的程序,能够独占使用处理器、内存和外设,而且程序中的代码和数据是系统内存中唯一的对象。然而,这种“幻觉”是操作系统为了便于应用的开发且不损失安全性刻意为应用程序营造出来的,它具体表现为 “进程” 这个抽象概念。站在计算机系统和操作系统的角度来看,并不存在这种“幻觉”。
    在这里插入图片描述
  • 事实上,在一段时间之内,往往会有多个程序同时或交替在操作系统上运行,因此程序并不能独占整个计算机系统。具体而言,进程是应用程序的一次执行过程,并且在这个执行过程中,由“操作系统”执行环境来管理程序执行过程中的 进程上下文 即一种控制流上下文。这里的进程上下文是指程序在运行中的各种物理/虚拟资源(寄存器、可访问的内存区域、打开的文件、信号等)的内容,特别是与程序执行相关的具体内容:内存中的代码和数据,栈、堆、当前执行的指令位置(程序计数器的内容)、当前执行时刻的各个通用寄存器中的值等。进程上下文如下图所示:
    在这里插入图片描述
  • 我们知道,为了提高处理器的利用率,操作系统需要让处理器足够忙,即让不同的程序轮流占用处理器来运行。如果一个程序因某个事件而不能运行下去时,就通过进程上下文切换把处理器占用权转交给另一个可运行程序。进程上下文切换如下图所示:
    在这里插入图片描述
  • 综上所述,我们可以给进程一个更加准确的定义:一个进程是一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。操作系统中的进程管理需要采用某种调度策略将处理器资源分配给程序并在适当的时候回收,并且要尽可能充分利用处理器的硬件资源。

3.4 地址空间/虚拟内存

  • 地址空间 (Address Space) 是对 物理内存的虚拟化和抽象,也称 虚存 (Virtual Memory)。它就是操作系统通过处理器中的 MMU 内存管理单元 (MMU, Memory Management Unit) 硬件的支持而给应用程序和用户提供一个大的(可能超过计算机中的物理内存容量)、连续的(连续的地址空间编址)、私有的(其他应用程序无法破坏)的存储空间。这需要操作系统将内存和外存(即持久存储,硬盘是一种典型的外存)结合起来管理,为用户提供一个容量比实际内存大得多的虚拟存储器,并且需要操作系统为应用程序分配内存空间,使用户存放在内存中的程序和数据彼此隔离、互不侵扰。
  • 操作系统中的虚存管理与处理器的 MMU 密切相关, 在启动虚存机制后,软件通过 CPU 访问的每个虚拟地址都需要通过 CPU 中的 MMU 内存管理单元转换为一个物理地址来进行访问。下面是虚拟的地址空间与物理内存和物理磁盘映射的图示:
    在这里插入图片描述

3.5 文件

  • 文件 (File) 主要用于 对持久存储的抽象,并进一步扩展到为 外设的抽象
    文件可理解为存放在持久存储介质(比如硬盘、光盘、U盘等)上,方便应用程序和用户读写的数据。以磁盘为代表的持久存储介质的数据访问单位是一个扇区或一个块,而在内存中的数据访问单位是一个字节或一个字。这就需要操作系统通过文件来屏蔽磁盘与内存差异,尽量以内存的读写方式来处理持久存储的数据。当处理器需要访问文件中的数据时,可通过操作系统把它们装入内存。
  • 文件管理的任务是有效地支持文件的存储、 检索和修改等操作。下面是文件对磁盘的抽象映射图示:
    在这里插入图片描述
  • 文件也是对外设的抽象
    从一个更高和更广泛的层次上看,各种外设虽然差异很大,但也有基本的读写操作,可以通过文件来进行统一的抽象,并在操作系统内部实现中来隐藏对外设的具体访问过程,从而让用户可以以统一的文件操作来访问各种外设。这样就可以把文件看成是对外设的一种统一抽象,应用程序通过基本的读写操作来完成对外设的访问。

【 4. 操作系统的特征 】

  • 从总体上看,操作系统具有五个方面的特征:虚拟化 (Virtualization)、并发性 (Concurrency)、异步性、共享性和持久性 (Persistency)。
    • 虚拟化 可以理解为它 对内存、CPU 的抽象和处理
    • 并发性和共享性 可以理解为操作系统 支持多个应用程序“同时”运行
    • 异步性 可以从操作系统调度、中断处理对应用程序执行造成的影响等几个方面来理解;
    • 持久性 则可以从操作系统中的文件系统支持 把数据方便地从磁盘等存储介质上存入和取出 来理解。

4.1 虚拟性

4.1.1 内存虚拟化

  • 程序员在写应用程序的时候,不用考虑其程序的起始内存地址要放到计算机内存的具体某个位置,而是用字符串符号定义了各种变量和函数,直接在代码中便捷地使用这些符号就行了。这是由于 操作系统建立了一个 地址固定 , 空间巨大 的虚拟内存给应用程序来运行,这是 内存虚拟化 。内存虚拟化的核心问题是:采用什么样的方式让虚拟地址和物理地址对应起来,也就是如何将虚拟地址“翻译”成物理地址。
  • 内存虚拟化 其实是一种 “空间虚拟化” , 可进一步细分为 内存地址虚拟化 和 内存大小虚拟化 。
    • 这里的每个符号在运行时是要对应到具体的内存地址的。这些内存地址的具体数值是什么?程序员不用关心。因为 编译器会自动帮我们把代码中的各种符号翻译成地址,形成可执行程序。这就是 内存地址虚拟化
    • 程序使用的内存是否占得太大了?在一般情况下,程序员也不用关心。应用程序在运行时不用考虑当前物理内存是否够用。如果应用程序需要一定空间的内存,但由于在某些情况下,物理内存的空闲空间可能不多了,这时操作系统通过把物理内存中最近没使用的空间(不是空闲的,只是最近用得少)换出(就是“挪地”)到硬盘上暂时缓存起来,这样空闲空间就大了,就可以满足应用程序的运行时内存需求了,从而实现了 内存大小虚拟化,这就是 内存大小虚拟化
  • 实际上,编译器 (Compiler,比如 gcc) 和链接器 (linker,比如 ld) 也不知道程序每个符号对应的地址应该放在未来程序运行时的哪个物理内存地址中。
    所以,编译器的一个简单处理办法就是,设定一个固定地址(比如 0x10000)作为起始地址开始存放代码,代码之后是数据,所有全局变量和函数的符号都在这个起始地址之后的某个固定偏移位置。
    假定程序每次运行都是位于一个不会变化的起始地址, 这里全局变量的地址在编译链接后会确定不变。但 局部变量是放在堆栈中的,会随着堆栈大小的动态变化而变化。这里编译器产生的地址就是虚拟地址。
  • 这里,编译器和链接器图省事,找了一个适合它们的解决办法。
    当程序要运行的时候,这个符号所对应的虚拟内存地址到计算机的物理内存地址的映射必须要解决了,这自然就推到了操作系统身上,操作系统会把编译器和链接器生成的执行代码和数据放到空闲的物理内存中,并建立虚拟地址到物理地址的映射关系。 由于物理内存中的空闲区域是动态变化的,这导致虚拟地址到物理地址的映射关系也是动态变化的,需要操作系统来维护好可变的映射关系,确保编译器“固定起始地址”的假设成立。
    只有操作系统维护好了这个映射关系,才能让程序员只需写一些易于人理解的字符串符号来代表一个内存空间地址。这样,编译器只需确定一个固定地址作为程序的起始地址,就可以不用考虑将来这个程序要在哪个物理地址空间运行的问题,从而实现了 内存地址虚拟化 。

4.1.2 CPU虚拟化

  • 不同的应用程序可以在内存中并发运行,相同的应用程序也可有多个拷贝在内存中并发运行。而每个程序都“认为”自己完全独占了 CPU 在运行,这是 CPU虚拟化,也是一种 “时间虚拟化” 。操作系统给了运行的应用程序一个幻象,即 操作系统把时间分成小段,每个应用程序占用其中一小段时间片运行,用完这一时间片后,操作系统会切换到另外一个应用程序,让它运行。由于时间片很短,操作系统的切换开销也很小,应用程序或使用应用程序的用户基本上是看不出的,反而感觉到多个程序各自在独立“并行”执行,从而实现了 CPU虚拟化 。

4.2 并发性

  • 并行 (Parallel) 是指 两个或者多个事件在 同一时刻 发生
  • 并发 (Concurrent) 是指 两个或多个事件在 同一时间间隔内 发生
  • 操作系统为了能够让 CPU 充分地忙起来,并充分利用各种资源,就需要有多种不同的应用程序在执行。这些应用程序是 分时执行 的,并由操作系统来完成各个应用在运行时的任务切换。对于基于单 CPU 的计算机而言,各个“同时”运行的 程序其实是串行分时复用一个 CPU ,任一个时刻点上只有一个程序在 CPU 上运行。 这些虚拟性的特征给应用程序的开发和执行提供了非常方便的执行环境,但也给操作系统的设计与实现提出了很多挑战。
  • 并发性带来的问题
    并发性虽然能有效改善系统资源的利用率,但也带来了对共享资源的争夺问题,即同步互斥问题。还会带来执行时间的不确定性问题,即并发程序在执行中是走走停停,断续推进的,使得应用程序的完成时间是不确定的。并发性对操作系统的设计也带来了很多挑战,一不小心就会出现程序执行结果不确定,程序死锁等很难调试和重现的问题。

4.3 异步性

  • 在这里,异步 是指 由于操作系统的调度和中断等,会不时地暂停或打断当前正在运行的程序,使得程序的整个运行过程走走停停
  • 在应用程序运行的表现上,特别体现在它的 执行完成时间是不可预测的。但需要注意,只要应用程序的输入是一致的,那么它的输出结果应该是符合预期的。

4.4 共享性

  • 共享 是指多个应用并发运行时, 宏观上体现出它们可同时访问同一个资源,即这个资源可被共享。但其实在 微观上,操作系统 在硬件等的支持下要确保应用程序互斥(互不影响,要么按顺序先后访问,要么在硬件级上保护使得能同时访问)访问这个共享的资源
    • 单核处理器 下,对于两个应用同时访问同一个内存单元的情况,从宏观的应用层面上看,二者都能正确地读出同一个内存单元的内容;而在微观上,操作系统会调度应用程序的 先后执行 顺序,确保在任何一个时刻,只有一个应用去访问存储单元。
    • 多核处理器下,多个 CPU 核可能 同时访问 同一内存单元,在这种多核场景下的共享性不仅仅由 OS 来保证,还需硬件级的 Cache 一致性保证。

4.5 持久性

  • 操作系统的 持久性:操作系统提供了文件系统来 从可持久保存的存储介质(磁盘, SSD 等,以后以硬盘来代表)中取数据和代码到内存中,并可以把内存中的数据写回到硬盘上。硬盘在这里是外设,具有持久性,以文件系统的形式呈现给应用程序。
  • 文件系统也可看成是操作系统对存储外设(如硬盘、SSD 等)的虚拟化。 这种持久性的特征进一步带来了共享属性,即在文件系统中的文件可以被多个运行的程序所访问,从而给应用程序之间实现数据共享提供了方便。即使掉电,存储外设上的数据还不会丢失,可以在下一次机器加电后提供给运行的程序使用。持久性对操作系统的执行效率提出了挑战,如何让数据在高速的内存和慢速的硬盘间高效流动是需要操作系统考虑的问题。

参考

  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MR_Promethus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值