1. 进程的定义
进程 是操作系统中用于执行程序的基本单位。一个进程在执行时拥有自己的地址空间、代码、数据和系统资源。具体包含以下几个部分:
-
程序代码:
- 定义:程序的指令集,是执行的核心部分。
- 存储:代码通常被加载到内存中的只读区域,以防止被修改。
-
数据段:
- 已初始化数据段:
- 定义:存储程序中已初始化的全局变量和静态变量。
- 存储:这部分数据在程序加载时被初始化为指定的值。
- 未初始化数据段(BSS段):
- 定义:存储未初始化的全局变量和静态变量。
- 初始化:在程序开始运行时,操作系统会将这部分数据初始化为零。
- 已初始化数据段:
-
堆:
- 定义:用于动态分配内存的区域,程序可以在运行时请求和释放内存。
- 管理:内存分配和释放由程序的内存管理机制(如
malloc
和free
)负责。 - 碎片化:随着时间的推移,堆可能会出现内存碎片现象,影响内存分配效率。
-
栈:
- 定义:用于存储函数调用的局部变量、函数参数以及返回地址。
- 特点:栈在函数调用时动态增长,函数返回时相应的栈空间被释放。栈的操作遵循后进先出(LIFO)原则。
-
寄存器:
- 定义:保存当前进程的执行状态,包括程序计数器(PC)、累加器、数据寄存器等。
- 作用:寄存器用于存储临时数据,支持高效的计算和数据操作。
-
程序计数器(PC):
- 定义:指示下一条将要执行的指令的内存地址。
- 作用:在每次指令执行后,程序计数器会更新,确保指令按正确的顺序执行。
2. 进程的状态
进程的状态描述了进程在特定时刻的运行情况。每个状态表示了进程的不同执行阶段:
-
就绪状态(Ready):
- 定义:进程已准备好运行,等待 CPU 的调度。
- 特征:进程在就绪队列中,操作系统根据调度算法选择进程。
- 调度算法:可能包括优先级调度、时间片轮转等策略。
-
运行状态(Running):
- 定义:进程正在 CPU 上执行。
- 特征:CPU 执行进程的指令,进程的寄存器状态和程序计数器不断更新。
- 切换:运行中的进程可能因时间片用完或中断被切换至就绪状态。
-
阻塞状态(Blocked):
- 定义:进程因等待某种事件而不能继续执行。
- 特征:进程在等待期间不会占用 CPU,但会在阻塞队列中等待事件发生。
- 恢复:事件发生后,进程会被唤醒,转回就绪状态。
-
终止状态(Terminated):
- 定义:进程执行完毕或被系统终止。
- 特征:进程的所有资源被释放,PCB 被销毁。
- 清理:操作系统进行进程状态清理,包括释放占用的内存和关闭相关的资源。
3. 进程控制块(PCB)
进程控制块(PCB) 是操作系统用于管理进程的核心数据结构。它存储了进程的各种状态信息,确保操作系统可以有效地控制进程的执行。PCB 包含以下信息:
-
进程标识符(PID):
- 定义:唯一标识进程的编号。
- 作用:用来区分系统中的不同进程,便于进程管理和调度。
-
进程状态:
- 定义:记录进程的当前状态(如就绪、运行、阻塞、终止)。
- 作用:帮助操作系统管理进程的调度和状态转换。
-
程序计数器(PC):
- 定义:指示下一条将要执行的指令的地址。
- 作用:在进程切换时保存和恢复指令执行的进度。
-
寄存器状态:
- 定义:保存进程的寄存器内容,包括通用寄存器、程序计数器等。
- 作用:在进程上下文切换时保存和恢复寄存器的状态,以确保进程的正确恢复。
-
内存管理信息:
- 定义:包括页表、段表等内存管理结构。
- 作用:描述进程的虚拟地址空间和物理地址映射,支持进程的内存保护和虚拟内存功能。
-
I/O 状态信息:
- 定义:记录进程使用的 I/O 资源信息,如打开的文件描述符、设备状态等。
- 作用:帮助操作系统管理进程的 I/O 操作和资源分配。
fork()
函数
fork()
是一个用于创建新进程的系统调用,它将当前进程(父进程)复制为一个新进程(子进程)。fork()
函数在 UNIX 和类 UNIX 系统中广泛使用,其基本原理和细节如下:
1. 基本功能
- 调用:
fork()
系统调用创建一个新的进程。 - 返回值:
- 子进程:在子进程中,
fork()
返回 0。 - 父进程:在父进程中,
fork()
返回子进程的进程标识符(PID)。 - 失败:如果
fork()
调用失败,则返回 -1,且设置errno
以指示错误类型。
- 子进程:在子进程中,
2. 进程复制
- 进程控制块(PCB):
fork()
创建的子进程会拥有一个新的 PCB,子进程的 PCB 是父进程 PCB 的复制。 - 内存空间:
- 地址空间:子进程会获得父进程的地址空间的副本,包括代码段、数据段和堆栈段。
- 写时复制(Copy-on-Write):为了提高效率,现代操作系统通常采用写时复制技术。即在
fork()
后,父进程和子进程共享同一物理内存,直到其中一个进程修改数据,系统才会为修改的部分分配新的内存。
3. 文件描述符
- 继承:子进程会继承父进程的文件描述符,包括文件描述符表的副本。
- 影响:父子进程共享的文件描述符对操作会产生影响,例如关闭文件描述符会影响到两个进程的文件操作。
4. 进程属性
- 进程 ID:每个进程都有一个唯一的进程标识符(PID)。
fork()
创建的子进程会获得一个新的 PID,且不同于父进程的 PID。 - 父进程 ID(PPID):子进程的父进程 ID(PPID)是调用
fork()
的进程的 PID。
5. 进程调度
- 调度:创建新进程后,操作系统会将其加入调度队列中,决定何时分配 CPU 时间给新创建的进程。
- 优先级:新进程的优先级通常与父进程相同,但调度策略会影响实际的执行顺序
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid;
// 创建新进程
pid = fork();
if (pid < 0) {
// fork() 失败
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程
printf("This is the child process with PID %d\n", getpid());
} else {
// 父进程
printf("This is the parent process with PID %d\n", getpid());
}
return 0;
}
多任务编程
进程应用多任务编程是通过创建和管理多个进程来同时执行不同的任务。每个进程在系统中独立运行,拥有自己的内存空间和资源。操作系统通过时间片轮转或其他调度策略分配 CPU 时间,确保各个进程都能得到执行机会,从而提高系统的整体效率和响应能力。