操作系统(二)操作系统的运行机制
Published in:2024-10-13 | category: 操作系统
Words: 8.2k | Reading time: 27min | reading:

操作系统的运行机制

内核程序VS应用程序

一条高级语言的代码翻译过来可能对应多条机器指令。程序运行的过程就是CPU执行一条一条的机器指令的过程。

微软、苹果有一帮人负责实现操作系统,他们写的是“内核程序”,由很多内核程序组成了“操作系统内核”,或简称“内核(Kernel)”
内核是操作系统最重要最核心的部分,也是最接近硬件的部分
甚至可以说,一个操作系统只要有内核就够了(eg: Docker->仅需Linux内核)
操作系统的功能未必都在内核中,如图形化用户界面GUI

特权指令VS非特权指令

应用程序只能使用“非特权指令”,如:加法指令、减法指令等
操作系统内核作为“管理者”,有时会让CPU执行一些“特权指令”,如:内存清零指令。这些指令影响重大,只允许“管理者”–即操作系统内核来使用

在CPU设计和生产的时候就划分了特权指令和非特权指令,因此CPU执行一条指令前就能判断出其类型。

内核态VS用户态

CPU能判断出指令类型,但是它怎么区分此时正在运行的是内核程序or应用程序

CPU有两种状态,“内核态”和“用户态”
处于内核态时,说明此时正在运行的是内核程序,此时可以执行特权指令
处于用户态时,说明此时正在运行的是应用程序,此时只能执行非特权指令

CPU中有一个寄存器叫程序状态字寄存器(PSW),其中有个二进制位,1表示“内核态”,0表示“用户态”
别名:内核态=核心态=管态;用户态=目态

内核态、用户态的切换

内核态->用户态:执行一条特权指令–修改PSW的标志位为“用户态”,这个动作意味着操作系统将主动让出CPU使用权
用户态->内核态:由“中断”引发,硬件自动完成变态过程,触发中断信号意味着操作系统将强行夺回CPU的使用权

  1. CPU使用权交给操作系统的含义

    • 指令执行权限角度
      • 在计算机系统中,CPU是执行指令的核心部件。当把CPU使用权交给操作系统时,意味着CPU开始执行操作系统内核中的指令序列。例如,在计算机启动时,BIOS(基本输入输出系统)完成初始化后,会将CPU控制权交给引导加载程序,引导加载程序再把控制权交给操作系统内核。这时,CPU就按照操作系统内核的代码逻辑,开始执行诸如初始化硬件设备、创建进程管理的数据结构、加载驱动程序等一系列复杂的指令。
    • 资源管理角度
      • 操作系统负责管理计算机系统的各种资源,包括内存、I/O设备、CPU时间等。当拥有CPU使用权时,操作系统可以合理地分配CPU时间片给不同的进程。比如,在多任务操作系统中,操作系统会根据一定的调度算法(如先来先服务、时间片轮转、优先级调度等),决定哪个进程可以使用CPU。这就好像一个交通警察(操作系统)在指挥交通(管理CPU资源),决定哪辆车(进程)可以通过路口(使用CPU)。
  2. 操作系统执行指令的方式

    • 自动执行引导过程指令
      • 在启动阶段,操作系统会自动执行一系列预先编写好的指令。例如,当计算机电源开启后,BIOS加载引导扇区的内容,引导扇区的代码引导操作系统内核加载到内存中,这个过程是自动进行的,由硬件和预先存储在固件中的程序触发,然后操作系统内核开始自动执行初始化指令,包括设置中断向量表、初始化内存管理单元等。
    • 响应事件执行指令
      • 操作系统会自动响应各种事件来执行指令。以系统调用为例,当用户程序需要执行一些特权操作(如文件读写、内存分配等),它会通过系统调用指令触发一个软件中断。操作系统内核收到这个中断信号后,会自动执行相应的处理指令。例如,当用户程序请求读取一个文件时,操作系统会自动执行文件系统相关的指令,如定位文件在磁盘上的位置、读取文件内容到内存缓冲区等,然后将结果返回给用户程序。
    • 基于定时器中断执行指令
      • 定时器中断是操作系统进行多任务调度的重要手段。操作系统设置一个定时器,每隔一定时间(如几毫秒)就会产生一次定时器中断。当定时器中断发生时,操作系统会自动执行指令来进行进程切换。例如,假设当前进程的时间片用完了,定时器中断触发后,操作系统会自动保存当前进程的上下文(如程序计数器、寄存器内容等),然后选择下一个要执行的进程,恢复其上下文并开始执行该进程的指令。

