进程管理
进程是程序在运行中的一次实例,是系统资源分配的基本单位。
进程代表了一个应用层程序的实例化对象,执行一个应用层的自定义程序,每个进程都有自己的地址空间、文件描述符表、内存映射、栈、寄存器等。在程序的执行中,系统将为进程分配 CPU 资源、为程序许诺一个独立而广阔的虚拟内存空间、并单独管理该程序的 IO 文件描述符资源,等等,供程序使用。
进程是一个任务,任务支持并发。并发可以允许操作系统以一定的顺序,先后执行多个不同的任务,并且每个任务仅仅只执行一个较小时间段,例如:40 ms,然后就暂停执行,切换到另一个任务上继续执行。由于这个轮换的时间很短,在人的角度上来看,多个程序仿佛是同时在运行,从而给人一种假象。并发的意义在于可以让多个程序交替执行,从而模拟多任务同时运行。
另外,并发是基于调度的。并发依赖于系统对于每个任务的时间片分配,每个任务都有机会被调度,从而完成特定的任务。进程的调度需要采用特定的规则和精心设计的算法,以保证进程的调度公平。
任务
在 Linux 中,进程使用一个 task_struct 来描述,这也是线程的结构体定义。因为在 linux 中,进程和线程都被抽象为一个任务——可调度对象。进程和线程使用一些标志位和变量来加以识别和区分,除此之外,进程结构体往往作为程序的主线程进行调用,线程结构体通过指针与主线程的结构体共享一些变量。
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 函数的形式存在。具体参考系统调用
进程加载
当一个程序被执行时:
- 将程序的 ELF 文件(Executable and Linkable Format)内容加载到内存中
- 分配到进程的虚拟地址空间中的相应位置(位置分配由编译器决定)
- 操作系统为进程创建页表,维护虚拟内存到物理内存的映射逻辑
进程树和进程组
在 linux 中,所有的进程都是通过其他进程 fork 而来的,由此形成了进程之间的父子派生关系。每个进程都必须要有父进程,即使你看到一个进程是完全 detached 的,其实它也会默认找 1 号进程作为父进程。使用 fork 调用可以创建子进程,子进程默认和父进程位于同一个进程组。
进程在 VFS 中的虚拟文件
每个进程在 /proc/[pid]/
下有一组虚拟文件:
文件名 | 含义 |
---|---|
/proc/[pid]/status | 进程状态和信息 |
/proc/[pid]/cmdline | 启动命令 |
/proc/[pid]/fd/ | 打开的文件描述符 |
/proc/[pid]/maps | 内存映射信息 |