OSTEP第1期: 进程
前言
本文是 OSTEP 学习笔记 的第0期,主要是介绍一下进程 Process的概念。
本系列文章列表:
什么是进程
进程(Process) 简单来说,就是一个运行中程序的实例。 它是操作系统分配资源的基本单位, 也是程序执行的基本单位。
进程实际上是操作系统对于程序运行的抽象。 它包含了程序的代码、数据、状态信息等, 并且拥有自己的地址空间、文件描述符等资源。
地址空间 Address Space
每个进程都有自己的地址空间(Address Space),他是一片内存地址的集合。 地址空间通常包括以下几个部分:
- 代码段(Text Segment):存放程序的机器指令。
- 数据段(Data Segment):存放已初始化的全局变量和静态变量。
- 栈(Stack Segment):存放函数调用时的局部变量和返回地址。
- 堆(Heap Segment):用于动态内存分配。
进程状态
在进程的执行生命周期中,进程会经历一系列离散的状态。 这些状态就被称为进程状态(Process State),他是对进程当前时刻行为的描述。
常见的进程状态包括:
- 新建(New/Initial):这个进程刚刚被创建(在真实系统中,我们往往不会看到这个状态)。
- 运行(Running):进程正在使用CPU执行指令。
- 阻塞(Waiting/Blocked):进程等待某个事件(如I/O操作完成)发生。
- 就绪(Ready):进程已经准备好运行,但还没有被调度器选中。
- 终止(Terminated/Final/Zombie):进程已经完成但还没有被操作系统清理。
进程状态转换/生命周期
- 新建到就绪:当进程被创建后,进入就绪状态。
- 就绪到运行:调度器选择一个就绪进程运行。
- 运行到阻塞:进程等待某个事件(如I/O操作)时进入阻塞状态。
- 阻塞到就绪:等待的事件发生后,进程进入就绪状态。
- 运行到就绪:进程被抢占或自愿放弃CPU时进入就绪状态。
- 运行到终止:进程完成执行后进入终止状态。
进程控制块 Process Control Block (PCB)
为了管理进程,操作系统为每个进程维护 一个进程控制块(Process Control Block, PCB), 也称为进程描述符(Process Descriptor)。
PCB 通常包含以下信息:
- 进程标识符(Process ID, PID):唯一标识进程的数字。
- 进程状态(Process State):如运行、就绪、阻塞等。
- 程序计数器(Program Counter, PC):指向下一条要执行的指令。
- 寄存器上下文(Register Context):(在切换离开该进程前)对当前寄存器内容的一个快照(snapshot)。
- 调度信息(Scheduling Information): 如进程优先级,调度队列的指针。
- 凭据(Credentials):决定了进程能访问的资源。
- 内存管理信息(Memory Management Information):分配给进程的内存区域相关信息。
- 统计信息(Accounting Information):CPU使用时间统计,时间限制。
- 父进程指针,子进程列表,资源列表等。
进程表 Process Table
操作系统维护一个进程表(Process Table), 它是一个包含所有PCB的数组或链表。 里面维护了所有当前进程的PID和PCB的映射关系。
同时,操作系统维护了几个列表结构 如就绪列表(Ready List)和阻塞列表(Blocked List), 用于存储处于不同状态的进程。
查看进程信息 (ps/pstree)
在类Unix系统中,可以使用 ps
命令查看当前系统中的进程信息。
$ ps [options]
PID TTY TIME CMD
20940 pts/4 00:00:00 bash
30812 pts/4 00:00:00 ps
-e: 显示所有进程,包括其他用户的进程。
-f: 全格式显示,包含更多信息,如PPID(父进程ID)、UID
w: 宽输出格式,显示完整的命令行。
f: 显示进程树结构。
也可以使用 pstree
命令以树状结构显示进程的层级关系。
$ pstree
systemd─┬─2*[agetty]
└─cron
对进程的操作 Operations on Processes
操作系统为进程管理提供了一些基本的服务,包括:
- 创建进程
- 终止进程
- 挂起/恢复进程
- 修改进程优先级
- 等待进程(如父进程等待子进程结束)
- 获取进程状态
- 进程间通信(IPC)
创建进程 Create Process
一个进程可以通过系统调用创建另一个进程。 此时,创建新进程的进程称为父进程(Parent Process), 被创建的进程称为子进程(Child Process)(他也可以创建其他子进程,实现进程树)。 当父进程被终止时,操作系统通常会保持子进程继续运行,并允许其独立于父进程存在。
具体流程如下:
- 操作系统为新进程分配一个唯一PID和相应的内存。
- 初始化PCB,保存PID PPID, 设置 PC, SP等寄存器。
- 将新进程添加到进程表和对应队列中。
- 构造其他必要的资源和状态(如文件,内存等)。
- 把新进程置于就绪状态,等待调度。
实际操作方法:
在 Unix 系统中,通常使用 fork()
和exec()
系统调用来创建新进程。
fork()
:通过复制当前进程来创建一个新进程。exec()
:用一个新程序替换当前进程的内存空间。
在 Windows API 中,通常使用 CreateProcess()
函数来创建新进程。
CreateProcess()
函数允许指定新进程的可执行文件、命令行参数、环境变量等,相当于 fork()
和 exec()
的结合。
fork()
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
printf("hello world (pid:%d)\n", (int)getpid());
int rc = fork();
if (rc < 0) { // fork failed and exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
printf("hello, I am child (pid:%d)\n", (int)getpid());
} else {
int wc = wait(NULL); // parent waits for child
printf("hello, I am parent of %d (pid: %d) \n", rc, (int)getpid());
}
return 0;
}
exec()
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
printf("hello world (pid:%d)\n", (int)getpid());
int rc = fork();
if (rc < 0) { // fork failed and exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child process
printf("hello, I am child (pid:%d)\n", (int)getpid());
char *args[3];
args[0] = strdup("wc"); // PROG: WC
args[1] = strdup("proc3.c"); // PARAM: FILE
args[2] = NULL; // END OF ARRAY
execvp(args[0], args);
printf("this shouldn't print out");
} else { // parent process
int wc = wait(NULL);
printf("hello, I am parent of %d (wc: %d) (pid: %d) \n", rc, wc, (int)getpid());
}
return 0;
}