Skip to content

进程管理

进程是程序在运行中的一次实例,是系统资源分配的基本单位。

进程代表了一个应用层程序的实例化对象,执行一个应用层的自定义程序,每个进程都有自己的地址空间、文件描述符表、内存映射、栈、寄存器等。在程序的执行中,系统将为进程分配 CPU 资源、为程序许诺一个独立而广阔的虚拟内存空间、并单独管理该程序的 IO 文件描述符资源,等等,供程序使用。

进程是一个任务,任务支持并发。并发可以允许操作系统以一定的顺序,先后执行多个不同的任务,并且每个任务仅仅只执行一个较小时间段,例如:40 ms,然后就暂停执行,切换到另一个任务上继续执行。由于这个轮换的时间很短,在人的角度上来看,多个程序仿佛是同时在运行,从而给人一种假象。并发的意义在于可以让多个程序交替执行,从而模拟多任务同时运行。

另外,并发是基于调度的。并发依赖于系统对于每个任务的时间片分配,每个任务都有机会被调度,从而完成特定的任务。进程的调度需要采用特定的规则和精心设计的算法,以保证进程的调度公平。

任务

在 Linux 中,进程使用一个 task_struct 来描述,这也是线程的结构体定义。因为在 linux 中,进程和线程都被抽象为一个任务——可调度对象。进程和线程使用一些标志位和变量来加以识别和区分,除此之外,进程结构体往往作为程序的主线程进行调用,线程结构体通过指针与主线程的结构体共享一些变量。

c
struct task_struct {
    pid_t pid;
    long state;
    struct mm_struct *mm;
    struct files_struct *files;
    struct task_struct *parent;
    struct list_head children;
    ...
};
属性进程A(主线程)线程B(属于A)
mm(内存空间)共享共享
files(文件表)共享共享
fs(工作目录)共享共享
独立独立
task_struct独立独立

内存模型

进程的内存是操作系统许诺的一个虚假空间,分为两个部分,一部分是内核空间,一部分是用户空间,内核空间预先存放了操作系统为用户提供过的一些数据和信息,用户的代码和数据存放在用户空间,用户可以在自己的空间中执行程序,同时也可以调用内核空间中的函数,从而进入内核态,使用内核提供的 API 执行更加底层的操作,包括:IO 操作,硬件控制,系统控制等等。

内存模型

  • 栈区,为函数调用提供局部变量空间、保存返回地址,每个线程都有自己的栈空间,内存由操作系统自动维护;
  • 堆区,为程序提供动态分配内存(如 malloc, new),内存需要程序员手动管理;

进程的生命周期

进程的生命周期

生命周期可以使用进程相关的系统调用来进行管理,以 C 函数的形式存在。具体参考系统调用

进程加载

当一个程序被执行时:

  1. 将程序的 ELF 文件(Executable and Linkable Format)内容加载到内存中
  2. 分配到进程的虚拟地址空间中的相应位置(位置分配由编译器决定)
  3. 操作系统为进程创建页表,维护虚拟内存到物理内存的映射逻辑

进程树和进程组

在 linux 中,所有的进程都是通过其他进程 fork 而来的,由此形成了进程之间的父子派生关系。每个进程都必须要有父进程,即使你看到一个进程是完全 detached 的,其实它也会默认找 1 号进程作为父进程。使用 fork 调用可以创建子进程,子进程默认和父进程位于同一个进程组。

进程在 VFS 中的虚拟文件

每个进程在 /proc/[pid]/ 下有一组虚拟文件:

文件名含义
/proc/[pid]/status进程状态和信息
/proc/[pid]/cmdline启动命令
/proc/[pid]/fd/打开的文件描述符
/proc/[pid]/maps内存映射信息