云计算百科
云计算领域专业知识百科平台

0基础学嵌入式--全网最详细Linux开发指南:一篇文章带你学懂Linux进程

Linux进程完全指南:从程序到PCB的深度解析

        理解进程是理解操作系统的关键。本文带你从程序加载到进程调度,彻底掌握Linux进程管理的所有核心概念。

一、程序 vs 进程:静态与动态的哲学

1.1 基本概念澄清

// 程序:静态的二进制文件
// 示例:编译后的a.out
$ gcc hello.c -o hello # 生成程序文件

// 进程:程序的一次执行实例
$ ./hello & # 启动一个进程
$ ./hello & # 再启动一个进程(同一个程序,不同进程)

关键区别:

  • 程序:存放在硬盘中的数据集合(静态)

  • 进程:程序动态执行的全过程(动态)

类比理解:

  • 程序 = 乐谱(静态的符号)

  • 进程 = 演奏过程(动态的执行)

1.2 进程监控命令大全

实时查看进程

# 1. top – 动态监控(类似Windows的任务管理器)
top
# 排序:按P(CPU)、M(内存)、T(运行时间)
# 退出:q

# 2. htop – 增强版top(需要安装)
sudo apt install htop
htop

进程信息查询

# 查看所有进程(标准格式)
ps -ef

# 查看进程详细信息(BSD格式)
ps aux

# 常用组合:查找特定进程
ps -ef | grep nginx # 查找nginx进程
ps aux | grep "python" # 查找Python相关进程

# 查看进程树状结构
pstree
pstree -p # 显示PID

进程控制命令

# 1. 杀死进程
kill 1234 # 正常终止PID为1234的进程
kill -9 1234 # 强制杀死(SIGKILL)
killall firefox # 杀死所有firefox进程
pkill -f "pattern" # 根据模式杀死进程

# 2. 后台任务管理
./server & # 后台运行
jobs # 查看后台任务
fg %1 # 将任务1放到前台
bg %1 # 将暂停的任务1放到后台继续运行

# 3. 进程优先级
nice -n 10 ./task.sh # 低优先级运行(nice值-20到19,越大优先级越低)
renice 5 -p 1234 # 修改运行中进程的优先级

二、进程地址空间:4GB虚拟内存的秘密

2.1 虚拟地址 vs 物理地址

// 程序员看到的是虚拟地址
int *p = malloc(sizeof(int)); // 返回虚拟地址

// 实际物理地址由MMU(内存管理单元)管理
// CPU → MMU → 物理RAM

