进程管理

1579 字
8 分钟
进程管理

1. 进程的本质与描述#

1.1 进程的基本概念#

  • 通俗定义:

    • 程序的一个执行实例。

    • 正在执行的程序。

  • 内核观点:担当分配系统资源(CPU时间,内存)的实体。

  • 组成公式:

进程 = 内核数据结构对象 + 自己的代码和数据
进程 = PCB (task_struct) + 自己的代码和数据
  • 核心理解:操作系统中每一个正在执行的程序(包括所有命令行工具、用户程序等)都是进程。操作系统对所有被加载到内存中的程序进行管理。

1.2 进程控制块(PCB)与“先描述,再组织”#

  • PCB (Process Control Block):操作系统为每个进程维护的一个数据结构,用于存放该进程的所有属性信息。在Linux中具体实现为 struct task_struct。

  • 管理方式:操作系统将所有进程的PCB通过数据结构(如链表)组织起来。因此,对进程的管理,就转化为对这个PCB链表的增、删、查、改。

  • 代码示例:

// 简化的PCB结构示意
struct task_struct {
// 标识符:唯一标识进程的ID(PID)
// 状态:进程的运行状态(运行、就绪、阻塞等)
// 优先级:进程调度的优先级
// 程序计数器:即将执行的下一条指令地址
// 内存指针:指向代码和数据的指针
// 上下文数据:CPU寄存器的数据快照(用于进程切换)
// I/O状态信息:进程使用的I/O设备和文件列表
// 记账信息:CPU使用时间等统计信息
// 链接指针:用于组织进程链表
struct task_struct *next;
// ... 其他属性
};

2. 进程标识与信息查看#

2.1 getpid()与getppid()#

  • getpid():获取当前进程的进程ID (PID)。

  • getppid():获取当前进程的父进程ID (PPID)。

2.2 进程信息在 /proc 文件系统中的体现#

  • Linux将正在运行的进程信息映射到 /proc/[pid] 目录下。

  • 重要符号链接:

    • cwd:指向进程的当前工作目录。例如 fopen(“d.txt”, “w”) 会在此目录下创建文件。

    • exe:指向进程对应的可执行程序文件的完整路径。

2.3 命令行解释器(Shell)与进程的关系#

  • 用户登录后,操作系统会分配一个命令行解释器进程(如bash)。

  • 用户在Shell中输入命令执行程序时,Shell进程会调用 fork() 创建子进程来运行该命令。

  • 因此,我们执行的大多数命令进程的父进程ID (PPID) 通常就是当前Shell的PID。

3. 进程的创建:fork() #

3.1 fork() 的基本行为#

  • 功能:创建一个新的子进程。

  • 调用方式:pid_t fork(void);

  • 核心特点:调用一次,返回两次。分别在父进程和子进程中各返回一次。

  • 返回值含义:

    • 在父进程中:返回新创建的子进程的 PID(大于0)。

    • 在子进程中:返回 0。

    • 如果创建失败(例如内存不足),则在父进程中返回 -1,并设置 errno。

3.2 为什么fork()会返回两次?#

fork() 的核心功能是创建子进程,包括:

  1. 为子进程申请新的PCB。

  2. 拷贝父进程的PCB信息给子进程。

  3. 将子进程的PCB插入进程链表和调度队列。

    • 当执行到 return 语句时,子进程已经被创建并可能已被调度。因此,fork() 会在两个独立的进程上下文(父、子)中各执行一次返回操作,从而产生两次返回.

3.3 为什么同一个变量 id 能同时满足 id == 0 和 id > 0 的条件,导致 if 和 else 都能执行?#

  • 父进程和子进程拥有各自独立的地址空间。fork() 之后,子进程获得了父进程数据段的副本。

  • 在父进程的地址空间中,id 被赋值为子进程的PID(>0)。

  • 在子进程的地址空间中,id 被赋值为0。

  • 因此,两个进程根据自己地址空间中的 id 值,执行了不同的代码分支。从外部观察,就好像 if 和 else 同时成立。

4. 进程的独立性与写时拷贝#

4.1 进程的独立性#

  • 核心原则:进程是操作系统资源分配的基本单位,进程之间相互独立。一个进程的异常不应影响其他进程(尤其是父进程)的运行。

  • 体现在数据上:子进程创建后,理论上应拥有父进程数据的独立副本。

4.2 写时拷贝#

  • 问题:如果 fork() 时立刻复制父进程的全部数据,会造成巨大的内存和时间开销,而很多情况下子进程会立刻执行 exec() 加载新程序,导致复制的数据被丢弃。

  • 解决方案:写时拷贝技术。

  • 工作原理:

    1. fork() 创建子进程时,并不立即复制父进程的数据(代码段、数据段、堆栈等)。

    2. 内核将父子进程的地址空间映射到相同的物理内存页,并将这些页标记为只读。

    3. 当任何一个进程试图修改某块数据时,会触发一个页错误(Page Fault)。

    4. 此时,操作系统内核才真正地为要修改的进程复制该内存页,并修改其页表映射,使其指向新的副本。然后恢复进程执行,完成写操作。

  • 优势:

    • 延迟拷贝:避免了不必要的复制,大大提升了 fork() 的效率。

    • 节约资源:如果数据不需要修改,则父子进程可以一直共享同一份物理内存。

    • 保障独立:从进程视角看,每个进程都拥有自己独立的数据副本,符合进程独立性的要求。

5. 总结#

  1. 进程是程序的执行实体,由PCB(task_struct)和代码数据组成。操作系统通过管理PCB链表来管理进程。

  2. 进程有唯一的PID,可以通过 /proc/[pid] 查看其详细信息。Shell是特殊的进程,负责创建其他用户进程。

  3. fork() 是创建进程的基本方式,其“调用一次,返回两次”的特性是实现多任务的基础。

  4. 写时拷贝是保证进程数据独立性的关键技术,它在效率和资源利用上达到了很好的平衡。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Profile Image of the Author
HandlerOne
おかえり
公告
本站已整理 138 篇技术笔记,并按 8 条主线排成学习路线(141 站);站内搜索与桌宠对话均已上线。
分类
标签
最新动态
站点统计
文章
138
分类
12
标签
42
总字数
597,044
站点信息
构建平台
腾讯云 · Nginx
博客版本
v6.16.8
文章目录