spice and wolfspice and wolf Be the One you wanna Be

CPU虚拟化

  • os
  • 2023年 5月 9日
  • (0)

进程

进程是操作系统提供的基本抽象,进程的非正式定义非常简单:进程就是运行中的程序。为了能让数个程序同时运行,操作系统给我们提供了一种假象,即每个进程都能独立地使用CPU,这等同于一个事实,操作系统将一个CPU虚拟化为了多个CPU供多个进程使用。而为了实现CPU虚拟化,操作系统实现了一种机制,即时分共享。时分共享技术将CPU的时间切分为很小的时间片,通过在一段时间内将时间片分给不同的进程使用来达到多个程序同时运行的假象。

抽象

进程是运行中的程序,如何在操作系统中对动态程序状态进行表示,是体现进程概念的关键。它由两部分组成,一部分为内存,进程运行时的初始值和中间变量保存在内存中;另一个组成部分为寄存器,跟进程运行本身有关的变量放在寄存器中。

进程创建
  1. 加载静态代码。操作系统将代码和所有静态数据从磁盘读取,并加载到内存中,加载到进程的地址空间中。
  2. 初始化。在操作系统运行进程之前还需要做一些初始化操作:
    • 初始化栈。操作系统为程序的运行时栈分配内存。程序使用栈存放局部变量、函数参数和返回地址。
    • 初始化堆。操作系统为程序的堆分配内存。堆用于显式请求的动态分配数据。
    • 其他初始化。如在I/O时就需要特定的初始化操作。
  3. 启动程序,运行main()函数。通过跳转到main()例程,OS将CPU的控制权转移到新创建的进程中,从而程序开始执行。
进程状态

进程有三个常用状态,当创建后,进程会在三个状态之间转换。

  • 运行。在运行状态下,进程正在处理器上运行。这意味着它正在执行指令。
  • 就绪。在就绪状态下,进程已经准备好运行,但由于某种原因,操作系统选择不在此时运行。
  • 阻塞。在阻塞状态下,一个进程执行了某种操作,知道发生其他事件时才会准备运行。一个常见的例子是,当进程向磁盘发起I/O请求时,它会被阻塞,因此其他进程可以使用处理器。
I/O请求进程状态转换

虚拟化

操作系统通过进程间的时分共享实现了CPU的虚拟化,但是要实现用户易用的虚拟化还需要解决一些挑战。第一个是性能挑战,如何才能在不降低性能的基础上实现虚拟化?第二个是控制权的挑战,如何才能在虚拟化的同时,让操作系统保持CPU的控制权,让进程间的切换更能符合用户的需求呢?所以在虚拟化之后,保持控制权的同时获取高性能,是操作系统设计的一个主要目的之一。

直接执行

直接执行是操作系统运行程序的一种最直接的方式流程为,当OS希望启动程序运行时,它会在进程列表中为其创建一个进程条目,为其分配一些内存,将程序代码从磁盘加载到内存中,找到入口点(main()函数或类似的),跳转到那里,并开始运行用户的代码。下图展示了这种直接执行协议的执行过程。

但是以上机制面临两个问题:第一个问题是操作系统如何对进程进行限制,让它不能随意进行破坏性操作,比如不能对操作系统内存和磁盘地址进行操作;第二个问题是如何进行进程间的切换。

问题1:受限操作

一个进程必须能够执行I/O操作和一些受限制的操作,但是又不能让其完全控制系统。如何实现这种效果?

硬件通过提供不同的执行模式来协助操作系统。在用户模式下,应用不能完全访问硬件资源。在内核模式下,操作系统可以访问机器的全部资源。

当用户态的进程需要执行特权操作时,它会发起一个系统调用,大多数操作系统都提供几百个系统调用接口。

要执行系统调用,程序必须执行特殊的陷阱指令。该指令同时跳入内核并将特权级别提升到内核模式。一旦进入内核态,系统就能执行任何需要的特权操作,从而为调用进程执行所需的工作。完成后,操作系统调用一个特殊的从陷阱返回指令,该指令返回到发起调用的用户程序中,同时将特权级别降低为用户模式。

