进程
进程简介
进程就是运行中的程序。操作系统运行程序必须做的第一件事是将代码和所有静态数据(例如初始化变量)加载(load)到内存中,加载到进程的地址空间中。
操作系统用于维护进程记录的结构就是进程表或进程控制块(Process Control Block,PCB)。
维护的资料信息应当包括寄存器、程序计数器、状态字、栈指针、优先级、进程 ID、信号、创建时间、所耗 CPU 时间、当前持有的各种句柄等。
进程的内存占用
从高地址到低地址依次为
- Stack
- 未分配内存
- Heap
- BSS(未初始化数据)
- Data(初始化数据)
- Text(程序代码)
Linux 可通过size 编译的二进制文件
查看各个块的大小。
内核态和用户态
当处理器执行到syscall
指令时,察觉这是一个系统调用指令,将进行如下操作:
- 设置处理器至内核态。
- 保存当前寄存器(栈指针、程序计数器、通用寄存器)。
- 将栈指针设置指向内核栈地址。
- 将程序计数器设置为一个事先约定的地址上,该地址上存放的是系统调用处理程序的起始地址。
系统调用和过程调用之间的关键区别在于,系统调用将控制转移(跳转)到 OS 中,同时提高硬件特权级别。
用户应用程序以所谓的用户模式(user mode)运行,这意味着硬件限制了应用程序的功能。
例如,以用户模式运行的应用程序通常不能发起对磁盘的 I/O 请求,不能访问任何物理内存页或在网络上发送数据包。
在发起系统调用时 ,通常通过一个称为陷阱(trap)的特殊硬件指令,硬件将控制转移到预先指定的陷阱处理程序(trap handler)(即预先设置的操作系统),并同时将特权级别提升到内核模式(kernel mode)。在内核模式下,操作系统可以完全访问系统的硬件,因此可以执行诸如发起 I/O 请求或为程序提供更多内存等功能。
当操作系统完成请求的服务时,它通过特殊的陷阱返回(return-from-trap)指令将控制权交还给用户,该指令返回到用户模式,同时将控制权交还给应用程序,回到应用离开的地方。
进程的创建过程
- 分配进程控制块。
- 初始化机器寄存器。
- 初始化页表。
- 将程序代码从磁盘读进内存。
- 将处理器状态设置为“用户态”。
- 跳转到程序的起始地址(设置程序计数器)。
进程的 API
- fork() 创建新进程,父进程通过 fork 创建子进程,子进程并不是完全拷贝了父进程。具体来说,虽然它拥有自己的地址空间(即拥有自己的私有内存)、寄存器、程序计数器等。
- wait() 父进程需要等待子进程执行完毕。
- exec() 让子进程执行与父进程不同的程序。
进程调度算法
- 先来先服务
- 最短任务优先
- 最短完成时间优先
- 时间片轮转
- 优先级调度
- 多级反馈队列
进程状态
进程分为 3 种状态:执行、阻塞和就绪,但是只支持四种转换。
- 执行 → 就绪
- 执行 → 阻塞
- 阻塞 → 就绪
- 就绪 → 执行
进程通信方式
- 管道(记名管道和匿名管道)
管道所占的空间既可以是内存,也可以是磁盘。要创建一个管道,一个进程只需调用管道创建的系统调用即可。该系统调用所做的事情就是在某种存储介质上划出一片空间,赋给其中一个进程写的权利,另一个进程读的权利即可。
记名管道:如果要在两个不相关的进程(如两个不同进程里面的进程)之间进行管道通信,则需要使用记名管道。
管道只能一端读,一端写。
- 套接字
套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
- 信号
信号就是一个内核对象,或者说是一个内核数据结构。发送方将该数据结构的内容填好,并指明该信号的目标进程后,发出特定的软件中断。操作系统接收到特定的中断请求后,知道是有进程要发送信号,于是到特定的内核数据结构里查找信号接收方,并进行通知。接到通知的进程则对信号进行相应处理。
使用信号用来传输很小的信息,使用管道或套接字不划算。
- 信号量
信号量实际上就是一个简单整数。一个进程在信号变为 0 或者 1 的情况下推进,并且将信号变为 1 或 0 来防止别的进程推进。当进程完成任务后,则将信号再改变为 0 或 1,从而允许其他进程执行。需要注意的是,信号量不只是一种通信机制,更是一种同步机制。
- 共享内存
共享内存就是两个进程共同拥有同一片内存。对于这片内存中的任何内容,二者均可以访问。要使用共享内存进行通信,一个进程首先需要创建一片内存空间专门作为通信用,而其他进程则将该片内存映射到自己的(虚拟)地址空间。这样,读写自己地址空间中对应共享内存的区域时,就是在和其他进程进行通信。
主要用于共享大量数据。
- 消息队列
消息队列相比管道,它无需固定的读写进程,任何进程都可以读写(当然是有权限的进程)。其次,它可以同时支持多个进程,多个进程可以读写消息队列。即所谓的多对多,而不是管道的点对点。
linux 通过ipcs
命令可以查看消息队列、共享内存、信号量的具体信息
进程切换开销
-
直接开销
- 切换页表全局目录(PGD)
- 切换内核态堆栈
- 切换硬件上下文(进程恢复前,必须装入寄存器的数据统称为硬件上下文)
- 刷新 TLB
- 系统调度器的代码执行
-
间接开销
- CPU 缓存失效导致的进程需要到内存直接访问的 IO 操作变多
参考
《操作系统之哲学原理第 2 版》邹恒明
《操作系统导论》