操作系统最核心的工作之一就是:

管理。

那么问题来了:

操作系统到底是怎么管理一个运行中的程序的?

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 文件系统查看,大多数进程信息也可以通过 topps 等工具获得。

例如:

/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()

也让我第一次真正接触到了:

一个程序如何与操作系统发生联系。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