欢迎光临
我们一直在努力

Linux进程全面解析:从基础到高级管理(2/3)

  • 认识冯诺依曼系统

  • 操作系统概念与定位,理解 "管理"

  • 深入理解进程概念,了解 PCB

  • 学习进程状态,学会创建进程,掌握僵尸进程和孤儿进程,及其形成原因和危害

  • 了解进程调度,Linux 进程优先级,理解进程竞争性与独立性,理解并行与并发

  • 理解进程切换,以及 Linux2.6 kernel,O (1) 调度算法架构

  • 理解环境变量,熟悉常见环境变量及相关指令getenv/setenv函数

  • 理解 C 内存空间分配规律,了解进程内存映像和应用程序区别,认识虚拟地址空间。

        书接上文:

Linux进程全面解析:从基础到高级管理(1/3)https://blog.csdn.net/syagain_zsx/article/details/161751639?spm=1001.2014.3001.5501


三、进程

3-1 进程基本概念与基本操作

从不同维度可对进程做出精准定义,Linux系统下进程有明确的组成结构:

  • 课本概念:程序的一次执行实例、正在运行的程序。

  • 内核观点:承担系统资源分配的实体,负责占用CPU时间、内存等核心系统资源。

  • Linux实操定义:进程 = 内核数据结构(task_struct) + 自身程序代码与数据。

3-1-2 描述进程——PCB(进程控制块)

操作系统需要统一管理所有进程,因此需要专门的数据结构记录进程所有信息,即PCB。

  • 进程的所有属性信息,都会存储在进程控制块(PCB)中,可简单理解为进程属性的集合。

  • Linux操作系统中,PCB对应的具体数据结构为task_struct,是PCB在Linux下的具体实现。

task_struct 核心特性:

  • 是Linux内核自定义的专用数据结构类型。

  • 系统运行时会被加载到内存(RAM)中,完整保存对应进程的全部信息。

3-1-3 task_struct 核心内容分类

task_struct 结构体包含进程运行的所有核心信息,主要分为以下类别:

  • 标识符:进程唯一标识,用于系统区分不同进程。

  • 状态信息:记录进程任务状态、退出代码、退出信号等核心状态数据。

  • 优先级:定义进程相对于其他进程的执行优先级,影响进程调度顺序。

  • 程序计数器:保存进程下一条待执行指令的内存地址。

  • 内存指针:包含进程代码、数据的内存指针,以及进程间共享内存块的指针。

  • 上下文数据:进程暂停执行时,CPU寄存器中留存的临时数据(进程切换核心依据)。

  • I/O状态信息:记录进程的I/O请求、已分配的I/O设备、进程占用的文件列表等信息。

  • 记账信息:统计进程占用的CPU总时长、时钟数、运行时长限制、账号信息等。

  • 其他信息:包含进程运行所需的其余辅助参数,后续详细讲解。

进程组织方式:

系统中所有正在运行的进程,其对应的 task_struct 结构体,都会以双向链表的形式在内核中统一组织、管理,可在内核源代码中查阅相关定义。

3-1-4 查看进程的方式

