news 2026/4/24 20:33:19

从零到壹嵌入式Linux编程实战教程课:第10课 进程管理子系统(二):进程调度与销毁 模块二:内核核心机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零到壹嵌入式Linux编程实战教程课:第10课 进程管理子系统(二):进程调度与销毁 模块二:内核核心机制

嵌入式Linux核心课程课

文章目录

  • 嵌入式Linux核心课程课
    • 一、课程目标
    • 二、进程调度核心概述
      • 2.1 调度核心目的
      • 2.2 调度器核心概念
    • 三、Linux核心调度策略(详解+样例代码)
      • 3.1 调度策略一:CFS调度(Completely Fair Scheduler,完全公平调度)
        • 核心原理
        • 适用场景
      • 3.2 调度策略二:RT调度(Real-Time Scheduler,实时调度)
        • 核心原理
        • 适用场景
      • 3.3 样例代码1:查看进程调度策略与优先级(用户态)
      • 3.4 样例代码2:内核态查看调度类(验证调度机制)
    • 四、进程调度触发时机
    • 五、进程销毁机制(详解+样例代码)
      • 5.1 进程销毁的两种方式
        • 方式1:主动退出(正常销毁)
        • 方式2:被动终止(异常销毁)
      • 5.2 进程销毁完整流程
      • 5.3 关键概念:僵尸进程与孤儿进程
      • 5.4 样例代码3:进程主动退出与父进程回收(用户态)
      • 5.5 样例代码4:内核态释放进程资源(简化版)
    • 六、课堂练习
    • 七、课后作业
    • 八、本章总结
    • 九、核心关键词
    • 第10课 课程回顾总结
  • 上一节课答案:第9课 进程管理子系统(一):进程概念与创建 实战作业代码
    • 代码功能说明
    • 注意事项

一、课程目标

  1. 理解进程调度的核心意义与设计原则,掌握调度器的作用

  2. 掌握Linux内核两种核心调度策略(CFS、RT)的原理与适用场景

  3. 理解进程优先级、时间片的分配逻辑,以及调度触发时机

  4. 掌握进程销毁的完整流程,理解僵尸进程、孤儿进程的产生与处理

  5. 能通过代码分析进程调度机制,排查调度相关异常

  6. 衔接进程创建知识,形成完整的进程生命周期管理认知


二、进程调度核心概述

进程调度是内核的核心功能之一,本质是合理分配CPU资源,决定多个就绪进程中哪个能获得CPU执行权,确保系统高效、公平、实时地运行。

2.1 调度核心目的

  • 提高CPU利用率:避免CPU空闲,让CPU始终处于有效工作状态

  • 保证系统公平性:为每个进程分配合理的CPU时间,避免饥饿

  • 满足实时性需求:嵌入式场景中,确保高优先级实时进程快速响应

  • 优化系统响应速度:缩短进程等待时间,提升用户体验

2.2 调度器核心概念

Linux内核通过**调度器类(sched_class)**管理不同类型的调度策略,每个进程都属于某一个调度类,调度器根据调度类的优先级决定调度顺序。

核心调度器:Linux 2.6.23后引入CFS调度器,替代传统O(1)调度器,成为默认调度器。


三、Linux核心调度策略(详解+样例代码)

Linux内核支持多种调度策略,核心分为两大类:普通进程调度(CFS)和实时进程调度(RT),分别对应不同的应用场景。

3.1 调度策略一:CFS调度(Completely Fair Scheduler,完全公平调度)

核心原理

CFS是Linux默认调度策略,适用于普通用户进程(非实时),核心思想是“公平分配CPU时间”——为每个进程分配一个“虚拟运行时间”,调度器始终选择虚拟运行时间最少的进程执行。

关键机制:

  • 虚拟运行时间(vruntime):根据进程优先级动态调整,优先级越高,vruntime增长越慢,获得CPU的机会越多。

  • 时间片:CFS不固定时间片大小,而是根据就绪进程数量动态调整,避免短进程等待过长时间。

  • 调度触发:进程时间片耗尽、进程主动放弃CPU、高优先级进程进入就绪态时触发。