当操作系统通过中断夺回CPU控制权来处理引发中断的事件(如检测到用户态试图执行特权指令这种非法事件)时,会进行以下几种处理:

一、异常处理相关操作

  1. 识别异常类型和来源

    • 操作系统首先会确定中断是由非法的特权指令执行尝试引发的。通过中断向量表(一个存储了各种中断处理程序入口地址的数据结构),操作系统可以快速定位到与这种特定类型的非法指令执行相关的处理程序。这个处理程序会详细记录异常发生的程序位置(例如,记录是哪个应用程序中的哪条指令引发了异常),这有助于后续的故障排查和安全审计。
  2. 保存应用程序上下文

    • 在停止当前应用程序的执行之前,操作系统会保存应用程序的当前状态,包括程序计数器(PC)的值,即下一条要执行的指令的地址;各个通用寄存器(如累加器、索引寄存器等)的内容;以及其他与程序执行相关的状态信息(如栈指针、标志寄存器等)。这样做是为了在处理完中断后,如果决定继续执行该应用程序,能够恢复到中断发生前的状态。
  3. 报告异常情况

    • 操作系统可能会向系统日志记录异常信息,包括异常类型(这里是用户态执行特权指令)、发生异常的应用程序标识符、异常发生的时间等。这对于系统管理员或安全软件来监控系统安全和稳定性非常重要。同时,操作系统也可能会向用户显示一个错误提示,告知用户应用程序出现了异常,不过具体是否显示提示以及如何显示会因操作系统设计和配置而异。

二、安全相关操作

  1. 终止违规应用程序

    • 一种常见的处理方式是直接终止引发异常的应用程序。操作系统会回收该应用程序占用的所有资源,包括内存空间、打开的文件句柄、网络连接等。这可以防止恶意代码进一步破坏系统或者窃取用户数据。在终止应用程序后,操作系统可能会向用户显示一个错误消息,说明应用程序因为安全违规而被终止。
  2. 安全审计和监控

    • 操作系统会更新安全审计记录,记录这次非法尝试执行特权指令的事件。这些记录可以用于后续的安全分析,例如统计特定应用程序或者用户账户的异常行为频率,以便发现潜在的安全威胁模式。安全监控软件可以利用这些记录来实时监控系统安全状态,并在发现异常行为频繁发生时采取更严格的安全措施,如限制可疑用户账户的权限或者对相关应用程序进行更深入的安全检查。
  3. 隔离和防护措施

    • 如果操作系统检测到这种非法尝试可能是由于外部攻击(如黑客入侵)引起的,它可能会启动隔离机制。例如,将受到影响的应用程序所在的网络连接进行隔离,防止攻击扩散到其他网络节点。同时,操作系统可能会加强系统的防护措施,如更新防火墙规则、增加入侵检测系统的敏感度等。

三、资源和系统状态恢复操作

  1. 恢复系统资源状态
    • 在处理中断的过程中,操作系统需要确保系统资源的状态是正确的。例如,如果应用程序在试图执行特权指令之前已经占用了某些硬件资源(如独占访问了某个I/O设备),操作系统需要检查这些资源是否处于安全状态,是否需要进行清理或者重新初始化。如果资源状态被破坏,操作系统需要采取措施恢复它们,以保证其他应用程序能够正常使用这些资源。
  2. 重新调度CPU
    • 完成对中断事件的处理后,操作系统会根据其调度策略重新安排CPU的使用。这可能涉及选择下一个要执行的应用程序或者内核任务。如果系统中有其他高优先级的任务等待执行(如实时性要求很高的系统服务),操作系统可能会优先安排这些任务上CPU运行。如果被终止的应用程序还有相关的清理工作需要完成(如保存未完成的数据),操作系统可能会安排一个专门的清理程序来执行这些任务,然后再将CPU使用权分配给其他正常的应用程序。