Linux系统提供两种主流方式查看进程信息:

  • 通过 /proc 系统文件夹查看:proc是系统虚拟文件目录,实时存储系统进程信息。例如PID为1的进程信息,可通过 /proc/1 文件夹查看。

  • 通过用户级工具查看:常用 top(实时监控进程)、ps(查看静态进程快照)指令获取进程信息。

  • 常驻进程测试代码:

    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    int main()
    {
    while(1){
    sleep(1);
    }
    return 0;
    }

    3-1-5 系统调用获取进程标识符

    Linux提供专属系统调用函数,可获取进程核心标识:

    • PID(进程ID):当前进程的唯一标识,通过 getpid() 获取。

    • PPID(父进程ID):创建当前进程的父进程标识,通过 getppid() 获取。

    获取PID、PPID测试代码:

    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    int main()
    {
    printf("pid: %d\\n", getpid());
    printf("ppid: %d\\n", getppid());
    return 0;
    }

    3-1-6 系统调用创建进程——fork初识

    可通过 man fork 指令查询fork函数官方手册,其核心特性如下:

    • fork 函数拥有两个返回值,是进程创建的核心特性。

    • 父子进程代码完全共享,数据独立私有,采用写时拷贝机制节省内存。

    基础fork创建进程代码:

    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    int main()
    {
    int ret = fork();
    printf("hello proc : %d!, ret: %d\\n", getpid(), ret);
    sleep(1);
    return 0;
    }

    实际开发中,fork创建进程后,需通过if分支实现父子进程逻辑分流:

    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    int main()
    {
    int ret = fork();
    if(ret < 0){
    perror("fork");
    return 1;
    }
    else if(ret == 0){ // 子进程执行逻辑
    printf("I am child : %d!, ret: %d\\n", getpid(), ret);
    }else{ // 父进程执行逻辑
    printf("I am father : %d!, ret: %d\\n", getpid(), ret);
    }
    sleep(1);
    return 0;
    }

    课堂核心重点(待后续详解)
    • fork 函数为什么会存在两个返回值?

    • 两个返回值分别如何分配给父进程、子进程?

    • 同一变量返回值,为何能同时满足 if、else if 不同分支条件(后续课程详解)


    3-2 进程状态

    3-2-1 Linux内核源代码进程状态定义

    为区分进程运行的不同场景,Linux内核定义了多种进程状态(内核中进程也称为任务),所有状态均在内核源码中统一枚举定义,具体如下:

    /*
    *The task state array is a strange "bitmap" of
    *reasons to sleep. Thus "running" is zero, and
    *you can test for combinations of others with
    *simple bit tests.
    */
    static const char *const task_state_array[] = {
    "R (running)", /* 0 */
    "S (sleeping)", /* 1 */
    "D (disk sleep)", /* 2 */
    "T (stopped)", /* 4 */
    "t (tracing stop)",/* 8 */
    "X (dead)", /* 16 */
    "Z (zombie)", /* 32 */
    };

    各进程状态详细解析:

    • R 运行状态(running):并非代表进程一定正在占用CPU运行,只要进程处于运行中,或位于CPU运行队列中等待调度,均为R状态。

    • S 可中断睡眠状态(sleeping):进程等待某一事件完成而主动休眠,属于可中断睡眠,可被信号唤醒。

    • D 不可中断磁盘休眠状态(disk sleep):进程等待IO操作结束,该状态下进程不可被信号中断、唤醒,常用于磁盘读写等关键IO场景。

    • T 停止状态(stopped):进程被暂停运行。可通过 SIGSTOP 信号暂停进程,通过 SIGCONT 信号恢复进程继续运行。

    • t 追踪停止状态(tracing stop):进程被调试工具、ptrace系统调用追踪时的暂停状态。

    • X 死亡状态(dead):进程彻底释放资源的临时返回状态,瞬时结束,无法在任务列表中观测到该状态。

    • Z 僵尸状态(zombie):子进程退出,父进程未读取其退出状态,子进程残留PCB信息驻留进程表的特殊状态。

    3-2-2 进程状态查看指令

    Linux主要通过 ps aux、ps axj 查看系统进程状态,各参数作用:

    ps aux / ps axj 命令

    • a:显示当前终端下所有用户的全部进程,包含其他用户进程。

    • x:显示无控制终端的进程,多用于查看后台守护进程。

    • j:展示进程组ID、会话ID、父进程ID等作业控制相关信息。

    • u:以用户维度展示进程详情,包含进程所属用户、CPU占用、内存占用等信息。

    3-2-3 僵尸进程(Z)

    僵尸进程是Linux中特殊的进程状态,有明确的生成条件与运行特性:

    • 生成原因:子进程正常退出,父进程未调用 wait() 等系统调用读取子进程的退出返回值。

    • 核心特性:子进程退出后,资源大部分释放,但PCB进程控制块会保留在系统进程表中,持续等待父进程读取退出状态。

    • 判定条件:子进程退出、父进程正常运行、父进程未回收子进程资源,子进程进入Z僵尸状态。

    僵尸进程模拟代码(维持30秒僵尸进程):

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    int main()
    {
    pid_t id = fork();
    if(id < 0){
    perror("fork");
    return 1;
    }
    else if(id > 0){
    // 父进程:持续运行,不回收子进程
    printf("parent[%d] is sleeping…\\n", getpid());
    sleep(30);
    }else{
    // 子进程:运行5秒后退出,成为僵尸进程
    printf("child[%d] is begin Z…\\n", getpid());
    sleep(5);
    exit(EXIT_SUCCESS);
    }
    return 0;
    }

    测试方式:编译运行代码,新开终端使用 ps aux 实时查看进程状态,可观测子进程5秒后变为Z状态。

    编译并在另⼀个终端下启动监控

    开始测试

    看到结果

    3-2-4 僵尸进程的危害
    • 进程退出状态需要通过PCB保存,用于告知父进程任务执行结果,若父进程长期不读取,子进程将持续保持Z状态。

    • 僵尸进程的PCB不会被系统释放,会持续占用内存空间,属于永久占用的内核数据结构资源。

    • 若父进程频繁创建子进程且不回收,会产生大量僵尸进程,持续消耗系统内存,最终造成内存泄漏。

    • 系统进程数量存在上限,大量僵尸进程会占用进程名额,导致后续无法创建新进程。

    3-2-5 孤儿进程

    与僵尸进程对应,孤儿进程是父进程提前退出产生的特殊进程:

    • 生成原因:父进程先于子进程退出,剩余继续运行的子进程称为孤儿进程。

    • 系统处理机制:孤儿进程会被系统1号进程(init/systemd)自动领养,由1号进程统一负责回收孤儿进程的退出资源,无内存泄漏危害。

    孤儿进程模拟代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    int main()
    {
    pid_t id = fork();
    if(id < 0){
    perror("fork");
    return 1;
    }
    else if(id == 0){
    // 子进程:长时间运行
    printf("I am child, pid : %d\\n", getpid());
    sleep(10);
    }else{
    // 父进程:3秒后提前退出
    printf("I am parent, pid: %d\\n", getpid());
    sleep(3);
    exit(0);
    }
    return 0;
    }

    3-3 进程优先级

    3-3-1 基本概念

    进程优先级是CPU分配系统资源的核心依据,决定进程的调度先后顺序:

    • 优先级越高的进程,拥有优先被CPU调度执行的权利。

    • 合理配置进程优先级,可优化多任务系统的资源分配,提升整机运行性能。

    • 支持进程CPU绑定,可将低优先级进程固定在指定CPU核心,优化整体调度效率。

    3-3-2 查看系统进程优先级信息

    通过 ps -l 命令可查看进程核心优先级相关字段,关键参数解析:

    • UID:进程执行者的用户身份ID。

    • PID:进程唯一标识ID。

    • PPID:父进程ID,标识进程的创建来源。

    • PRI:进程默认优先级(80),数值越小,优先级越高,越先被CPU执行。

    • NI:进程nice值,用于修正进程优先级。

    3-3-3 PRI与NI详解
    • PRI:进程基础优先级,决定进程调度基础顺序,数值越小优先级越高。

    • NI(nice值):优先级修正数值,是用户可手动调整的参数。

    • 优先级计算公式:PRI(new) = PRI(old) + nice

    • nice值为负数:新PRI值变小,进程优先级升高,执行优先级更高。

    • nice值为正数:新PRI值变大,进程优先级降低。

    • 取值范围:nice值固定为 -20 ~ 19,共40个优先级档位。

    • Linux下调整进程优先级,本质就是修改进程的nice值。

    • 总的优先级范围 [60, 99]

    3-3-4 PRI与NI核心区别
    • PRI是进程的真实调度优先级,NI是优先级的修正参数,二者并非同一概念。

    • nice值无法直接决定优先级,只能对原生PRI优先级进行微调修正。

    3-3-5 进程优先级查看与修改方式

    1、top命令动态修改nice值

  • 终端输入 top 进入进程监控界面;

  • 按下 r 键,输入目标进程PID;

  • 输入新的nice值,即可完成优先级修改。

  • 2、专用命令:nice(创建进程时指定优先级)、renice(修改已存在进程优先级)

    3、系统调用函数

    #include <sys/time.h>
    #include <sys/resource.h>
    // 获取进程优先级
    int getpriority(int which, int who);
    // 设置进程优先级
    int setpriority(int which, int who, int prio);

    3-3-6 进程核心补充概念
    • 竞争性:系统进程数量远多于CPU核心数量,所有进程竞争有限的CPU资源,因此系统通过优先级机制规范资源分配,保证任务高效执行。

    • 独立性:多进程并发运行时,各进程拥有独立资源空间,运行过程中相互隔离、互不干扰。

    • 并行:多CPU核心环境下,多个进程在不同CPU核心上同时运行,真实同步执行多个任务。

    • 并发:单CPU核心环境下,系统通过快速进程切换,让多个进程在一段时间内交替推进,宏观上实现多任务同时运行,微观上同一时刻仅有一个进程运行。


    赞(0)
    未经允许不得转载:171主机测评 » Linux进程全面解析:从基础到高级管理(2/3)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址