【梳理】简明操作系统原理 第一章 进程(内含文档高清截图)

参考教材:
Operating Systems: Three Easy Pieces
Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau
在线阅读:
http://pages.cs.wisc.edu/~remzi/OSTEP/
University of Wisconsin Madison 教授 Remzi Arpaci-Dusseau 认为课本应该是免费的
————————————————————————————————————————
这是专业必修课《操作系统原理》的复习指引。
在本文的最后附有复习指导的高清截图。需要掌握的概念在文档截图中以蓝色标识,并用可读性更好的字体显示 Linux 命令和代码。代码部分语法高亮。

一 进程

1、进程(process)的定义是:一个运行中的程序(的实例(instance))。

2、一台计算机中一般运行数十个到上百个进程。但CPU的每个核心并不是每时每刻都跑同一个进程,而是在进程之间来回切换。也就是说,在很短的时间内跑其中一个,过后又跑另外一个,一会儿再跑另外一个。但从用户的角度(宏观的角度)看来,所有的进程都同时在运行。这种资源调度的机制普遍存在于操作系统中,称为分时(time sharing)。相应地,space sharing则用于分配内存空间、硬盘空间等资源给不同的文件或进程。

3、上下文切换(context switching)是实现进程间的正确切换的机制。它让CPU运行另外一个进程之前先保存这个进程用到的寄存器和程序计数器中的内容,再开始运行新任务。这个机制能让被暂停的进程再次继续运行时,先前的数据不会出错或丢失。

4、机器状态(machine state)主要包括:
(1)地址空间(address space)。这是分配给一个进程的内存空间。
(2)寄存器(register)。这是CPU的核心部件之一,用于保存程序运行过程中产生的数据。其速度极快,但数量非常少,因为相对而言一个寄存器给芯片增加的成本非常昂贵。
(3)程序计数器(program counter,PC)。这是一个特殊的寄存器,总是指向下一条准备执行的指令。有的CPU中,用IP(instruction pointer,指令指针)表示。
(4)栈指针(stack pointer)和帧指针(frame pointer)。它们用于管理函数的参数、本地变量(局部变量)和返回地址。
(5)I / O信息,包括进程打开的文件及打开方式(以只读或可写的方式等)等。

5、所有的现代操作系统中,都含有进程API。以下几类的进程API:
(1)创建进程。
(2)(强制)销毁进程。主要用于强制结束出错或非法的进程。
(3)等待(暂停)。主要用于等待一个进程暂停或完成。
(4)获取状态。主要用于获取进程的一些信息,例如已运行时间。
(5)杂项控制。比如进程的暂停和继续。

6、创建一个进程时,操作系统负责先将磁盘上的程序代码和全局的变量、常量等读入内存。早期的操作系统中,程序及其数据总是被全部装入内存;而现代的操作系统中,一般不总是把程序的一切信息都装入内存,而是只装载需要立刻用到的部分,其余部分只有在需要使用时才读入内存。
然后,操作系统还需要为进程分配内存空间。一种空间是运行时堆栈(runtime stack)。栈空间用来保存函数参数、局部变量和返回地址。一种空间叫做堆(heap)。堆空间保存动态分配的内存。例如C / C++中可以用malloc和free,或者new和delete来申请或释放新的内存空间。
操作系统还需要完成额外的初始化任务,比如IO相关的工作。例如UNIX系统中,每个进程默认拥有三个文件描述符:stdin、stdout和stderr。
最后,操作系统设定程序入口,例如main()。跳到入口以后,操作系统将CPU的控制权移交给新建的进程,进程开始执行。
总之,大致过程是这样的:
在进程列表中创建相应的条目→分配内存→载入代码和数据→建立参数堆栈→清除寄存器→跳到程序入口→等待程序执行完毕并返回→释放占用的内存→从进程列表中移除相应的条目。

7、进程状态(process state)一般分成三种:
(1)运行(running)。这个状态表示CPU正在执行该进程含有的指令。
(2)就绪(ready)。这个状态的进程可以被执行。但由于某些原因,操作系统暂时选择不执行它。
(3)阻塞(blocked)。这个进程未就绪,即不可以被执行。例如请求了一个磁盘IO进行读写文件的进程,在读写完毕之前会变成阻塞状态。操作系统在一个CPU核心上选择接下来要执行的进程时,只会选择就绪态的进程,而不会选择被阻塞的进程。
有的时候,系统还会给进程设置初始(initial)状态、最终(final)状态(基于UNIX的系统称之为僵尸(zombie)态),分别在进程正在创建时、进程已经退出但还未将其从内存中完全释放时赋予。final状态有时候很有用,因为这个状态允许其它进程(例如父进程)读取返回值,并判断进程是否出错。基于UNIX的系统中,进程返回0代表正常退出。判断完毕后,调用相应的API,等待子进程完成。子进程完成后,父进程就通知操作系统,可以释放该进程占用的相关数据。
操作系统要负责设置每个进程的状态。一般而言,为了追踪每个进程的状态,操作系统都会采用一些数据结构来实现进程的调度。这样的数据结构可以是一个表,称为进程列表(process list),里面存储了就绪的全部进程和一些用于跟踪正在运行的进程的额外信息。当暂停执行一个进程时,它正在使用的寄存器的值要保存下来;继续执行时,要把这些值恢复到相应的寄存器中。具体的机制在后面的章节中再详细讨论。
存储一个进程的信息的数据结构有时也称为进程控制块(process control block,PCB),或者进程描述符(process descriptor)。