中断和异常

中断的作用

中断的类型

内中断(也称“异常/例外”)

  1. 陷阱/陷入:
    由陷入指令引发,是应用程序故意引发的

  2. 故障:
    由错误条件引起的,可能被内核程序修复。内核程序修复故障后会把CPU使用权还给应用程序,让它继续执行下去。如:缺页故障(当cpu执行进程的某个页面时,发现他要访问的页(虚拟地址的页)没有在物理内存中,而导致的中断(页错误)。 (一个可执行文件可能很大,放在磁盘上,由局部性原理一次只将其中一部分读进内存))

  3. 终止:
    由致命错误引起,内核程序无法修复该错误,因此一般不再将CPU使用权还给引发终止的应用程序,而是直接终止该应用程序。如:整数除0(程序本身bug)、非法使用特权指令

    与当前执行的指令有关,中断信号来源于CPU内部。CPU在执行指令时会检查是否有异常发生

  4. 尝试在用户态下执行特权指令

  5. 被除数为0

  6. 有时候应用程序想请求操作系统内核的服务,此时会执行一条特殊(不是特权)的指令–陷入指令,该指令会引发一个内部中断信号

执行“陷入指令”,意味着应用程序主动地将CPU控制权还给操作系统内核。“系统调用”就是通过陷入指令完成的。

外中断(也称“中断”)

与当前执行的指令无关,中断信号来源于CPU外部。每个指令周期末尾,CPU都会检查是否有外中断信号需要处理

  1. 时钟中断,由时钟部件发来的中断信号

    • 时钟中断的基本概念

    • 时钟中断是由计算机系统中的硬件定时器产生的一种中断信号。定时器以固定的频率(例如,每秒产生100次中断,对应的时间间隔为10毫秒)发出中断请求。当定时器发出中断时,CPU会暂停当前正在执行的任务,转而执行与时钟中断相关的处理程序。

    • 这个处理程序通常位于操作系统内核中,主要用于更新系统时间、统计进程的执行时间、进行进程调度等操作。例如,在一个分时操作系统中,时钟中断是实现时间片轮转调度算法的关键机制。

    • 并发的概念

    • 并发是指多个任务在宏观上看起来是同时执行的情况。实际上,在单CPU系统中,这些任务是通过分时复用CPU资源来实现“同时”执行的。例如,操作系统中有多个进程(如一个文本编辑进程和一个音乐播放进程),它们看起来是同时运行的,但在微观层面,CPU在每个瞬间只能执行一个进程的指令。

    • 时钟中断在并发中的作用

    • 进程调度基础

      • 在多进程并发环境下,时钟中断是实现进程调度的重要触发机制。假设系统中有进程A和进程B,每个进程都被分配了一个时间片来使用CPU。当定时器产生时钟中断时,操作系统内核会检查当前正在执行的进程(例如进程A)的时间片是否用完。如果用完了,内核就会暂停进程A的执行,保存其上下文(包括程序计数器、寄存器内容等),然后选择另一个进程(如进程B)来执行。通过这种方式,多个进程能够轮流使用CPU,实现并发执行。
    • 公平性保障

      • 时钟中断有助于保证各个并发进程能够公平地获得CPU资源。以时间片轮转调度为例,每个进程都有机会在固定的时间间隔内使用CPU。如果没有时钟中断,一个进程可能会一直占用CPU,导致其他进程无法执行。时钟中断的固定频率就像一个节拍器,确保每个进程都能在一定的节奏下得到CPU的服务。
    • 系统响应性提升

      • 对于具有不同优先级的并发进程,时钟中断也起到重要作用。例如,在一个实时操作系统中,高优先级的进程需要及时响应外部事件。当一个高优先级进程等待执行,而当前低优先级进程正在占用CPU时,时钟中断可以使操作系统内核有机会检查高优先级进程的等待时间是否超过了允许的限度。如果超过了,内核可以暂停低优先级进程,优先执行高优先级进程,从而提高系统对紧急事件的响应能力。
    • 资源统计与管理

      • 时钟中断还用于统计并发进程的资源使用情况。在每次时钟中断发生时,操作系统可以记录各个进程的执行时间、I/O操作次数等信息。这些信息对于资源管理非常重要,例如,可以根据进程的资源使用情况来调整它们的优先级,或者决定是否需要为某个进程分配更多的内存或其他资源。同时,通过对资源使用情况的统计,操作系统可以更好地优化系统性能,避免某个进程过度占用资源而影响其他进程的正常运行。
  2. I/O中断:由输入/输出设备发来的中断信号

    当输入/输出任务完成后,向CPU发送中断信号

