目录

进程

进程简介

进程就是运行中的程序。操作系统运行程序必须做的第一件事是将代码和所有静态数据(例如初始化变量)加载(load)到内存中,加载到进程的地址空间中。

操作系统用于维护进程记录的结构就是进程表或进程控制块(Process Control Block,PCB)。

维护的资料信息应当包括寄存器、程序计数器、状态字、栈指针、优先级、进程 ID、信号、创建时间、所耗 CPU 时间、当前持有的各种句柄等。

进程的内存占用

从高地址到低地址依次为

  1. Stack
  2. 未分配内存
  3. Heap
  4. BSS(未初始化数据)
  5. Data(初始化数据)
  6. Text(程序代码)

Linux 可通过size 编译的二进制文件查看各个块的大小。

内核态和用户态

当处理器执行到syscall指令时,察觉这是一个系统调用指令,将进行如下操作:

  1. 设置处理器至内核态。
  2. 保存当前寄存器(栈指针、程序计数器、通用寄存器)。
  3. 将栈指针设置指向内核栈地址。
  4. 将程序计数器设置为一个事先约定的地址上,该地址上存放的是系统调用处理程序的起始地址。

系统调用和过程调用之间的关键区别在于,系统调用将控制转移(跳转)到 OS 中,同时提高硬件特权级别。

用户应用程序以所谓的用户模式(user mode)运行,这意味着硬件限制了应用程序的功能。

例如,以用户模式运行的应用程序通常不能发起对磁盘的 I/O 请求,不能访问任何物理内存页或在网络上发送数据包。

在发起系统调用时 ,通常通过一个称为陷阱(trap)的特殊硬件指令,硬件将控制转移到预先指定的陷阱处理程序(trap handler)(即预先设置的操作系统),并同时将特权级别提升到内核模式(kernel mode)。在内核模式下,操作系统可以完全访问系统的硬件,因此可以执行诸如发起 I/O 请求或为程序提供更多内存等功能。

当操作系统完成请求的服务时,它通过特殊的陷阱返回(return-from-trap)指令将控制权交还给用户,该指令返回到用户模式,同时将控制权交还给应用程序,回到应用离开的地方。

进程的创建过程

  1. 分配进程控制块。
  2. 初始化机器寄存器。
  3. 初始化页表。
  4. 将程序代码从磁盘读进内存。
  5. 将处理器状态设置为“用户态”。
  6. 跳转到程序的起始地址(设置程序计数器)。

进程的 API

  1. fork() 创建新进程,父进程通过 fork 创建子进程,子进程并不是完全拷贝了父进程。具体来说,虽然它拥有自己的地址空间(即拥有自己的私有内存)、寄存器、程序计数器等。
  2. wait() 父进程需要等待子进程执行完毕。
  3. exec() 让子进程执行与父进程不同的程序。

进程调度算法

  1. 先来先服务
  2. 最短任务优先
  3. 最短完成时间优先
  4. 时间片轮转
  5. 优先级调度
  6. 多级反馈队列

进程状态

进程分为 3 种状态:执行、阻塞和就绪,但是只支持四种转换。

  1. 执行 → 就绪
  2. 执行 → 阻塞
  3. 阻塞 → 就绪
  4. 就绪 → 执行

进程通信方式

  1. 管道(记名管道和匿名管道)

管道所占的空间既可以是内存,也可以是磁盘。要创建一个管道,一个进程只需调用管道创建的系统调用即可。该系统调用所做的事情就是在某种存储介质上划出一片空间,赋给其中一个进程写的权利,另一个进程读的权利即可。

记名管道:如果要在两个不相关的进程(如两个不同进程里面的进程)之间进行管道通信,则需要使用记名管道。

管道只能一端读,一端写。

  1. 套接字

套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

  1. 信号

信号就是一个内核对象,或者说是一个内核数据结构。发送方将该数据结构的内容填好,并指明该信号的目标进程后,发出特定的软件中断。操作系统接收到特定的中断请求后,知道是有进程要发送信号,于是到特定的内核数据结构里查找信号接收方,并进行通知。接到通知的进程则对信号进行相应处理。

使用信号用来传输很小的信息,使用管道或套接字不划算。

  1. 信号量

信号量实际上就是一个简单整数。一个进程在信号变为 0 或者 1 的情况下推进,并且将信号变为 1 或 0 来防止别的进程推进。当进程完成任务后,则将信号再改变为 0 或 1,从而允许其他进程执行。需要注意的是,信号量不只是一种通信机制,更是一种同步机制。

  1. 共享内存

共享内存就是两个进程共同拥有同一片内存。对于这片内存中的任何内容,二者均可以访问。要使用共享内存进行通信,一个进程首先需要创建一片内存空间专门作为通信用,而其他进程则将该片内存映射到自己的(虚拟)地址空间。这样,读写自己地址空间中对应共享内存的区域时,就是在和其他进程进行通信。

主要用于共享大量数据。

  1. 消息队列

消息队列相比管道,它无需固定的读写进程,任何进程都可以读写(当然是有权限的进程)。其次,它可以同时支持多个进程,多个进程可以读写消息队列。即所谓的多对多,而不是管道的点对点。

linux 通过ipcs命令可以查看消息队列、共享内存、信号量的具体信息

进程切换开销

  1. 直接开销

    1. 切换页表全局目录(PGD)
    2. 切换内核态堆栈
    3. 切换硬件上下文(进程恢复前,必须装入寄存器的数据统称为硬件上下文)
    4. 刷新 TLB
    5. 系统调度器的代码执行
  2. 间接开销

    1. CPU 缓存失效导致的进程需要到内存直接访问的 IO 操作变多

参考

《操作系统之哲学原理第 2 版》邹恒明

《操作系统导论》