8、系统调用fork()可以在编程语言中调用,用于创建调用该命令的进程的副本。子进程与父进程几乎处处相同。父进程调用fork()时,并不会引起无限递归。因为子进程是由当前状态的父进程复制出来的。子进程会从fork()开始执行,且返回0,并执行接下来的语句;父进程的fork()在成功创建子进程后返回子进程的PID(进程标识符)。
fork()位于头文件<unistd.h>中,MSVC编译器无此文件。因为这个命令来自针对类UNIX操作系统的POSIX标准。

9、exit是C++的函数,用于退出程序。

10、系统调用wait()位于<sys/wait.h>,这个头文件也是POSIX标准中的。其作用是:调用后立即阻塞自己,直到当前进程的某个子进程退出。其参数用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL。

11、系统调用exec()及其变种execl()、execle()、execlp()、execv()、execvp()、execvpe(),位于<unistd.h>中。MSVC包含这六个变种及其它若干个变种,但不包含exec()。这里介绍execvp()。其作用是在环境变量中查找指定的程序并执行。程序名由第一个参数给出,而第二个参数要给出执行这个程序的命令行参数。相当于
int main(int argc, char** argv)
中的argv。
执行失败时,该调用返回-1。执行成功时,不会返回值。因为它会用待执行的程序的代码段覆写当前进程的代码段,进程的栈和堆也被重新初始化,然后执行新的指令。
fork()和exec()及其变种配合,使得shell得以实现。shell的本质是类UNIX系统的一个用户程序,用于解释命令、运行其它程序。常见的shell包括bash、tcsh、zsh等。shell在执行命令时,先用fork()生成一个自己的子进程,然后调用exec()或它的某个变体去执行命令,然后调用wait()等待子进程结束后给出相应的提示,等待用户输入下一条指令。这些系统调用通过在子进程中运行新的程序,使得当前进程不受影响。

12、STDOUT_FILENO是一个文件描述符,其定义是:
#define STDOUT_FILENO 1;

STDOUT_FILENO == fileno(stdout);
一般而言,使用中的fprintf、fputs等函数时,多用stdout;而STDOUT_FILENO主要配合系统调用使用。使用系统调用可能更快,但增加了代码难度,而且不便移植(Windows平台没有STDOUT_FILENO的定义)。另外,中的函数是通过系统调用实现的,所以fprintf之类的函数可能会调用write()。

13、除了常见的fork()、exec()、wait()以外,类UNIX系统中还有许多与进程交互的接口。举例:系统调用kill()用于向进程发送信号(作用相似的还有稍微显得更加用户友好的命令行工具killall),例如暂停、结束和其它指令。在许多UNIX Shell中,特定的按键组合用于传递特殊信号:Ctrl+C发送SIGINT(中断)到进程(正常结束);而Ctrl+Z发送SIGTSTP(停止)信号,使进程在执行期间暂停(可以用特定命令在一段时间后继续进程,例如很多shell的内置命令fg)。
整个信号子系统提供了一个功能丰富的基础架构,用于一个或多个向进程发送外部事件。进程通过系统调用signal()来抓取信号,使得在信号传递到进程时进程暂停正常执行,转而执行指定代码。

14、现代操作系统都有用户(user)的概念。一些系统常常被很多人同时使用,一般而言用户只对自己创建的进程有完全控制权,而输入正确密码的用户可以访问系统资源。至于分配CPU、内存、磁盘等资源的任务,则交由操作系统。
一个系统总是需要一个可以无限制访问系统的最高权限用户,基于UNIX的系统称超管为root,也称superuser。你应该像当蜘蛛侠一样当系统管理员:能力越大,责任越大(With great power comes great responsibility.)。为了确保安全性,平时不建议总是以root身份进行操作。如果确实需要,小心使用,因为root实行毁坏性的操作并不受限制。

15、有很多实用的命令行工具。ps显示正在运行的进程;top显示进程的资源占用等情况。也有许多不同的CPU监视工具,比如MenuMeters,可以查看CPU占用率。

16、用户手册(Manual pages,Man pages)记录了UNIX的一系列系统调用或库的使用说明。这些细节充沛的文档在互联网诞生之前就开始出现了。作为系统级程序员,阅读用户手册是必须的成长步骤。使用shell或系统调用时,应该详细阅读它们的文档。如果你向同事们询问一些系统调用的用法时,他们有时只简单回复道:“RTFM.”(Read the fucking manuals.)。如果你总是先阅读用户手册,那么一定程度上可以避免这种尴尬的境地。
【梳理】简明操作系统原理 第一章 进程(内含文档高清截图)
【梳理】简明操作系统原理 第一章 进程(内含文档高清截图)
【梳理】简明操作系统原理 第一章 进程(内含文档高清截图)
【梳理】简明操作系统原理 第一章 进程(内含文档高清截图)
【梳理】简明操作系统原理 第一章 进程(内含文档高清截图)
【梳理】简明操作系统原理 第一章 进程(内含文档高清截图)