中断机制的基本原理

不同的中断信号,需要不同的中断处理程序来处理。当CPU检测到中断信号后,会根据中断信号的类型去查询“中断向量表”,以此来找到相应的中断处理程序在内存中的存放位置。
中断向量表:中断信号类型->中断处理程序指针
显然,中断处理程序一定是内核程序,需要运行在“内核态”

系统调用

什么是系统调用

“系统调用”是操作系统提供给应用程序(程序员/编程人员)使用的接口,可以理解为一种可供应用程序调用的特殊函数,应用程序可以通过系统调用来请求获得操作系统内核的服务(系统调用引发系统中断)

系统调用与库函数的区别

小例子:为什么系统调用是必须的

  1. 文件操作方面

    • 读取文件内容
      • 假设你正在编写一个简单的文本编辑器应用程序。当用户在这个编辑器中打开一个文件并希望查看文件内容时,应用程序(运行在用户态)本身没有权限直接访问磁盘上的文件。这是因为直接的硬件访问是一种特权操作,防止用户程序由于错误或恶意目的而破坏文件系统或其他用户的数据。
      • 应用程序需要通过系统调用(如在Linux中使用read系统调用)来请求操作系统内核(运行在内核态)执行文件读取操作。操作系统内核拥有管理文件系统和硬件设备的权限,它可以找到文件在磁盘上的存储位置,将文件内容从磁盘读取到内存缓冲区,然后将这些数据返回给应用程序。如果没有系统调用,应用程序将无法访问和读取文件内容,文本编辑器也就无法正常工作。
    • 写入文件内容
      • 当用户在文本编辑器中修改了文件内容并希望保存时,应用程序同样需要通过系统调用(如write系统调用)来请求操作系统将内存中的数据写入磁盘文件。操作系统会处理诸如更新文件的元数据(如文件大小、修改时间等)、确保数据的一致性(通过缓冲、同步等机制)以及处理磁盘I/O错误等复杂任务。如果没有系统调用,用户程序可能会错误地写入文件,导致数据丢失或文件系统损坏。
  2. 进程管理方面

    • 创建新进程
      • 考虑一个多任务操作系统环境下的软件,例如一个图形处理软件,它可能需要启动一个子进程来执行一些额外的任务,如进行图像格式转换。应用程序(运行在用户态)本身无法直接创建新的进程,因为进程的创建涉及到系统资源的分配(如内存空间、进程控制块等)和复杂的内核操作。
      • 通过系统调用(如在Linux中的fork系统调用),应用程序可以请求操作系统内核创建一个新的进程。操作系统内核会为新进程分配必要的资源,设置进程的初始状态,包括程序计数器、寄存器内容等,然后将新进程加入到进程调度队列中。没有系统调用,应用程序就无法创建新的进程,复杂的多任务协作功能将无法实现。
    • 终止进程
      • 当一个应用程序完成了它的任务或者遇到了不可恢复的错误时,它需要安全地终止自己。但是简单地停止程序执行可能会导致资源泄漏等问题。通过系统调用(如exit系统调用),应用程序可以请求操作系统内核来回收它所占用的所有资源,包括释放内存、关闭打开的文件和网络连接等。这可以确保系统的稳定性和资源的有效利用,若没有系统调用,进程终止过程可能会混乱,影响整个系统的正常运行。
  3. 内存管理方面

    • 动态内存分配
      • 编写一个动态数据结构相关的程序,例如一个链表程序。当需要向链表中添加新节点时,可能需要动态地分配内存来存储节点数据。应用程序(运行在用户态)不能直接访问和分配物理内存,因为这需要对内存管理单元(MMU)和物理内存进行精细的控制,以避免不同应用程序之间的内存冲突和系统崩溃。
      • 通过系统调用(如在C语言中的malloc函数,其底层是通过系统调用实现的),应用程序可以请求操作系统内核为其分配一块合适大小的内存。操作系统内核会根据当前的内存使用情况,在内存池中找到一块空闲的内存区域,将其分配给应用程序,并更新内存管理的数据结构。如果没有系统调用,应用程序很难安全有效地获取和使用内存,可能会导致内存访问错误或系统内存管理混乱。