适用场景

普通应用程序(如Shell、浏览器、后台服务),对实时性要求不高,追求公平性和CPU利用率。

3.2 调度策略二:RT调度(Real-Time Scheduler,实时调度)

核心原理

RT调度适用于实时进程,采用“优先级抢占式调度”——实时进程优先级高于所有普通进程,高优先级实时进程可抢占低优先级进程的CPU执行权。

RT调度的两种子策略:

  • SCHED_FIFO(先进先出):同优先级实时进程,按就绪顺序执行,一旦执行,直到主动放弃CPU或被更高优先级进程抢占。

  • SCHED_RR(时间片轮转):同优先级实时进程,按时间片轮转执行,时间片耗尽后切换到下一个同优先级进程。

适用场景

嵌入式实时场景(如工业控制、车载系统、医疗设备),对响应时间有严格要求(如毫秒级响应)。

3.3 样例代码1:查看进程调度策略与优先级(用户态)

查看当前进程的调度策略、优先级,验证CFS调度#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sched.h> int main() { pid_t pid = getpid(); int policy; struct sched_param param; // 获取当前进程的调度策略 policy = sched_getscheduler(pid); if (policy == -1) { perror("sched_getscheduler failed"); return -1; } // 获取当前进程的调度参数(优先级) if (sched_getparam(pid, &param) == -1) { perror("sched_getparam failed"); return -1; } // 打印调度策略 printf("当前进程PID:%d\n", pid); switch (policy) { case SCHED_OTHER: printf("调度策略:CFS(SCHED_OTHER)\n"); break; case SCHED_FIFO: printf("调度策略:RT-FIFO(SCHED_FIFO)\n"); break; case SCHED_RR: printf("调度策略:RT-RR(SCHED_RR)\n"); break; default: printf("调度策略:未知\n"); } // 打印优先级(CFS优先级范围0-139,RT优先级范围1-99) printf("进程优先级:%d\n", param.sched_priority); return 0; }

运行结果:

当前进程PID:1234

调度策略:CFS(SCHED_OTHER)

进程优先级:0

说明:普通用户进程默认使用CFS调度策略,优先级为0(CFS优先级0对应nice值0,nice值范围-20~19,值越小优先级越高)。

3.4 样例代码2:内核态查看调度类(验证调度机制)

内核模块,查看当前进程的调度类,区分CFS与RT调度 #include <linux/module.h> #include <linux/sched.h> #include <linux/init.h> static int __init sched_class_demo_init(void) { // 判断当前进程的调度类 if (current->sched_class == &fair_sched_class) { printk("当前进程使用 CFS 公平调度\n"); } else if (current->sched_class == &rt_sched_class) { printk("当前进程使用 RT 实时调度\n"); } else { printk("当前进程使用其他调度类\n"); } // 打印进程优先级(prio为动态优先级,static_prio为静态优先级) printk("进程静态优先级:%d,动态优先级:%d\n", current->static_prio, current->prio); return 0; } static void __exit sched_class_demo_exit(void) { printk("调度类演示模块卸载\n"); } module_init(sched_class_demo_init); module_exit(sched_class_demo_exit); MODULE_LICENSE("GPL");

运行结果:

当前进程使用 CFS 公平调度

进程静态优先级:120,动态优先级:120

说明:CFS进程的静态优先级默认120(对应nice值0),动态优先级与静态优先级一致;RT进程的静态优先级范围为1~99,高于CFS进程。


四、进程调度触发时机

进程调度不会随机触发,内核在特定时机触发调度,确保系统稳定高效,核心触发时机分为以下4种:

  1. 进程时间片耗尽:CFS调度中,进程的虚拟运行时间达到阈值,调度器触发切换。

  2. 进程主动放弃CPU:进程调用sleep()、wait()等函数,进入睡眠状态,主动释放CPU。

  3. 高优先级进程就绪:高优先级进程(尤其是RT进程)从睡眠或创建状态进入就绪态,抢占当前运行进程的CPU。

  4. 内核态返回用户态时:内核处理完系统调用、中断后,返回用户态前,检查是否需要调度。


