进程到底是什么?——PCB、PID、PPID 与 fork
操作系统最核心的工作之一就是:
管理。
那么问题来了:
操作系统到底是怎么管理一个运行中的程序的?
Linux 中最核心的概念之一:
进程
一、什么是进程?
课本中通常把进程描述为:
程序的一次执行实例。
但仅仅这样理解还不够。
从 Linux 内核的角度来看:
进程是承担系统资源分配的实体。
也就是说,进程不仅仅是磁盘上的一份代码。
进程
=
task_struct
+
程序代码
+
程序数据
二、程序和进程有什么区别?
比如磁盘上有:
a.out
它是一份程序。
当我们执行:
./a.out
这份程序被加载起来并开始运行之后,才产生一个真正的:
进程。
所以可以简单理解成:
程序:
静态的代码和数据
进程:
程序运行起来之后的实例
同一个程序可以被运行很多次。
那么:
同一个程序
↓
不同的进程
↓
各自拥有自己的运行状态
三、什么是 PCB?
操作系统要管理一个进程,就必须先描述这个进程。
于是就需要一个专门的数据结构:
PCB——Process Control Block,进程控制块。
Linux 中对应的就是:
task_struct
Linux 会用 task_struct 描述进程,而且这个结构体会被加载到内存中。
四、task_struct 里有什么?
task_struct 中保存着很多进程相关的信息。
比如:
进程标识符
进程状态
进程优先级
程序计数器
内存相关信息
上下文数据
I/O信息
记账信息
这其实很好理解。
如果操作系统要管理一个进程,就必须知道:
它是谁?
现在是什么状态?
优先级是多少?
执行到哪里了?
用了哪些资源?
这些信息就保存在 PCB 中。
五、进程是怎么被组织起来的?
只有一个 task_struct 还不够。
一个操作系统里面可能同时运行:
几百个进程
几千个进程
甚至更多
所以操作系统还必须把这些进程组织起来。
Linux 中,运行的进程会以:
task_struct 双链表
的形式组织在内核中。
描述对象:
task_struct
组织对象:
数据结构
管理对象:
操作系统
这就是操作系统“管理”思想的具体体现。
六、如何查看进程?
Linux 给我们提供了非常方便的工具。
例如:
ps
或者:
top
也可以直接查看:
/proc
进程信息可以通过 /proc 文件系统查看,大多数进程信息也可以通过 top、ps 等工具获得。
例如:
/proc/1
就可以查看 PID 为 1 的进程相关信息。
七、什么是 PID?
每个进程都有自己的编号:
PID——Process ID
我们可以通过:
getpid();
获取当前进程的 PID。
还可以通过:
getppid();
获得当前进程的父进程 PID。
例如:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
八、父进程和子进程
Linux 中非常有意思的一个概念就是:
一个进程可以创建另一个进程。
最经典的系统调用:
fork();
就是用来创建进程的。
九、fork 是什么?
最简单的例子:
int ret = fork();
调用 fork() 之后,会出现两个进程:
原来的进程
↓
父进程
fork()
↓
子进程
fork 有两个返回值。
而且:
父子进程共享代码
数据各自拥有自己的空间
采用写时拷贝机制
十、fork 为什么有两个返回值?
这是刚开始学习 fork 时最容易疑惑的地方。
代码:
int ret = fork();
之后:
在子进程中
ret == 0
在父进程中
ret > 0
这个返回值实际上代表:
子进程的 PID。
因此我们通常写成:
if(ret < 0)
{
// fork失败
}
else if(ret == 0)
{
// 子进程
}
else
{
// 父进程
}
十一、一个很重要的认识
调用:
fork();
之后,并不是原来的程序突然“复制了一份文件”。
真正发生的是:
原进程
↓
产生子进程
↓
父子进程都从 fork 之后继续执行
所以:
printf("hello");
放在 fork() 后面,就可能执行两次。
因为:
父进程执行一次
+
子进程执行一次
这也是第一次学习进程时非常容易产生“为什么打印了两遍”的地方。
十二、fork 的核心思想
现在可以把 fork 总结成:
fork()
↓
创建子进程
↓
父子进程继续执行
↓
父子代码相同
↓
数据彼此独立
↓
后续可以分别执行不同逻辑
这也是 Linux 多进程编程的基础。
十三、今天的学习总结
今天我终于开始真正理解:
进程不是一个简单的“正在运行的程序”。
它背后实际上有一整套管理信息:
进程
↓
task_struct
↓
PID
状态
优先级
内存信息
寄存器上下文
I/O信息
...
而 Linux 通过这些信息统一管理整个系统中的进程。
与此同时:
getpid()
getppid()
fork()
也让我第一次真正接触到了:
一个程序如何与操作系统发生联系。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)