操作系统的最基本抽象:进程。它很简单地被视为一个正在运行的程序。
操作系统通过虚拟化(virtualizing)CPU 来提供几乎有无数个 CPU 可用的假象。通过让一个进程只运行一个 时间片,然后切换到其他进程,操作系统提供了存在多个虚拟 CPU 的假象。这就是时分共享(time sharing)CPU 技术,允许用户如愿运行多个并发进程。
上下文切换(context switch),让操作系统能够停止运行一个程序,并开始在给定的 CPU 上运行另一个程序。所有现代操作系统都采用了这种分时机制。
操作系统为正在运行的程序提供的抽象,就是所谓的进程(process)。
进程的机器状态(machine state):程序在运行时可以读取或更新的内容。
进程的机器状态组成: 内存:指令存在内存中。正在运行的程序读取和写入的数据也在内存中寄存器:许多指令明确地读取或更新寄存器。它们对于执行该进程很重要。一些非常特殊的寄存器: 程序计数器(Program Counter,PC)(有时称为指令指针,Instruction Pointer 或 IP):告诉我们程序当前正在执行哪个指令;栈指针(stack pointer)和相关的帧指针(frame pointer):用于管理函数参数栈、局部变量和返回地址。程序如何转化为进程?具体来说,操作系统如何启动并运行一个程序?进程创建实际如何进行?
第一件事是将代码和所有静态数据(例如初始化变量)加载(load)到内存中,加载到进程的地址空间中。加载后,运行此进程前,操作系统必须 为程序的**运行时栈(run-time stack 或 stack)**分配一些内存;也可能 为程序的堆(heap)分配一些内存;C程序中,通过malloc()来申请操作系统还将执行一些其他初始化任务,特别是与输入/输出(I/O)相关的任务小结:通过将代码和静态数据加载到内存中,通过创建和初始化栈以及执行与 I/O 设置相关的其他工作,OS 现在(终于)为程序执行搭好了舞台。然后它有最后一项任务:启动程序,在入口处运行,即 main()。通过跳转到main()例程(第 5 章讨论的专门机制),OS 将 CPU的控制权转移到新创建的进程中,从而程序开始执行。
3种状态:
运行(running):在运行状态下,进程正在处理器上运行。这意味着它正在执行指令。就绪(ready):在就绪状态下,进程已准备好运行,但由于某种原因,操作系统选择不在此时运行。阻塞(blocked):在阻塞状态下,一个进程执行了某种操作,直到发生其他事件时才会准备运行。 可以根据操作系统的载量,让进程在就绪状态和运行状态之间转换。从就绪到运行意味着该进程已经被调度(scheduled)。
从运行转移到就绪意味着该进程已经取消调度(descheduled)。
一旦进程被阻塞(例如,通过发起 I/O 操作),OS 将保持进程的这种状态,直到发生某种事件(例如,I/O 完成)。此时,进程再次转入就绪状态(也可能立即再次运行,如果操作系统这样决定)。
系统必须决定在 Process0 发出 I/O 时运行 Process1。这样做可以通过保持 CPU 繁忙来提高资源利用率。
操作系统是一个程序,和其他程序一样,它有一些关键的数据结构来跟踪各种相关的信息。
例如,为了跟踪每个进程的状态,操作系统可能会为所有就绪的进程保留某种进程列表(process list),以及跟踪当前正在运行的进程的一些附加信息。操作系统还必须以某种方式跟踪被阻塞的进程。当 I/O 事件完成时,操作系统应确保唤醒正确的进程,让它准备好再次运行。
除了运行、就绪和阻塞之外,还有其他一些进程可以处于的状态。有时候系统会有一个初始(initial)状态,表示进程在创建时处于的状态。另外,一个进程可以处于已退出但尚未清理的最终(final)状态(在基于 UNIX 的系统中,这称为僵尸状态)。
操作系统充满了我们将在这些讲义中讨论的各种重要数据结构(data structure)。进程列表(process list)是第一个这样的结构。有时候人们会将存储关于进程的信息的个体结构称为进程控制块(Process Control Block,PCB)。