五、进程销毁机制(详解+样例代码)

进程销毁是进程生命周期的最后一步,核心是释放进程占用的所有资源(内存、文件描述符、PCB等),分为“主动退出”和“被动终止”两种方式。

5.1 进程销毁的两种方式

方式1:主动退出(正常销毁)

进程通过调用exit()、_exit()系统调用主动退出,释放资源,常见场景:

  • 用户程序执行完毕,main函数return,底层调用exit();

  • 进程主动调用exit(),终止自身运行。

方式2:被动终止(异常销毁)

进程被其他进程或内核终止,常见场景:

  • 其他进程调用kill()系统调用,发送终止信号(如SIGKILL);

  • 进程执行非法操作(如除以0、访问非法内存),内核触发异常,终止进程;

  • 父进程终止,子进程被init进程(PID=1)收养,若子进程退出后父进程未回收,成为僵尸进程。

5.2 进程销毁完整流程

  1. 触发退出:进程调用exit()或被信号终止,进入退出流程;

  2. 关闭资源:关闭进程打开的所有文件描述符、网络连接、信号处理等;

  3. 释放内存:释放进程地址空间、页表、堆栈等内存资源;

  4. 更新PCB:将进程状态设置为EXIT_ZOMBIE(僵尸态),保存退出状态码;

  5. 通知父进程:向父进程发送SIGCHLD信号,告知父进程自身已退出;

  6. 资源回收:父进程调用wait()/waitpid(),读取子进程退出状态,释放子进程PCB,进程彻底销毁;若父进程未回收,子进程保持僵尸态,直到父进程退出后被init进程回收。

5.3 关键概念:僵尸进程与孤儿进程

  • 僵尸进程:子进程退出,父进程未调用wait()/waitpid()回收,子进程PCB未释放,状态为EXIT_ZOMBIE。危害:占用PID资源,长期积累会导致系统PID耗尽。

  • 孤儿进程:父进程先于子进程退出,子进程被init进程(PID=1)收养,子进程退出后由init进程回收,不会成为僵尸进程。

5.4 样例代码3:进程主动退出与父进程回收(用户态)

创建子进程,子进程主动退出,父进程调用waitpid()回收,避免僵尸进程 #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <stdlib.h> int main() { pid_t pid, wpid; int status; // 创建子进程 pid = fork(); if (pid < 0) { perror("fork failed"); return -1; } if (pid == 0) { // 子进程:执行任务后主动退出 printf("子进程(PID:%d):执行任务完毕,主动退出\n", getpid()); exit(0); // 主动退出,退出状态码0 } else { // 父进程:等待子进程退出并回收 printf("父进程(PID:%d):等待子进程退出...\n", getpid()); // waitpid() 等待指定子进程退出,获取退出状态 wpid = waitpid(pid, &status, 0); if (wpid == -1) { perror("waitpid failed"); return -1; } // 判断子进程退出状态 if (WIFEXITED(status)) { printf("父进程:子进程(PID:%d)正常退出,退出状态码:%d\n", wpid, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("父进程:子进程(PID:%d)被信号终止,终止信号:%d\n", wpid, WTERMSIG(status)); } } return 0; }

运行结果:

父进程(PID:1234):等待子进程退出…

子进程(PID:1235):执行任务完毕,主动退出

父进程:子进程(PID:1235)正常退出,退出状态码:0

说明:父进程通过waitpid()回收子进程,避免子进程成为僵尸进程;WIFEXITED(status)判断子进程是否正常退出,WEXITSTATUS(status)获取退出状态码。

5.5 样例代码4:内核态释放进程资源(简化版)

内核模块,模拟进程退出时释放资源的核心逻辑 #include <linux/module.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/slab.h> static int __init proc_exit_demo_init(void) { struct task_struct *task = current; printk("当前进程(PID:%d):模拟退出前释放资源\n", task->pid); // 模拟释放进程资源(实际内核由do_exit()函数完成) if (task->mm) { printk("释放进程地址空间(mm_struct)\n"); // 内核中实际调用mmput()释放mm_struct } printk("模拟进程状态设置为僵尸态(EXIT_ZOMBIE)\n"); printk("通知父进程,等待回收\n"); return 0; } static void __exit proc_exit_demo_exit(void) { printk("进程退出演示模块卸载\n"); } module_init(proc_exit_demo_init); module_exit(proc_exit_demo_exit); MODULE_LICENSE("GPL");