陷阱如何知道在OS内运行那些代码?先让用户程序不能自己制定运行的内核代码,因此内核通过在启动时设置陷阱表来告诉硬件在处理程序时需要执行代码的位置,这样在执行陷阱指令后就知道需要从哪运行内核代码。

我们假设每个进程都有一个内核栈,在进入内核和离开内核时,寄存器分别被保存和恢复。

受限直接执行协议有两个阶段。第一个阶段内核会初始化陷阱表,CPU会记住陷阱表以供后续使用。内核通过特权指令来执行此操作。第二个阶段,内核会先做一些前置操作,如在进程列表中分配一个节点,分配内存,然后才会执行程序代码,这会将CPU切换到用户模式并开始运行该进程。当进程希望发出系统调用时,它会重新陷入操作系统,然后再次通过从陷阱返回,将控制权还给进程。该进程然后完成他的工作,并从main()返回。这通常会返回到一些根存代码,他将正确退出该程序。此时,OS清理干净,任务完成了。

问题2:进程切换

协作方式:等待系统调用

过去的系统使用的一种方式称为协作方式。协作方式一个大前提是它假定应用程序会主动放弃CPU的控制权,在这个假设下,我们假定当前系统只有一个可用CPU,可知系统在同一时间要么只能运行操作系统要么只能运行一个进程(操作系统作为系统资源的管理者,在进程之上,相同点在于操作系统和进程的运行都需要CPU),当一个进程在运行时,操作系统就没有在运行,如何切换到操作系统中让它执行它该做后续任务呢?一般需要当前进程主动通过系统调用(yield)让出CPU的使用权,才能让操作系统执行后续操作。这一切的大前提是当前进程会主动让出CPU的使用权,但问题是如果当前进程永远不让出使用权,则操作系统则永远都无法使用CPU。

非协作方式:操作系统进程控制

为了解决协作方式带来的使用权丢失问题,现代操作系统采用非协作方式。它利用了时钟中断机制,当每次时钟中断时,会停止当前正在运行的程序,并运行操作系统预先配置的中断处理程序,这时操作系统会重新获得CPU使用权。而中断处理程序是在系统启动时指定的,在系统启动时操作系统会通知硬件在中断时需要执行那些代码,这些代码即中断处理程序。

保存和恢复上下文

进程切换方式有两种,一种是协作方式,一种是非协作方式,前者是让进程主动调用系统调用实现进行切换,而后者则是利用时钟中断的强制切换,无论是那种切换方式,在切换到另一个进程时都需要执行上下文切换。上下文切换就是要为当前正在执行的进程保存一些寄存器的值(例如,到它的内核栈中),并未即将执行的寄存器恢复一些寄存器值(例如,到内核栈)。这里的上下文,具体来讲,包括通用寄存器、程序计数器和当前内核栈的内容。

下图展示了上下文切换的整个过程:

受限直接执行协议(时钟中断)

此协议中,有两种类型的寄存器保存/恢复。第一种是发生时钟中断的时候。这种情况下,运行进程的用户寄存器由硬件隐式保存,使用该进程的内核栈。第二种情况是当操作系统决定从A却换到B。在这种情况下,内核寄存器被软件(即OS)明确地保存在该进程的进程结构的内存中。后一个操作让系统从好像刚刚由A陷入内核,变成好像刚刚由B陷入内核。

并发问题

存在以下几个并发问题,处理一个中断时发生另一个中断;系统调用期间发生中断,这时会发生什么?这就涉及到并发问题,这些问题我们将在后续章节中解答。

小结

操作系统通过时分共享对CPU进行了虚拟化,让每个进程看起来像是在独占CPU。然后通过将运行时状态分为用户态和内核态,独立出了操作系统对系统的控制权,让用户进程不能对系统进行无限制的操作,实现了受限直接执行协议,保障了系统的安全性;最后通过非协作方式的进程切换,让操作系统保持对系统的持久控制权。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Press ESC to close