Linux 是如何决定何时进行上下文切换的?

在Linux中,CPU 上下文切换是指当操作系统将 CPU 从一个进程切换到另一个进程时,保存当前进程的执行状态,并加载新进程的执行状态的过程就称为上下文切换。

但在 Linux 内核中,是否切换进程通常由一个关键标志位 TIF_NEED_RESCHED 来决定。

当该标志被设置时,内核会在合适的时机(例如从中断返回或系统调用结束时)调用schedule(),从而触发上下文切换。

TIF_NEED_RESCHED标志的设置过程

刚才我们提到切换进程或任务是由TIF_NEED_RESCHED标志位来判断,那这个标志位是如何设置的呢

接下来,我们将从 scheduler_tick() 函数开始,逐步揭示这个标志位是如何被设置,并最终触发上下文切换的。

1. scheduler_tick():调度的“心跳”

让我们先从**scheduler_tick()**函数开始 说起

scheduler_tick() 是由定时器中断(以 HZ 频率)触发的调度驱动函数。它会获取当前 CPU 上正在运行的进程,并调用该进程所属调度器的 task_tick() 方法,然后根据具体对应的调度策略判断是否需要重新调度。

// kernel/sched/core.c
void scheduler_tick(void)
{int cpu = smp_processor_id();struct rq *rq = cpu_rq(cpu);struct task_struct *curr = rq->curr;struct rq_flags rf;// ...rq_lock(rq, &rf); // 加锁以保护运行队列// ... 省略 ...// 这是核心调用:根据任务类型调用其对应的 task_tick 方法curr->sched_class->task_tick(rq, curr, 0);// ... 其他逻辑 ...rq_unlock(rq, &rf); // 解锁// ...
}

在上述代码中,最关键的一行是 curr->sched_class->task_tick(rq, curr, 0);

这行代码是调度器逻辑的入口,它根据当前运行任务 (curr) 所属的调度类 (sched_class),动态地调用其特有的 task_tick 方法。对于 CFS(完全公平调度器)任务,就会调用 task_tick_fair(),从而进入具体的调度决策流程。

2. task_tick_fair():CFS 的任务周期调度

刚才提到过每种调度策略都会通过一个sched_class结构体定义其行为,完全公平调度器(CFS)的调度类定义在**kernel/sched/fair.c** 文件中,它通过 DEFINE_SCHED_CLASS(fair) 结构体被绑定到 task_tick 接口上。

/* kernel/sched/fair.c */
DEFINE_SCHED_CLASS(fair) = {.enqueue_task       = enqueue_task_fair,.dequeue_task       = dequeue_task_fair,.yield_task         = yield_task_fair,....task_tick          = task_tick_fair,  // 👈 心跳函数绑定在这里....update_curr        = update_curr_fair,
};

那我们接着就看一下task_tick_fair函数

/** scheduler tick hitting a task of our scheduling class.** NOTE: This function can be called remotely by the tick offload that* goes along full dynticks. Therefore no local assumption can be made* and everything must be accessed through the @rq and @curr passed in* parameters.*/
static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
{struct cfs_rq *cfs_rq;struct sched_entity *se = &curr->se;for_each_sched_entity(se) {cfs_rq = cfs_rq_of(se);entity_tick(cfs_rq, se, queued);}if (static_branch_unlikely(&sched_numa_balancing))task_tick_numa(rq, curr);update_misfit_status(curr, rq);update_overutilized_status(task_rq(curr));task_tick_core(rq, curr);
}

首先CFS 把调度的最小单位抽象成 sched_entity,它即可以是线程也可以是进程组。每个sched_entity都会对应一个 运行队列

struct cfs_rq *cfs_rq;
struct sched_entity *se = &curr->se;

接着**for_each_sched_entity循环会一直向上遍历到最顶层的调度实体**(例如:线程 → 进程组 → 父组),并在每一层都调用 entity_tick() 函数。

for_each_sched_entity(se) {cfs_rq = cfs_rq_of(se);entity_tick(cfs_rq, se, queued);
}

3. entity_tick()update_deadline():判断是否“超时”

