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系统提供两种主流方式查看进程信息:

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

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

常驻进程测试代码:

cpp 复制代码
#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测试代码:

cpp 复制代码
#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创建进程代码:

cpp 复制代码
#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分支实现父子进程逻辑分流:

cpp 复制代码
#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内核定义了多种进程状态(内核中进程也称为任务),所有状态均在内核源码中统一枚举定义,具体如下:

bash 复制代码
/*
*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 auxps axj 查看系统进程状态,各参数作用:

bash 复制代码
ps aux / ps axj 命令
  • a:显示当前终端下所有用户的全部进程,包含其他用户进程。

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

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

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

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

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

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

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

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

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

cpp 复制代码
#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号进程统一负责回收孤儿进程的退出资源,无内存泄漏危害。

孤儿进程模拟代码:

cpp 复制代码
#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值

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

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

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

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

3、系统调用函数

cpp 复制代码
#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核心环境下,系统通过快速进程切换,让多个进程在一段时间内交替推进,宏观上实现多任务同时运行,微观上同一时刻仅有一个进程运行。


相关推荐
洛水水1 小时前
图床项目实现:MD5秒传 + 个人文件列表 + 图片分享等功能的完善
服务器·网络
Irissgwe1 小时前
8-1\IP 分片和组装的具体过程
linux·网络·tcp/ip·网络层·分片·组装
Zevalin爱灰灰1 小时前
makefile从入门到实战 第一章 认识makefile(一)
linux·makefile
爱吃泡芙的小白白1 小时前
无人机机巢:低空经济的自动化基石,一文读懂其原理、应用与未来
运维·自动化·无人机·低空经济
翼龙云_cloud1 小时前
阿里云代理商:轻量服务器建站常见问题及解决方案汇总
运维·阿里云·云计算
唔661 小时前
(一)一套完整的自动化脚本,一键搭建5节点负载均衡集群
运维·自动化·负载均衡
闪电悠米1 小时前
黑马点评-秒杀优化-04_lua_and_db_fallback
服务器·开发语言·网络·数据库·缓存·junit·lua
豆瓣鸡2 小时前
Docker快速入门
运维·docker·容器
Shadow(⊙o⊙)2 小时前
进程间通信0.0-pipe()匿名管道,详细分析进程池调度队列执行逻辑,进程池模拟实现。
linux·运维·服务器·开发语言·c++