为什么需要虚拟地址?

  • 内存保护:进程间隔离,防止相互干扰

  • 内存扩展:可使用比物理内存更大的地址空间

  • 简化编程:每个进程都有独立的4GB地址空间

  • 2.2 进程地址空间布局(32位系统)

    0xFFFFFFFF
    ┌─────────────────┐ ← 内核空间(1GB,用户不可访问)
    │ 内核 │
    ├─────────────────┤ ← 0xC0000000
    │ 栈区 │ ← 向下增长,存储局部变量
    │ ↓ │
    ├─────────────────┤
    │ … │ ← 未映射区域
    ├─────────────────┤
    │ 堆区 │ ← 向上增长,动态分配
    │ ↑ │
    ├─────────────────┤
    │ .bss段 │ ← 未初始化全局/静态变量(自动初始化为0)
    ├─────────────────┤
    │ .data段 │ ← 已初始化全局/静态变量
    ├─────────────────┤
    │ .rodata段 │ ← 只读数据(字符串常量)
    ├─────────────────┤
    │ .text段 │ ← 代码段(机器指令)
    └─────────────────┘ ← 0x00000000

    2.3 各内存区域详解

    1. 代码段 (.text)

    // 存储机器指令
    void func() {
    printf("Hello"); // 这部分代码在.text段
    }

    2. 数据段

    // .rodata:只读数据段
    char *str = "Hello"; // 字符串"Hello"在.rodata

    // .data:已初始化全局/静态变量
    int global_init = 100; // 在.data段
    static int static_init = 200; // 在.data段

    // .bss:未初始化全局/静态变量
    int global_uninit; // 在.bss段,自动初始化为0
    static int static_uninit; // 在.bss段,自动初始化为0

    3. 栈区 (Stack)

    void func() {
    int local_var = 10; // 局部变量在栈区
    char buffer[100]; // 数组在栈区
    } // 函数结束,局部变量自动释放

    栈区特点:

    • 大小默认约8MB(可用ulimit -s查看)

    • 向下增长(高地址 → 低地址)

    • 自动管理(编译器负责分配释放)

    • 存储:局部变量、函数参数、返回地址

    4. 堆区 (Heap)

    // 动态内存分配
    int *p = (int*)malloc(100 * sizeof(int)); // 在堆区分配
    free(p); // 必须手动释放

    堆区特点:

    • 向上增长(低地址 → 高地址)

    • 手动管理(程序员负责分配释放)

    • 大小受系统剩余内存限制

    • 分配较慢(需要系统调用)

    三、进程创建:fork的魔法

    3.1 fork函数详解

    #include <unistd.h>
    #include <stdio.h>

    int main() {
    pid_t pid = fork(); // 关键调用

    if (pid < 0) {
    // 错误:fork失败
    perror("fork failed");
    return -1;
    } else if (pid == 0) {
    // 子进程:fork返回0
    printf("我是子进程,PID=%d,父进程PID=%d\\n",
    getpid(), getppid());
    } else {
    // 父进程:fork返回子进程PID
    printf("我是父进程,PID=%d,创建的子进程PID=%d\\n",
    getpid(), pid);
    }

    return 0;
    }

    fork的执行过程:

    父进程执行fork()

    操作系统创建子进程

    复制父进程的:代码段、数据段、堆、栈、文件描述符表

    父子进程从fork()返回处继续执行

    父进程:fork()返回子进程PID(>0)
    子进程:fork()返回0

    3.2 获取进程ID

    #include <unistd.h>

    // 获取当前进程ID
    pid_t my_pid = getpid();
    printf("当前进程ID:%d\\n", my_pid);

    // 获取父进程ID
    pid_t parent_pid = getppid();
    printf("父进程ID:%d\\n", parent_pid);

    // 实际应用:创建守护进程
    if (fork() > 0) {
    exit(0); // 父进程退出
    }
    // 子进程继续执行(成为守护进程)
    setsid(); // 创建新会话

    四、进程状态:生命的七个阶段

    4.1 七种进程状态详解

    // 进程状态查看
    ps aux | grep process_name
    // STAT列显示进程状态:
    // R: 运行/就绪
    // S: 可中断睡眠
    // D: 不可中断睡眠
    // T: 停止
    // Z: 僵尸
    // X: 死亡

    1. 运行态 (R – Running)

    # 示例:CPU密集型进程
    while true; do echo "running"; done &
    ps aux | grep "while"
    # STAT显示:R

    2. 就绪态 (R – Ready)

    // 多个进程竞争CPU
    // 处于就绪队列,等待调度

    3. 可中断等待态 (S – Interruptible Sleep)

    # 示例:等待用户输入
    read -p "请输入:" input &
    ps aux | grep "read"
    # STAT显示:S

    4. 不可中断等待态 (D – Uninterruptible Sleep)

    # 通常发生在等待磁盘I/O
    # 无法被信号中断

    5. 停止态 (T – Stopped)

    # 发送SIGSTOP信号
    kill -STOP 1234
    # 恢复运行
    kill -CONT 1234

    6. 僵尸态 (Z – Zombie)

    #include <stdlib.h>
    #include <unistd.h>

    int main() {
    pid_t pid = fork();

    if (pid == 0) {
    // 子进程立即退出,成为僵尸
    exit(0);
    } else {
    // 父进程不回收子进程
    sleep(30); // 在此期间,子进程是僵尸状态
    // 应该调用wait()回收子进程
    }
    return 0;
    }

    7. 死亡态 (X – Dead)

    // 进程已结束,资源已回收
    // 通常看不到此状态

    4.2 状态转换图

    创建

    就绪态(R) ←──┐
    │ │
    ↓ (调度) │
    运行态(R) │
    │ │
    ├─→ 可中断等待(S) ──┘
    │ │
    ├─→ 不可中断等待(D)
    │ │
    ├─→ 停止(T) ────────┐
    │ │ │
    ↓ (结束) │ │
    僵尸(Z) │ │
    │ │ │
    ↓ (回收) │ │
    死亡(X) │ │
    └────────┘

    五、进程调度算法:CPU的时间分配艺术

    5.1 六种经典调度算法

    1. 先来先服务 (FCFS)

    // 类比:超市排队
    // 优点:简单公平
    // 缺点:短作业可能等待长作业(护航效应)

    2. 短作业优先 (SJF)

    // 总是调度预计运行时间最短的进程
    // 优点:平均等待时间最小
    // 缺点:长作业可能饿死

    3. 优先级调度

    # Linux中的实现:nice值
    nice -n -20 ./critical_task # 最高优先级
    nice -n 19 ./low_task # 最低优先级

    4. 时间片轮转 (RR)

    // Linux默认调度策略
    // 时间片:5-800ms(根据系统负载调整)

    // 现象:宏观并行,微观串行
    while (true) {
    // 每个进程运行一个时间片
    // 然后切换到下一个进程
    }

    查看时间片:

    # 查看调度参数
    cat /proc/sys/kernel/sched_rr_timeslice_ms

    5. 多级反馈队列 (MLFQ)

    队列1(高优先级,时间片短) ← 新进程
    ↓(用完时间片未结束)
    队列2(中优先级,时间片中等)
    ↓(用完时间片未结束)
    队列3(低优先级,时间片长)

    6. 负载均衡调度

    # 多核CPU上的调度
    # 将进程均衡分配到各个CPU核心
    taskset -c 0,1 ./program # 绑定到CPU0和1

    5.2 Linux调度策略查看

    # 查看进程调度策略
    chrt -p 1234

    # 设置调度策略
    chrt -f -p 99 1234 # 设置为FIFO,优先级99
    chrt -r -p 50 1234 # 设置为RR,优先级50

    六、进程间空间独立性

    6.1 虚拟地址空间映射

    进程1视角 进程2视角
    0xFFFFFFFF 0xFFFFFFFF
    ┌──────────┐ ┌──────────┐
    │ 内核 │ │ 内核 │
    ├──────────┤ ├──────────┤
    │ 栈 │ │ 栈 │
    ├──────────┤ ├──────────┤
    │ 堆 │ │ 堆 │
    ├──────────┤ ├──────────┤
    │ 数据段 │ │ 数据段 │
    ├──────────┤ ├──────────┤
    │ 代码段 │ │ 代码段 │
    └──────────┘ └──────────┘
    ↓ ↓
    MMU(内存管理单元)
    ↓ ↓
    物理RAM的不同区域

    6.2 验证进程空间独立

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>

    int global_var = 100; // 全局变量

    int main() {
    int local_var = 200; // 局部变量
    int *heap_var = malloc(sizeof(int));
    *heap_var = 300;

    pid_t pid = fork();

    if (pid == 0) {
    // 子进程修改所有变量
    global_var = 101;
    local_var = 201;
    *heap_var = 301;

    printf("子进程:global=%d, local=%d, heap=%d\\n",
    global_var, local_var, *heap_var);
    exit(0);
    } else {
    // 父进程等待子进程结束
    wait(NULL);
    printf("父进程:global=%d, local=%d, heap=%d\\n",
    global_var, local_var, *heap_var);
    }

    free(heap_var);
    return 0;
    }

    输出结果:

    子进程:global=101, local=201, heap=301
    父进程:global=100, local=200, heap=300

    结论:父子进程有独立的地址空间,修改互不影响。

    七、实战:编写健壮的进程程序

    7.1 避免僵尸进程

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/wait.h>

    int main() {
    for (int i = 0; i < 5; i++) {
    pid_t pid = fork();

    if (pid == 0) {
    // 子进程
    printf("子进程%d开始,PID=%d\\n", i, getpid());
    sleep(i + 1); // 模拟工作
    printf("子进程%d结束\\n", i);
    exit(0); // 正常退出
    } else if (pid > 0) {
    // 父进程
    printf("创建子进程%d,PID=%d\\n", i, pid);
    } else {
    perror("fork failed");
    exit(1);
    }
    }

    // 父进程回收所有子进程
    int status;
    pid_t child_pid;

    while ((child_pid = wait(&status)) > 0) {
    if (WIFEXITED(status)) {
    printf("子进程%d正常退出,返回值:%d\\n",
    child_pid, WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
    printf("子进程%d被信号%d终止\\n",
    child_pid, WTERMSIG(status));
    }
    }

    printf("所有子进程已回收\\n");
    return 0;
    }

    7.2 创建守护进程

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <signal.h>

    void create_daemon() {
    pid_t pid = fork();

    if (pid < 0) {
    perror("fork失败");
    exit(1);
    } else if (pid > 0) {
    // 父进程退出
    exit(0);
    }

    // 子进程继续

    // 1. 创建新会话,脱离终端
    setsid();

    // 2. 改变工作目录到根目录
    chdir("/");

    // 3. 重设文件权限掩码
    umask(0);

    // 4. 关闭不需要的文件描述符
    for (int i = 0; i < 3; i++) {
    close(i);
    }

    // 5. 重定向标准输入输出错误
    open("/dev/null", O_RDWR); // stdin
    dup(0); // stdout
    dup(0); // stderr

    // 6. 忽略某些信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGHUP, SIG_IGN);

    // 守护进程主循环
    while (1) {
    // 执行守护任务
    sleep(10);
    }
    }

    int main() {
    create_daemon();
    return 0;
    }

    八、性能监控与调试

    8.1 进程资源监控

    # 1. 实时监控
    top -p 1234 # 监控特定进程
    top -u username # 监控特定用户进程

    # 2. 查看进程打开的文件
    lsof -p 1234 # 查看进程打开的文件
    lsof -c nginx # 查看nginx打开的文件

    # 3. 查看进程内存映射
    pmap 1234 # 显示进程内存映射
    cat /proc/1234/maps # 更详细的内存映射

    # 4. 查看进程状态
    cat /proc/1234/status # 进程状态信息
    cat /proc/1234/stat # 进程统计信息

    8.2 性能分析工具

    # 1. 系统级监控
    vmstat 1 # 每秒显示系统状态
    mpstat 1 # CPU使用情况
    iostat 1 # I/O使用情况

    # 2. 进程级监控
    pidstat 1 # 进程资源使用统计
    strace -p 1234 # 跟踪系统调用

    # 3. 内存分析
    valgrind –tool=memcheck ./program # 内存泄漏检测

    九、常见问题与解决方案

    9.1 进程创建失败

    // 错误:Resource temporarily unavailable
    // 原因:达到进程数限制

    // 解决方案:
    // 1. 查看当前限制
    ulimit -u

    // 2. 修改限制(临时)
    ulimit -u 10000

    // 3. 查看系统总限制
    cat /proc/sys/kernel/pid_max

    // 4. 检查是否存在僵尸进程占用资源
    ps aux | grep "defunct"

    9.2 进程卡死分析

    # 1. 查看进程状态
    ps aux | grep process_name

    # 2. 查看进程堆栈
    pstack 1234

    # 3. 生成core dump(需提前设置)
    ulimit -c unlimited
    kill -ABRT 1234

    # 4. 使用gdb分析core文件
    gdb ./program core.1234

    9.3 避免常见错误

    // 错误1:忘记检查fork返回值
    pid_t pid = fork();
    // 正确:总是检查返回值
    if (pid < 0) {
    perror("fork failed");
    // 处理错误
    }

    // 错误2:产生僵尸进程
    if (fork() == 0) {
    exit(0);
    }
    // 正确:父进程回收子进程
    wait(NULL);

    // 错误3:fork后未处理文件描述符
    int fd = open("file.txt", O_RDWR);
    fork();
    // 正确:父子进程可能需要分别处理文件指针

    十、总结与最佳实践

    10.1 核心要点回顾

  • 进程是动态的程序执行实例

  • 每个进程有独立的4GB虚拟地址空间

  • fork创建子进程,复制父进程的地址空间

  • 进程有7种状态,需要正确管理

  • 调度算法决定CPU时间的分配

  • 10.2 最佳实践

  • 总是检查系统调用返回值

  • 及时回收子进程,避免僵尸进程

  • 合理设置进程优先级

  • 使用合适的调度策略

  • 监控进程资源使用情况

  • 10.3 面试常见问题

  • 进程和线程的区别?

  • fork和exec的区别?

  • 什么是僵尸进程?如何避免?

  • 进程地址空间包含哪些部分?

  • Linux有哪些进程调度算法?


  • 立即动手!

    理论知识需要实践巩固:

  • 编写一个程序,创建多个子进程并观察它们的状态变化

  • 实现一个简单的进程监控工具

  • 分析现有程序的进程行为

  • 尝试优化一个程序的进程调度策略

  • 记住:理解进程是理解操作系统的第一步,也是编写高性能、稳定程序的基础。


    互动问题:

    你在进程编程中遇到过什么有趣的问题?

    有哪些进程管理的实用技巧?

    在评论区分享你的经验,我们一起学习进步!

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 0基础学嵌入式--全网最详细Linux开发指南:一篇文章带你学懂Linux进程
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!