type
status
date
slug
summary
tags
category
icon
password
Property
程序本身是没有生命周期的,只是存在磁盘上面的一些指令(也可能是一些静态数据)。是操作系统让这些字节运行起来,让程序发挥作用。
人们常常希望同时运行多个程序,虽然只有少量的物理CPU可用,但是操作系统如何提供几乎有无数个的CPU可用的假象?
操作系统通过虚拟化CPU 来提供这种假象。通过让一个进程只运行一个时间片,然后切换到其他进程,操作系统提供了存在多个虚拟CPU 的假象。这就是时分共享CPU 技术,允许用户如愿运行多个并发进程。潜在的开销就是性能损失,因为如果CPU 必须共享,每个进程的运行就会慢一点。
- 时分共享(time sharing)是操作系统共享资源所使用的最基本的技术之一。通过允许资源由一个实体使用一小段时间,然后由另一个实体使用一小段时间,如此下去,所谓的资源(例如,CPU 或网络链接)可以被许多人共享。
- 时分共享的自然对应技术是空分共享,资源在空间上被划分给希望使用它的人。例如,磁盘空间自然是一个空分共享资源,因为一旦将块分配给文件,在用户删除文件之前,不可能将它分配给其他文件。
抽象:进程
操作系统为正在运行的程序提供的抽象,就是所谓的进程(process)。在任何时刻,我们都可以清点它在执行过程中访问或影响的系统的不同部分,从而概括一个进程。
为了理解构成进程的是什么,我们必须理解它的机器状态(machine state):程序在运行时可以读取或更新的内容。
- 进程的机器状态有一个明显组成部分,就是它的内存。指令存在内存中。正在运行的程序读取和写入的数据也在内存中。因此进程可以访问的内存(地址空间)是该进程的一部分。
- 进程的机器状态的另一部分是寄存器。许多指令明确地读取或更新寄存器,因此显然,它们对于执行该进程很重要。 注:有一些非常特殊的寄存器构成了该机器状态的一部分,例如,程序计数器(Program Counter,PC)(有时称为指令指针,Instruction Pointer 或IP)告诉我们程序当前正在执行哪个指令;栈指针(stack pointer)和相关的帧指针(frame pointer)用于管理函数参数栈、局部变量和返回地址。
操作系统使用进程列表记录当前的所有进程,进程列表由一系列进程控制块(PCB)组成(可以是数组或者双向链表等),进程的相关属性信息存储在进程控制块中,典型的进程属性信息包括:PID、状态、地址空间、硬件状态等。
- PID:进程的标识符,除了本身的PID以外,还应当记录parent PID
- State:进程状态
- Address space:进程的虚拟内存地址空间,包括主存(用户空间、内核空间)、寄存器(一般寄存器、控制寄存器)、外部设备(文件、socket、其他内存映射IO设备)等
- Hardware state:特指关键的寄存器如PC、SP等
进程API
- 创建(create):操作系统必须包含一些创建新进程的方法。在shell 中键入命令或双击应用程序图标时,会调用操作系统来创建新进程,运行指定的程序。
- 早期的(简单的)操作系统中,加载过程尽早完成,即在运行程序之前全部完成
- 现代操作系统惰性执行该过程,即仅在程序执行期间需要加载的代码或数据片段,才会加载
程序如何转化为进程? 进程创建实际如何进行?
操作系统运行程序必须做的第一件事是将代码和所有静态数据(如初始化变量)加载到内存中,加载到进程的地址空间中。程序最初以某种可执行格式驻留在磁盘上。将程序和静态数据加载到内存中的过程,需要操作系统从磁盘读取这些字节,并将它们放在内存中的某处。
将代码和数据加载到内存后,操作系统在运行此进程之前还需要执行一些其他的操作,必须为程序的运行时栈(run-time stack 或stack)分配一些内存。
c
程序使用栈存放局部变量、函数参数和返回地址。操作系统分配这些内存,并提供给进程。操作系统也有可能会用参数初始化栈。具体来说,它会将参数填入main()
函数,即argc
和argv
数组。操作系统也可能为程序的堆分配一些内存。在
c
程序中,堆用于显示请求的动态分配数据,通过malloc/free
函数申请给数据结构(链表、散列表、树等)的内存。操作系统还将执行一些其他初始化任务,特别是输入输出相关的。例如,Unix系统中,默认情况下每个进程都有三个打开的文件描述符,用于标准输入、输出和错误。
以上任务完成后,启动程序,在入口处运行,即
main()
。通过跳转到main()
例程,OS将CPU的控制权转移到新创建的进程中,从而开始执行程序。- 销毁(destroy):由于存在创建进程的接口,因此系统还提供了一个强制销毁进程的接口。当然,很多进程会在运行完成后自行退出。但是,如果它们不退出,用户可能希望终止它们,因此停止失控进程的接口非常有用。
- 等待(wait):有时等待进程停止运行是有用的,因此经常提供某种等待接口。
- 其他控制(miscellaneous control):除了杀死或等待进程外,有时还可能有其他控制。例如,大多数操作系统提供某种方法来暂停进程(停止运行一段时间),然后恢复(继续运行)。
- 状态(statu):通常也有一些接口可以获得有关进程的状态信息,例如运行了多长时间,或者处于什么状态。
unix
中关于进程的API有:fork()
:复制出与当前进程一样的子进程,当前进程与子进程同时运行(按照调度规则),根据fork
对当前进程与子进程的返回值不一样(父进程中,返回新创建子进程的进程ID;在子进程中,返回0),(按照返回值作为判断依据的分支)执行不同的程序片段直到返回。
wait()
:在使用了fork
已经创建了子进程的情况下, 当前进程执行到wait
处时等待子进程返回后才接着运行。
exec()
:使用exec
后,新的进程将直接替代当前进程(而不是创建子进程的并结束当前进程),新进程覆盖原来的内存空间(例如数据段、代码段、栈等)并接着运行直到返回。exec()
有几种变体:execl()
、execle()
、execlp()
、execv()
和execvp()
kill()
:向进程发送一个信号(包括要求进程睡眠、终止或其他指令)。
进程状态
进程可以处于以下三种状态之一:
- 运行(running):在运行状态下,进程正在处理器上运行。这意味着它正在执行指令。
- 就绪(ready):在就绪状态下,进程已准备好运行,但由于某种原因,操作系统选择不在此时运行。
- 阻塞(blocked):在阻塞状态下,一个进程执行了某种操作,直到发生其他事件时才会准备运行。一个常见的例子是,当进程向磁盘发起
I/O
请求时,它会被阻塞,因此其他进程可以使用处理器。
可以根据操作系统的载量,让进程在就绪状态和运行状态之间转换。从就绪到运行意味着该进程已经被调度(scheduled)。从运行转移到就绪意味着该进程已经取消调度(descheduled)。一旦进程被阻塞(例如,通过发起I/O 操作),OS 将保持进程的这种状态,直到发生某种事件(如,
I/O
完成)。此时,进程再次转入就绪状态(也可能立即再次运行,如果操作系统这样决定)。Process0
发起I/O
并被阻塞,等待I/O
完成。如,当从磁盘读取数据或等待网络数据包时,进程会被阻塞。OS
发现Process0
不使用CPU 并开始运行Process1
。当Process1
运行时,I/O
完成,将Process0
移回就绪状态。最后,Process1
结束,Process0
运行,然后完成注:操作系统必须做出许多决定。首先,系统必须决定在
Process0
发出I/O
时运行Process
1。这样做可以通过保持CPU 繁忙来提高资源利用率。其次,当I/O
完成,系统决定不切换回Process0
。目前还不清楚这是不是一个很好的决定,这些类型的决策由操作系统调度程序完成。数据结构
操作系统是一个程序,和其他程序一样,它有一些关键的数据结构来跟踪各种相关的信息。例如,为了跟踪每个进程的状态,操作系统可能会为所有就绪的进程保留某种进程列表(process list),以及跟踪当前正在运行的进程的一些附加信息。操作系统还必须以某种方式跟踪被阻塞的进程。当
I/O
事件完成时,操作系统应确保唤醒正确的进程,让它准备好再次运行。