entity_tick() 函数中会调用 update_curr(),该函数会负责更新当前任务的运行时间统计信息,并在此过程中判断任务是否已超出其分配的时间片。

update_curr() 随后会调用 update_deadline(),这里便是我们寻找的触发点。该函数会更新当前任务的运行时间,并判断是否需要触发调度。我们先看一下update_deadline的具体代码

/** XXX: strictly: vd_i += N*r_i/w_i such that: vd_i > ve_i* this is probably good enough.*/
static void update_deadline(struct cfs_rq *cfs_rq, struct sched_entity *se)
{if ((s64)(se->vruntime - se->deadline) < 0)return;/** For EEVDF the virtual time slope is determined by w_i (iow.* nice) while the request time r_i is determined by* sysctl_sched_base_slice.*/se->slice = sysctl_sched_base_slice;/** EEVDF: vd_i = ve_i + r_i / w_i*/se->deadline = se->vruntime + calc_delta_fair(se->slice, se);/** The task has consumed its request, reschedule.*/if (cfs_rq->nr_running > 1) {resched_curr(rq_of(cfs_rq));clear_buddies(cfs_rq, se);}
}

当Linux中任务的虚拟运行时间超过其截止时间,并且运行队列 (cfs_rq) 中有其他可运行的任务时,就会认为当前任务的时间片已用完。此时就会调用 resched_curr() 从而设置TIF_NEED_RESCHED 标志位了

if (cfs_rq->nr_running > 1) {resched_curr(rq_of(cfs_rq));clear_buddies(cfs_rq, se); 
}

4. resched_curr():设置 TIF_NEED_RESCHED 标志

最后再看一下**resched_curr**的代码,代码在kernel/sched/core.c中