运行结果:

当前进程(PID:1236):模拟退出前释放资源

释放进程地址空间(mm_struct)

模拟进程状态设置为僵尸态(EXIT_ZOMBIE)

通知父进程,等待回收

说明:内核中进程退出的核心函数是do_exit(),负责释放进程资源、设置僵尸态、通知父进程,本代码模拟了该过程的核心逻辑。


六、课堂练习

  1. 简述CFS调度与RT调度的核心区别,以及各自的适用场景。

  2. Linux进程调度的触发时机有哪些?

  3. 什么是僵尸进程?如何避免僵尸进程产生?

  4. CFS调度中,虚拟运行时间(vruntime)的作用是什么?

  5. 进程主动退出与被动终止的区别是什么?各自的触发场景有哪些?


七、课后作业

  1. 画图描述进程调度的完整流程,标注CFS与RT调度的优先级关系。

  2. 编写程序:创建两个RT进程(SCHED_RR策略),设置不同优先级,验证高优先级进程抢占低优先级进程。

  3. 编写程序:创建子进程后,父进程不调用wait(),观察子进程是否成为僵尸进程,并用ps命令查看。

  4. 简述进程销毁的完整流程,说明init进程在进程回收中的作用。

  5. 查阅资料,说明Linux内核中do_fork()(创建进程)与do_exit()(销毁进程)的核心关联。


八、本章总结

本章承接上一课进程创建的内容,详细讲解了进程管理子系统的核心后续流程——进程调度与销毁。重点介绍了CFS完全公平调度和RT实时调度两种核心策略,明确了两者的原理、适用场景及优先级差异;解析了进程调度的触发时机,说明内核如何合理分配CPU资源;同时讲解了进程销毁的两种方式、完整流程,以及僵尸进程、孤儿进程的产生与处理方法。

进程调度是保障系统高效、实时运行的关键,进程销毁是释放系统资源、避免资源泄漏的核心环节。通过本课学习,形成了完整的进程生命周期(创建→调度→运行→销毁)认知,掌握了进程调度与销毁的底层逻辑,为后续学习进程通信、内核调试、嵌入式实时系统优化打下坚实基础。


九、核心关键词

进程调度、CFS调度、RT调度、调度器、虚拟运行时间、时间片、进程销毁、僵尸进程、孤儿进程、exit()、waitpid()


第10课 课程回顾总结

本课作为进程管理子系统的第二部分,重点讲解了进程调度与销毁的核心机制,衔接上一课进程创建的内容,形成了完整的进程生命周期管理体系。课程首先明确了进程调度的核心目的的是合理分配CPU资源,保障系统公平性、高效性和实时性,介绍了调度器类的核心概念。随后详细解析了CFS完全公平调度和RT实时调度两种核心策略,对比了两者的原理、关键机制和适用场景,通过用户态与内核态样例代码,直观展示了调度策略的应用与验证方法。

课程还讲解了进程调度的四大触发时机,明确了调度器何时触发进程切换。在进程销毁部分,详细说明了主动退出与被动终止两种方式,拆解了进程销毁的完整流程,重点解释了僵尸进程与孤儿进程的产生原因、危害及处理方法,通过代码演示了父进程回收子进程的核心操作。

通过本课学习,掌握了进程调度与销毁的底层逻辑,理解了CFS与RT调度的区别,能通过代码分析调度机制、避免僵尸进程。本课知识是嵌入式Linux内核开发的重要基础,对理解系统资源管理、优化实时性、排查进程异常具有重要意义,为后续进程通信、驱动开发等内容奠定了基础。


上一节课答案:第9课 进程管理子系统(一):进程概念与创建 实战作业代码