系统调用按功能分类

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

  1. 设备管理
    完成设备的 请求/释放/启动 等功能
  2. 文件管理
    完成文件的 读/写/创建/删除 等功能
  3. 进程控制
    完成进程的 创建/撤销/阻塞/唤醒 等功能
  4. 进程通信
    完成进程之间的 消息传递/信号传递 等功能
  5. 内存管理
    完成内存的 分配/回收 等功能

Linxu系统调用例子

  1. 进程管理相关系统调用

    • fork()
    • 功能:用于创建一个新的进程。新进程(子进程)是调用进程(父进程)的副本,它们几乎共享相同的内存空间(代码段、数据段等),但有独立的进程地址空间。子进程会从fork调用返回的位置开始执行,在父进程中,fork返回子进程的进程ID,而在子进程中,fork返回0。
    • 应用场景:在多进程程序中,比如服务器软件创建子进程来处理客户端请求,或者在命令行中执行一个程序并希望它在后台运行时,会使用fork系统调用。
    • exec()系列(如execve、execl等)
      • 功能:用于在当前进程的上下文中加载并执行一个新的程序。exec系统调用会替换当前进程的地址空间内容,包括代码段、数据段和堆栈段等,以新程序的内容来运行。不同的exec函数变体主要是参数传递方式的不同。
      • 应用场景:在需要启动其他程序来完成特定任务的场景中使用。例如,在一个脚本语言解释器中,当执行外部命令时,可能会使用exec系统调用来加载并运行相应的二进制程序。
    • wait()和waitpid()
    • 功能:用于使父进程暂停执行,等待子进程结束。wait会阻塞父进程,直到任意一个子进程结束,然后返回子进程的状态信息。waitpid则可以更灵活地指定等待特定的子进程结束,并且可以设置一些选项,如非阻塞等待等。
    • 应用场景:在父进程需要获取子进程的执行结果或者确保子进程正确退出的情况下使用。例如,在一个进程创建子进程来执行计算任务后,父进程可以使用waitwaitpid来获取子进程的计算结果并进行后续处理。
  2. 文件系统相关系统调用

    • open()
      • 功能:用于打开一个文件或设备文件。它返回一个文件描述符,用于后续对文件进行读写操作等。可以指定打开文件的模式,如只读(O_RDONLY)、只写(O_WRONLY)、读写(O_RDWR),以及一些其他的选项,如创建新文件(O_CREAT)、追加模式(O_APPEND)等。
      • 应用场景:在任何需要读取或写入文件的程序中都会用到。例如,在一个文本编辑器中,打开文件进行编辑时会使用open系统调用。
    • read()和write()
      • 功能:read用于从打开的文件描述符中读取数据到指定的缓冲区,write用于将缓冲区中的数据写入到打开的文件描述符中。它们可以用于各种文件类型,包括普通文件、设备文件等。
      • 应用场景:广泛应用于文件读写操作。例如,在一个文件复制程序中,通过read从源文件读取数据,然后通过write将数据写入目标文件。
    • close()
      • 功能:用于关闭一个已经打开的文件描述符。当文件描述符关闭后,与之相关的内核资源会被释放,并且文件描述符可以被重新分配给其他文件。
      • 应用场景:在文件操作结束后,为了释放资源和避免文件描述符泄漏,需要使用close系统调用。例如,在一个网络服务器程序中,当处理完一个客户端的文件上传或下载请求后,会关闭相关的文件描述符。
  3. 内存管理相关系统调用

    • mmap()和munmap()
      • 功能:mmap用于将文件或者设备的内容映射到进程的虚拟地址空间,这样进程可以像访问内存一样访问文件内容。munmap则用于解除这种映射关系。mmap可以提高文件访问的效率,并且在一些共享内存的场景中非常有用。
      • 应用场景:在需要高效地访问文件内容或者实现进程间共享内存的场景中使用。例如,在一个数据库管理系统中,可能会使用mmap将数据库文件映射到内存中,以加快数据访问速度。
    • brk()和sbrk()
      • 功能:用于调整进程的堆空间大小。brk通过指定一个新的堆末尾地址来改变堆大小,sbrk则是通过指定一个增量来改变堆大小。
      • 应用场景:在动态内存分配库(如mallocfree)的底层实现中可能会用到这些系统调用。当程序需要动态地增加或减少内存使用量时,会间接使用这些系统调用。
  4. 信号处理相关系统调用

    • signal()和sigaction()
      • 功能:signal用于设置信号处理函数,当指定的信号(如SIGINT中断信号、SIGTERM终止信号等)被接收到时,执行相应的处理函数。sigaction是一个更复杂、功能更强大的信号处理系统调用,它可以设置更多关于信号处理的细节,如信号的屏蔽、信号处理的属性等。
      • 应用场景:在需要对系统信号做出响应的程序中使用。例如,在一个后台服务程序中,当接收到SIGTERM信号时,通过信号处理系统调用来执行清理工作并安全地退出程序。

