type
Post
status
Published
date
Jul 29, 2025
slug
2025/07/29/Linux-process-scheduler-process-switch
summary
tags
Linux进程调度器
category
Kernel
created days
new update day
icon
password
Created_time
Jul 29, 2025 03:44 AM
Last edited time
Jul 29, 2025 06:20 AM
Read the fucking source code!
--By 鲁迅
A picture is worth a thousand words.
--By 高尔基
说明:
- Kernel版本:4.14
- ARM64处理器,Contex-A53,双核
- 使用工具:Source Insight 3.5, Visio
1. 概述
进程切换:内核将CPU上正在运行的进程挂起,选择下一个进程来运行。
ARM架构中,CPU上一次只能运行一个任务,内核需要为任务分配运行时间来进行调度,以便同时能处理多个任务请求。
如下图所示:

当进行任务切换的时候,思考下两个问题:
- 怎样通过抢占来实现进程的切换?
- 当进程切换的时候,到底切换的什么,是怎么实现的?
这两个问题,也是本文探讨的主题了。
2. 抢占
2.1 用户抢占
2.1.1 抢占触发点
- 可以触发抢占的情况很多,比如进程的时间片耗尽、进程等待在某些资源上被唤醒时、进程优先级改变等。
- Linux 内核是通过设置
TIF_NEED_RESCHED
标志来对进程进行标记的,设置该位则表明需要进行调度切换,而实际的切换将在抢占执行点来完成。
不看代码来讲结论,那都是耍流氓。先看一下两个关键结构体:
struct task_struct
和struct thread_info
。我们在前边的文章中也讲过struct task_struct
用于描述任务,该结构体的首个字段放置的正是struct thread_info
,struct thread_info
结构体中flag
字段就可用于设置TIF_NEED_RESCHED
,此外该结构体中的preempt_count
也与抢占相关。struct task_struct { #ifdef CONFIG_THREAD_INFO_IN_TASK /* * For reasons of header soup (see current_thread_info()), this * must be the first element of task_struct. */ struct thread_info thread_info; #endif ... } /* * low level task data that entry.S needs immediate access to. */ struct thread_info { unsigned long flags; /* low level flags */ mm_segment_t addr_limit; /* address limit */ #ifdef CONFIG_ARM64_SW_TTBR0_PAN u64 ttbr0; /* saved TTBR0_EL1 */ #endif int preempt_count; /* 0 => preemptable, <0 => bug */ }; #include <asm/current.h> #define current_thread_info() ((struct thread_info *)current) //通过该宏可以直接获取thread_info的信息 #endif
看看具体哪些函数过程中,设置了
TIF_NEED_RESCHED
标志吧:
- 内核提供了
set_tsk_need_resched
函数来将thread_info
中flag
字段设置成TIF_NEED_RESCHED
;
- 设置了
TIF_NEED_RESCHED
标志,表明需要发生抢占调度;
2.1.2 抢占执行点
用户抢占:抢占执行发生在进程处于用户态时。
抢占的执行,最明显的标志就是调用了
schedule()
函数,来完成任务的切换。具体来说,在用户态执行抢占在以下几种情况:
- 异常处理后返回到用户态;
- 中断处理后返回到用户态;
- 系统调用后返回到用户态;
如下图:

- ARMv8有4个
Exception Level
,其中用户程序运行在EL0
,OS运行在EL1
,Hypervisor
运行在EL2
,Secure monitor
运行在EL3
;
- 用户程序在执行过程中,遇到异常或中断后,将会跳到
ENTRY(vectors)
向量表处开始执行;
- 返回用户空间时进行标志位判断,设置了
TIF_NEED_RESCHED
则需要进行调度切换,没有设置该标志,则检查是否有收到信号,有信号未处理的话,还需要进行信号的处理操作;
2.2 内核抢占
Linux内核有三种内核抢占模型,先上图:

CONFIG_PREEMPT_NONE
:不支持抢占,中断退出后,需要等到低优先级任务主动让出CPU才发生抢占切换;
CONFIG_PREEMPT_VOLUNTARY
:自愿抢占,代码中增加抢占点,在中断退出后遇到抢占点时进行抢占切换;
CONFIG_PREEMPT
:抢占,当中断退出后,如果遇到了更高优先级的任务,立即进行任务抢占;
在内核中抢占触发点,也是设置
struct thread_info
的flag
字段,设置TIF_NEED_RESCHED
表明需要请求重新调度。抢占触发点的几种情况,在用户抢占中已经分析过,不管是用户抢占还是内核抢占,触发点都是一致的。
2.2.2 抢占执行点
内核抢占:抢占执行发生在进程处于内核态时。

总体而言,内核抢占执行点可以归属于两大类:
- 中断执行完毕后进行抢占调度;
- 主动调用
preemp_enable
或schedule
等接口的地方进行抢占调度;
Linux内核中使用
struct thread_info
中的preempt_count
字段来控制抢占。preempt_count
的低8位用于控制抢占,当大于0时表示不可抢占,等于0表示可抢占。
preempt_enable()
会将preempt_count
值减1,并判断是否需要进行调度,在条件满足时进行切换;
preempt_disable()
会将preempt_count
值加1;
此外,
preemt_count
字段还用于判断进程处于各类上下文以及开关控制等,如图:
3. 上下文切换
进程上下文
:包含CPU的所有寄存器值、进程的运行状态、堆栈中的内容等,相当于进程某一时刻的快照,包含了所有的软硬件信息;
进程切换时,完成的就是上下文的切换
,进程上下文的信息会保存在每个struct task_struct
结构体中,以便在切换时能完成恢复工作;
进程上下文切换的入口就是
__schedule()
,分析也围绕这函数展开。3.1 __schedule()
__schedule()
函数调用分析如下:
主要的逻辑:
- 根据CPU获取运行队列,进而得到运行队列当前的
task
,也就是切换前的prev
;
- 根据
prev
的状态进行处理,比如pending
信号的处理等,如果该任务是一个worker线程
还需要将其睡眠,并唤醒同CPU上的另一个worker线程
;
- 根据调度类来选择需要切换过去的下一个
task
,也就是next
;
context_switch
完成进程的切换;
context_switch()
的调用分析如下:
核心的逻辑有两部分:
进程的地址空间切换
:切换的时候要判断切入的进程是否为内核线程,- 1)所有的用户进程都共用一个内核地址空间,而拥有不同的用户地址空间;
- 2)内核线程本身没有用户地址空间。
- 在进程在切换的过程中就需要对这些因素来考虑,涉及到页表的切换,以及
cache/tlb
的刷新等操作。
寄存器的切换
:包括CPU的通用寄存器切换、浮点寄存器切换,以及ARM处理器相关的其他一些寄存器的切换;
进程的切换,带来的开销不仅是页表切换和硬件上下文的切换,还包含了
Cache/TLB
刷新后带来的miss
的开销。在实际的开发中,也需要去评估新增进程带来的调度开销。参考文献
作者:LoyenWang
出处:https://www.cnblogs.com/LoyenWang/
公众号:LoyenWang
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
欢迎加入“喵星计算机技术研究院”,原创技术文章第一时间推送。

- 作者:tangcuyu
- 链接:https://expoli.tech/articles/2025/07/29/Linux-process-scheduler-process-switch
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章