/** resched_curr - mark rq's current task 'to be rescheduled now'.** On UP this means the setting of the need_resched flag, on SMP it* might also involve a cross-CPU call to trigger the scheduler on* the target CPU.*/
void resched_curr(struct rq *rq)
{struct task_struct *curr = rq->curr;int cpu;lockdep_assert_rq_held(rq);if (test_tsk_need_resched(curr))return;cpu = cpu_of(rq);if (cpu == smp_processor_id()) {set_tsk_need_resched(curr); // 标记当前任务需要被调度set_preempt_need_resched(); // 触发抢占检查return;}if (set_nr_and_not_polling(curr))smp_send_reschedule(cpu);elsetrace_sched_wake_idle_without_ipi(cpu);
}

检查是否已标记 resched_curr函数首先会检查当前任务 (curr) 的 need_resched 标志是否已经被设置。如果已经被设置,说明任务已经被标记为需要调度就直接返回避免重复操作。

if (test_tsk_need_resched(curr))return;

处理当前对应CPU核心上的调度

这段代码检查 resched_curr 是否在当前 CPU 上被调用。如果是,它会执行两个关键步骤:

  • set_tsk_need_resched(curr): 显式地设置当前任务的 TIF_NEED_RESCHED 标志。
  • set_preempt_need_resched(): 告诉抢占机制,在下一个安全点(例如从中断返回或系统调用结束时),应该检查该标志并立即进行一次上下文切换。
if (cpu == smp_processor_id()) {set_tsk_need_resched(curr); // 标记当前任务需要被调度set_preempt_need_resched(); // 触发抢占检查return;
}

总结:need_resched 的调用链

现在,我们可以清晰地梳理出 TIF_NEED_RESCHED 标志的完整设置流程:

这个调用链展示了 Linux 内核如何利用一个周期性的定时器中断,结合 CFS 调度器的公平性原则,最终实现了抢占式多任务的调度核心。

完整代码参考: 完整的源码可以在 Linux 内核源码的 kernel/sched/fair.ckernel/sched/core.c 文件中找到这些函数的完整实现。

linux/kernel/sched/fair.c at master · torvalds/linux · GitHublinux/kernel/sched/core.c at master · torvalds/linux · GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921851.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921851.shtml
英文地址,请注明出处:http://en.pswp.cn/news/921851.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis 深度解析:数据结构、持久化与集群

Redis (Remote Dictionary Server) 是一种高性能的键值&#xff08;Key-Value&#xff09;内存数据库&#xff0c;以其丰富的数据结构、极低的延迟、出色的稳定性和强大的集群能力&#xff0c;在现代应用程序的开发中扮演着至关重要的角色。无论是作为缓存、消息队列、会话存储…

HTTPS优化简单总结

性能损耗选择椭圆曲线&#xff0c;并生成椭圆曲线的计算耗时CA证书验证的耗时计算pre-master的耗时硬件优化HTTPS是计算密集型任务&#xff0c;不是IO密集型任务所以硬件最好买更高级的CPU&#xff0c;而不是网卡&#xff0c;磁盘协议优化ECDHE代替RSA&#xff0c;因为ECDHE可以…

从IFA再出发:中国制造与海信三筒洗衣机的“答案”

当全球消费电子行业的目光再次聚焦柏林&#xff0c;柏林国际电子消费品展览会(IFA2025)不仅成为创新产品的秀场&#xff0c;更悄然变身为中国企业讲述全球化进阶故事的重要舞台。近日&#xff0c;海信旗下三筒洗衣机——棉花糖Ultra全家筒迎来它的国际首秀&#xff0c;首次海外…

c++工程如何提供http服务接口

在 C 工程里给类似 /index/api/ 的服务&#xff0c;基本步骤如下&#xff1a; 选一个HTTP服务框架&#xff1b;起一条监听线程&#xff08;或线程池&#xff09;&#xff1b;把路径-处理函数注册进去&#xff1b; 下面是 2 种简单的方案。方案 A&#xff1a;Crow&#xff08;He…

cfshow-web入门-php特性

web89 <?php ​ include("flag.php"); highlight_file(__FILE__); ​ if(isset($_GET[num])){$num $_GET[num];if(preg_match("/[0-9]/", $num)){die("no no no!");}if(intval($num)){echo $flag;} } 正则匹配检查不能是数字&#xff0c;但…

ctfshow - web - 命令执行漏洞总结(二)

web73该题目没有开启web72的open_basedir&#xff0c;所以可以使用var_export(scandir(/));exit();进行目录扫描。读取文件函数&#xff1a;require_once()web74scandir()函数被禁用&#xff0c;使用glob://伪协议进行读取根目录文件。cvar_export(glob(../../../*));exit(); c…

如何将视频从安卓手机传输到电脑?

无论你是否是视频爱好者&#xff0c;你可能都希望知道如何将视频从安卓手机传输到电脑&#xff0c;以释放存储空间并防止性能问题。这也有助于同步视频或防止意外删除。在本文中&#xff0c;我们将探索七种高效的传输方法。方法 1&#xff1a;仅通过 USB 将手机视频发送到电脑许…

Pico 4 Enterprise(企业版)与Unity的交互-有线串流调试篇

入手了Pico 4 E做VR开发&#xff0c;谁知入了天坑...根据官方文档&#xff0c;尝试了串流助手、企业串流、PICO Developer Center&#xff0c;陷入了各种版本问题、环境问题的陷阱。而且Pico4E的OS自24年12开始就不再更新&#xff0c;头盔中预装的企业串流版本也较低&#xff0…

redis里多线程的应用具体在哪些场景

Redis 6.0 引入的多线程I/O&#xff0c;​特指用于处理网络数据的读取&#xff08;read&#xff09;和写入&#xff08;write&#xff09;/解析&#xff08;parse&#xff09;的并行化&#xff0c;而绝非将命令的执行&#xff08;真正的数据操作&#xff09;变成多线程。这是一…

DI-GAN:基于深度学习的动态形变多模光纤透反射光控制

DI-GAN:基于深度学习的动态形变多模光纤透反射光控制 1 论文核心概念 本文提出了一种名为 DI-GAN(Deep Imaging Generative Adversarial Network) 的持续深度学习框架,用于动态形变多模光纤(MMF) 的光场控制。该框架能够同时利用透射和反射信息,实现对光纤末端光场的实…

【深度学习新浪潮】具身智能中使用到的世界模型是什么?

在具身智能中,世界模型(World Model) 是智能体对物理环境的内在“认知地图”,它通过学习环境的动态规律(如物体运动、物理交互、因果关系等),实现对未来状态的预测、对过去状态的反推,以及对未观测状态的补全。其核心价值在于:让智能体无需频繁与真实环境交互,就能在…

Qt_UI界面的设计

一、设置UI窗口大小二、接收框只读三、下拉选项双击添加选项1是添加&#xff0c;2是调整顺序四、标签字体居中字体大小五、发送框六、按钮七、透明框&#xff08;可以放标签或图片啥的&#xff09;设置最小宽度八、水平布局九、垂直布局十、弹簧&#xff08;方便给水平垂直布局…

FTP文件传输服务

一、FTP协议、服务器FTP&#xff1a;文件传输协议&#xff08;用于网络文件双向传输的应用层协议&#xff09;特点&#xff1a;最广泛、最底层、较简单&#xff0c;但是明文传输&#xff1b;适用于较大文件的传输1.常见客户端、服务器客户端&#xff1a;WINSCP or filezilla&am…

Nginx运维之路(Docker多段构建新版本并增加第三方模块)

喜大普奔&#xff0c;前两天发现Nginx竟然自带支持了ACME功能&#xff0c;让我很想测试一下&#xff0c;但是发现手头没有资源让我测试&#xff0c;忽然我想到可以用docker来构建nginx然后测试ACME功能&#xff0c;在这个过程中发现原来官方Nginx镜像并没有集成ACME插件&#x…

DrissionPage 优化天猫店铺商品爬虫:现代化网页抓取技术详解

概述在网络数据采集领域&#xff0c;传统的爬虫方法通常面临反爬机制、动态内容加载和效率低下等挑战。本文将以天猫店铺商品爬虫为例&#xff0c;详细介绍如何从传统的 Requests 库迁移到更现代化的 DrissionPage 解决方案&#xff0c;实现更高效、稳定的数据采集。----------…

pytest并发测试,资源问题导致用例失败解决办法

遇见的问题&#xff1a; 测试用例使用thrift资源和redis资源&#xff0c;单独运行case没有问题&#xff0c;但是使用并发pytest-xdist&#xff08;-n 10 和 --distloadscope&#xff09;运行失败原因&#xff1a; 测试用例间存在共享资源竞争&#xff08;如 Redis、Thrift 连接…

C 盘又满了?6 个「零风险清理法」+5 款神器,让电脑瞬间多出 100GB 空间

你是否遇到过这样的场景&#xff1a;正在赶工写报告&#xff0c;突然弹出「C 盘存储空间不足」的警告&#xff1b;想安装新软件&#xff0c;却因为 C 盘爆红而反复失败&#xff1b;甚至电脑越来越卡&#xff0c;开机要等 5 分钟&#xff0c;打开文件夹都要转圈…… 作为系统盘…

Android 项目:画图白板APP开发(四)——笔锋(单 Path)

上一章讲解了如何通过多个 Path 叠加形成笔锋效果&#xff0c;还有另外的方式实现笔锋&#xff0c;并且只需要一条Path就可以了。在讲解具体方案之前&#xff0c;我们需要了解一个有意思的工具 PathMeasure &#xff0c;这是一个非常强大且实用的工具&#xff0c;常用于高级动画…

从C++开始的编程生活(7)——取地址运算符重载、类型转换、static成员和友元

前言 本系列文章承接C语言的学习&#xff0c;需要有C语言的基础才能学会哦~ 第7篇主要讲的是有关于C的取地址运算符重载、类型转换、static成员和友元。 C才起步&#xff0c;都很简单 目录 前言 取地址运算符重载 const成员函数 基本语法 特点 取地址运算符重载 类型转换…

SQL 入门指南:排序与分页查询(ORDER BY 多字段排序、LIMIT 分页实战)

在 SQL 查询中&#xff0c;我们常需要 “按报名时间先后看活动名单”“只看第 2 页的活动报名数据”—— 这些需求靠基础查询无法实现&#xff0c;而ORDER BY&#xff08;排序&#xff09; 和LIMIT&#xff08;分页&#xff09; 就是解决这类问题的核心工具。今天我们用 “校园…