系统调用的过程

  1. 应用程序发起系统调用

    • 调用接口函数
      • 在应用程序中,程序员通过调用特定的函数来发起系统调用。这些函数在不同的编程语言和操作系统中有不同的表现形式。例如,在C语言中,用于文件读写的readwrite函数就是系统调用的接口。当应用程序执行这些函数时,实际上是在请求操作系统内核提供相应的服务。
      • 这些接口函数通常会进行一些必要的参数检查和预处理。例如,对于文件读写系统调用,接口函数会检查文件描述符是否有效、读写缓冲区是否正确分配等。
  2. 触发软中断(软件中断)

    • 指令执行
      • 在许多操作系统(如Linux)中,系统调用是通过软中断机制实现的。当应用程序调用系统调用接口函数后,会执行一条特殊的指令来触发软中断。例如,在传统的Linux系统中,通过执行int 0x80指令(在不同的硬件架构和操作系统版本可能有所不同)来产生软中断。
      • 这个软中断信号会使CPU暂停当前应用程序的执行(此时应用程序处于用户态),并将控制权转移给操作系统内核。
  3. 通过中断向量表找到系统调用处理程序

    • 中断向量表的作用
      • 计算机系统中有一个中断向量表,它存储了各种中断(包括软中断)处理程序的入口地址。当系统调用产生的软中断发生时,CPU根据中断向量表找到对应的系统调用处理程序的入口地址。
      • 例如,在Linux系统中,每个系统调用都有一个对应的系统调用号,这个号码会作为索引或者参数的一部分,帮助CPU在中断向量表或者相关的数据结构中定位到准确的系统调用处理程序。
  4. 系统调用处理程序执行内核服务

    • 内核态操作
      • 一旦找到系统调用处理程序,CPU开始执行这个处理程序。此时,CPU处于内核态,系统调用处理程序可以访问操作系统内核的所有资源,包括硬件设备、内存管理单元等。
      • 以文件读取系统调用为例,系统调用处理程序会根据文件描述符找到对应的文件在内核中的数据结构,检查文件的权限和状态,然后通过磁盘I/O操作从磁盘读取数据到内核缓冲区,再将数据从内核缓冲区复制到应用程序指定的用户缓冲区。
  5. 返回结果给应用程序并恢复执行

    • 结果返回
      • 系统调用处理程序在完成内核服务后,会将结果返回给应用程序。例如,对于文件读取系统调用,返回读取的字节数或者错误码(如果读取过程中出现问题)。这些结果会通过寄存器或者内存中的特定位置传递给应用程序。
      • 同时,操作系统会恢复应用程序的执行环境。这包括恢复应用程序的寄存器内容、程序计数器等,使应用程序能够从系统调用之后的指令继续执行。就好像应用程序在系统调用期间被“暂停”,现在又被“唤醒”,并且得到了它所请求的服务的结果。
Prev:
操作系统(三)操作系统的体系结构
Next:
计算机网络(一)计算机网络体系结构