第4章 现代操作系统中的进程管理与生命周期实现
操作系统的核心功能之一是管理计算机系统上运行的程序。通过将程序转变为进程,操作系统可以更有效地控制和分配系统资源,实现多任务处理。本章将深入探讨进程的本质、状态变化以及控制机制,帮助读者全面理解现代操作系统中进程管理的理论与实践。
4.1 进程的基本概念
4.1.1 进程的历史发展
进程概念的出现解决了早期计算机系统中程序执行的重大问题。在单任务操作系统时代,计算机一次只能运行一个程序,资源利用率低下。随着多道程序设计的发展,操作系统需要一种机制来管理并发执行的多个程序,这促使了进程概念的诞生。
早期的计算机系统直接在CPU上运行程序,没有抽象层,导致程序之间相互干扰。进程抽象的引入使得每个程序都运行在自己的虚拟空间中,互不干扰,显著提高了系统的稳定性和安全性。
c
// 早期的简单程序执行方式(无进程概念)
void early_computer_execution() {
load_program_to_memory(); // 将程序加载到内存
set_program_counter(); // 设置程序计数器
// 直接执行,直到程序结束
while (!program_finished) {
execute_next_instruction();
}
// 加载下一个程序
load_next_program();
}
4.1.2 进程定义与进程控制块结构
进程是一个执行中的程序实例,包含程序代码(文本段)、当前活动(程序计数器、处理器寄存器)、堆栈(临时数据)、数据段(全局变量)以及堆(动态分配的内存)。
进程控制块(PCB)是操作系统用来表示进程的数据结构,包含了进程的所有信息。PCB通常包括以下信息:
- 进程标识符(Process ID)
- 进程状态(运行、就绪、阻塞等)
- 程序计数器(下一条将执行的指令地址)
- CPU寄存器
- CPU调度信息(优先级、调度队列指针等)
- 内存管理信息(内存边界、页表等)
- 资源占用信息(打开的文件等)
- 账户信息(CPU使用时间等)
c
// 进程控制块的C语言表示
typedef struct {
int pid; // 进程ID
ProcessState state; // 进程状态(枚举类型)
int program_counter; // 程序计数器
int cpu_registers[16]; // CPU寄存器集合
int priority; // 进程优先级
// 内存管理信息
void* text_segment; // 代码段
void* data_segment; // 数据段
void* stack; // 栈
void* heap; // 堆
// 资源占用信息
int open_files[MAX_FILES];// 打开的文件描述符
// 记账信息
time_t creation_time; // 创建时间
int cpu_time_used; // 已使用的CPU时间
// 调度信息
struct pcb* next; // 链表中的下一个PCB
} ProcessControlBlock;
通过PCB,操作系统能够追踪每个进程的状态和资源使用情况,实现对进程的有效管理和控制。当进程切换时,操作系统保存当前进程的状态到其PCB中,然后加载下一个进程的PCB来恢复其执行环境。
c
// 进程上下文切换
void context_switch(ProcessControlBlock* current, ProcessControlBlock* next) {
// 保存当前进程状态
save_cpu_state(current);
current->program_counter = get_current_pc();
// 加载下一个进程状态
load_cpu_state(next);
set_program_counter(next->program_counter);
// 更新进程状态
current->state = READY;
next->state = RUNNING;
printf("进程切换: 从PID %d 切换到 PID %d\n", current->pid, next->pid);
}
4.2 进程的状态转换
进程在其生命周期中会经历不同的状态,操作系统通过管理这些状态转换来协调多个进程的执行。
4.2.1 双状态进程模型分析
最简单的进程模型只包含两个状态:运行(Running)和非运行(Not Running)。在这个模型中,进程要么正在执行,要么在等待被调度执行。
这种简单模型的优点是易于理解和实现,但它不能精确描述进程的各种等待情况,如等待I/O完成或等待某个事件发生。
c
// 双状态模型中的进程调度
void simple_scheduler() {
ProcessControlBlock* current_process;
while (1) {
// 选择下一个要运行的进程
current_process = select_next_process();
if (current_process == NULL) {
// 没有可运行的进程,系统空闲
idle();
continue;
}
// 运行选中的进程
current_process->state = RUNNING;
run_process(current_process);
// 进程运行结束或时间片用尽
current_process->state = NOT_RUNNING;
// 将进程放回队列(如果未结束)
if (!process_terminated(current_process)) {
enqueue_process(current_process);
}
}
}
4.2.2 进程的创建与终止机制
进程创建
操作系统中的进程可以通过多种方式创建:
- 系统初始化时创建
- 用户请求创建新进程
- 现有进程执行创建进程的系统调用
- 批处理作业初始化
在UNIX/Linux系统中,使用fork()系统调用创建新进程。fork()创建调用进程的副本,两个进程从fork()返回点继续执行。在子进程中,fork()返回0;在父进程中,返回子进程ID。
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid;
// 创建新进程
pid = fork();
if (pid < 0) {
// fork失败
fprintf(stderr, "Fork failed\n");
return 1;
} else if (pid == 0) {
// 子进程代码
printf("子进程: 我的PID是 %d,父进程PID是 %d\n",
getpid(), getppid());
} else {
// 父进程代码
printf("父进程: 我的PID是 %d,子进程PID是 %d\n",
getpid(), pid);
}
return 0;
}
在Windows系统中,使用CreateProcess()函数创建新进程,该函数直接创建一个运行指定程序的新进程。
进程终止
进程可以通过以下方式终止:
- 正常退出(主动)
- 错误退出(主动)
- 严重错误(被动)
- 被其他进程终止(被动)
在UNIX/Linux系统中,进程通过exit()系统调用终止自己,或通过kill()系统调用终止其他进程。
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t pid;
int status;
pid = fork();
if (pid < 0) {
fprintf(stderr, "Fork failed\n");
return 1;
} else if (pid == 0) {
// 子进程代码
printf("子进程(%d)开始执行\n", getpid());
sleep(2);
printf("子进程正常退出\n");
exit(0); // 正常终止
} else {
// 父进程代码
printf("父进程创建了子进程(%d)\n", pid);
// 等待子进程结束
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("子进程正常终止,退出状态: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("子进程被信号 %d 终止\n", WTERMSIG(status));
}
}
return 0;
}
4.2.3 五状态进程模型详解
更复杂且现实的进程模型包含五个状态:
- 新建(New): 进程正在被创建,尚未就绪
- 就绪(Ready): 进程已准备好运行,等待CPU分配
- 运行(Running): 进程正在执行
- 阻塞/等待(Blocked/Waiting): 进程等待某个事件发生
- 终止(Terminated): 进程已结束执行
进程状态转换图展示了这些状态之间的转换关系:
- 新建 → 就绪:进程创建完成后转为就绪状态
- 就绪 → 运行:调度器选择进程执行
- 运行 → 就绪:时间片用尽或更高优先级进程到达
- 运行 → 阻塞:进程等待I/O或其他事件
- 阻塞 → 就绪:等待的事件发生
- 运行 → 终止:进程执行完成或被终止
c
// 五状态模型的进程状态定义
typedef enum {
NEW,
READY,
RUNNING,
BLOCKED,
TERMINATED
} ProcessState;
// 进程状态转换函数
void change_process_state(ProcessControlBlock* process, ProcessState new_state) {
ProcessState old_state = process->state;
process->state = new_state;
printf("进程 %d 状态从 %s 变为 %s\n",
process->pid,
state_to_string(old_state),
state_to_string(new_state));
// 根据新状态执行相应操作
switch (new_state) {
case READY:
// 如果进程变为就绪态,将其加入就绪队列
if (old_state != RUNNING) { // 避免时间片到期的重复入队
add_to_ready_queue(process);
}
break;
case RUNNING:
// 进程开始运行,从就绪队列中移除
remove_from_ready_queue(process);
break;
case BLOCKED:
// 进程阻塞,可能需要加入等待队列
add_to_wait_queue(process);
break;
case TERMINATED:
// 进程终止,释放资源
release_resources(process);
break;
default:
break;
}
}
// 状态枚举转字符串
const char* state_to_string(ProcessState state) {
switch (state) {
case NEW: return "新建";
case READY: return "就绪";
case RUNNING: return "运行";
case BLOCKED: return "阻塞";
case TERMINATED: return "终止";
default: return "未知";
}
}
4.2.4 被挂起进程的处理
在虚拟内存系统中,进程可能会被挂起(Suspended),即暂时从内存中交换到磁盘上,以释放内存空间给其他进程使用。这引入了两个附加状态:
- 就绪挂起(Ready-Suspended): 进程在磁盘上,但一旦回到内存就可以运行
- 阻塞挂起(Blocked-Suspended): 进程在磁盘上且在等待某个事件
这样,进程状态模型扩展为七状态模型:新建、就绪、运行、阻塞、就绪挂起、阻塞挂起和终止。
c
// 七状态模型
typedef enum {
NEW,
READY,
RUNNING,
BLOCKED,
READY_SUSPENDED,
BLOCKED_SUSPENDED,
TERMINATED
} ExtendedProcessState;
// 挂起进程
void suspend_process(ProcessControlBlock* process) {
if (process->state == READY) {
// 就绪进程挂起
change_extended_state(process, READY_SUSPENDED);
// 将进程映像写入磁盘
swap_out_process(process);
printf("就绪进程 %d 被挂起到磁盘\n", process->pid);
}
else if (process->state == BLOCKED) {
// 阻塞进程挂起
change_extended_state(process, BLOCKED_SUSPENDED);
// 将进程映像写入磁盘
swap_out_process(process);
printf("阻塞进程 %d 被挂起到磁盘\n", process->pid);
}
}
// 恢复挂起进程
void resume_process(Proc