进程创建综合实战(fork+写时复制验证+进程信息打印) #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <string.h> int main() { pid_t pid; char buf[32] = "父进程初始数据"; printf("【父进程】PID:%d,准备创建子进程,buf:%s\n", getpid(), buf); // 调用fork创建子进程,验证写时复制 pid = fork(); if (pid < 0) { perror("fork创建子进程失败"); return -1; } if (pid == 0) { // 子进程:修改buf,验证写时复制(修改后才复制页) strcpy(buf, "子进程修改后数据"); printf("【子进程】PID:%d,PPID:%d,buf:%s\n", getpid(), getppid(), buf); // 子进程执行完毕,主动退出 sleep(2); printf("【子进程】执行完毕,退出\n"); return 0; } else { // 父进程:不修改buf,查看数据是否被影响(验证COW) sleep(1); // 等待子进程修改数据 printf("【父进程】PID:%d,子进程PID:%d,buf:%s\n", getpid(), pid, buf); // 等待子进程退出,避免僵尸进程 wait(NULL); printf("【父进程】子进程已退出,父进程执行完毕\n"); } return 0; }

代码功能说明

该程序是第9课进程概念与创建的实战作业,核心验证fork创建子进程及写时复制(COW)机制。程序中父进程创建子进程后,子进程修改共享数据缓冲区,父进程不修改,通过打印缓冲区内容,验证写时复制的核心逻辑——只有当子进程修改数据时,内核才会复制对应内存页,父进程数据不受影响。同时程序打印父子进程PID、PPID,展示进程关系,父进程通过wait()回收子进程,避免僵尸进程。代码贴合课程重点,巩固进程创建、写时复制、进程关系等核心知识点,为后续进程调度、销毁学习打基础。

注意事项

  1. 编译命令:gcc fork_cow_demo.c -o fork_cow_demo

  2. 运行时需确保父进程等待子进程执行(sleep()函数),否则可能出现打印顺序混乱。

  3. 写时复制验证关键:子进程修改数据后,父进程数据应保持初始值,若两者数据一致,说明COW机制未生效(需检查内核配置)。

  4. 父进程必须调用wait()/waitpid()回收子进程,否则子进程会成为僵尸进程,可通过ps -ef | grep defunct查看。

  5. fork创建子进程后,父子进程执行顺序由调度器决定,sleep()函数仅用于控制打印顺序,不影响进程执行逻辑。

  6. 若fork失败,大概率是系统PID资源耗尽或权限不足,可通过ps -ef查看进程数量,结束无用进程后重试。

  7. 可使用strace ./fork_cow_demo 查看fork系统调用的执行过程,观察COW机制的底层调用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/24 20:28:19

AWS CLI终极指南:如何快速掌握Elemental Media系列媒体处理服务

AWS CLI终极指南&#xff1a;如何快速掌握Elemental Media系列媒体处理服务 【免费下载链接】aws-cli Universal Command Line Interface for Amazon Web Services 项目地址: https://gitcode.com/GitHub_Trending/aw/aws-cli AWS CLI&#xff08;Amazon Web Services C…

作者头像 李华
网站建设 2026/4/24 20:20:53

H3C设备GRE隧道配置实战:从静态路由到OSPF联动

1. GRE隧道基础概念与H3C设备适配 GRE&#xff08;Generic Routing Encapsulation&#xff09;是一种经典的三层隧道技术&#xff0c;它的工作原理就像给快递包裹套上一个透明的保护袋。想象一下&#xff0c;你有一份重要的纸质文件&#xff08;原始数据包&#xff09;需要跨城…

作者头像 李华
网站建设 2026/4/24 20:18:31

Jimeng AI Studio部署教程:NVIDIA驱动版本适配要求与CUDA环境检查脚本

Jimeng AI Studio部署教程&#xff1a;NVIDIA驱动版本适配要求与CUDA环境检查脚本 1. 引言&#xff1a;为什么部署前要先检查环境&#xff1f; 如果你正准备尝试Jimeng AI Studio这款轻量级的影像生成工具&#xff0c;我猜你已经迫不及待想看到它生成的第一张图片了。但请先别…

作者头像 李华