Linux 进程调度(二)之进程的上下文切换
目录
- 一、概述
- 二、上下文切换的实现
- 1、context_switch
- 2、switch_mm
- 3、switch_to
- 三、观测进程上下文切换
一、概述
进程的上下文切换是指在多任务操作系统中,当操作系统决定要切换当前运行的进程时,将当前进程的状态保存起来,并恢复下一个要运行的进程的状态。上下文切换是操作系统实现进程调度和实现多任务的关键机制之一。
操作系统一个非常重要的功能就是进程的管理,通过调度策略选择合适的进程来执行,对于单个 CPU 而言,进程是串行分时执行,这就需要内核支持进程切换,挂起一个正在 CPU 中执行的进程,恢复执行之前挂起的进程。
CPU 和寄存器是所有进程共用的,CPU 在运行任何 task 之前,必须地依赖一些环境,包括 CPU 寄存器和程序计数器,除此之外,进程运行过程中还需要用到虚拟内存。进程在切换过程中,主要的工作就是切换进程空间(虚拟内存)切换 CPU 寄存器和程序计数器。
二、上下文切换的实现
进程切换由两部分组成:
- 切换页全局目录安装一个新的地址空间;
- 切换内核态堆栈及硬件上下文。
Linux 内核中由 context_switch 实现了上述两部分内容。
- 调用
switch_mm完成用户空间切换; - 调用
switch_to完成内核栈及寄存器切换。
1、context_switch
下面是上下文切换的内核源码,完整的源码见目录 kernel/sched/core.c 的 context_switch 函数:
static inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,struct task_struct *next)
{struct mm_struct *mm, *oldmm;prepare_task_switch(rq, prev, next);mm = next->mm; // 下一个要执行的进程的虚拟内存oldmm = prev->active_mm; // 将要被切换出去的进程的虚拟内存arch_start_context_switch(prev);if (!mm) { // 内核线程的 mm 为 NULLnext->active_mm = oldmm;atomic_inc(&oldmm->mm_count);enter_lazy_tlb(oldmm, next);} else // 用户进程的 mm 不为 NULLswitch_mm(oldmm, mm, next);if (!prev->mm) {prev->active_mm = NULL;rq->prev_mm = oldmm;}spin_release(&rq->lock.dep_map, 1, _THIS_IP_);context_tracking_task_switch(prev, next);switch_to(prev, next, prev); // 切换寄存器和内核栈barrier();return finish_task_switch(prev);
}
执行流程如下:
- 通过进程描述符
next->mm是否为空判断当前进程是否是内核线程,因为内核线程的内存描述符mm_struct *mm总是为空。 - 如果是内核线程则借用 prev 进程的
active_mm,对于用户进程,active_mm == mm;对于内核线程,mm = NULL,active_mm = prev->active_mm。 - 如果
prev->mm不为空,则说明 prev 是用户进程,调用 mmgrab 增加mm->mm_count引用计数。 - 对于内核线程,会启动懒惰 TLB 模式。懒惰 TLB 模式是为了减少无用的TLB刷新。
enter_lazy_tlb与体系结构相关。 - 如果是用户进程则调用
switch_mm(或switch_mm_irqs_off) 完成用户地址空间切换,switch_mm(或switch_mm_irqs_off) 与体系结构相关。 - 调用
switch_to完成内核态堆栈及硬件上下文切换,switch_to与体系结构相关。 switch_to执行完成后,next 进程获得 CPU 使用权,prev 进程进入睡眠状态。- 调用
finish_task_switch,如果 prev 是内核线程,则调用 mmdrop 减少内存描述符引用计数。如果引用计数为 0,则释放与页表相关的所有描述符和虚拟内存。
2、switch_mm
对于用户进程需要完成用户空间的切换,switch_mm 函数完成了这个任务。switch_mm 是与体系架构相关的函数。更确切地说,是切换地址转换表(pgd),由于 pgd 包括进程 系统空间(0xc000 0000 ~ 0xffff ffff)和 用户空间(0x0000 0000 ~ 0xbfff ffff)的地址映射,但是由于所有进程的系统空间的地址映射都是相同的。所以实质上就是进行用户空间的切换。
Linux 5.6.4 内核调用 switch_mm_irqs_off 切换用户进程空间,对于没有定义该函数的架构,则调用的是switch_mm。x86 体系架构定义了 switch_mm_irqs_off 函数,ARM 体系架构没有定义。
#ifndef switch_mm_irqs_off#define switch_mm_irqs_off switch_mm
#endif
函数定义为:
static inline void switch_mm( struct mm_struct * prev,struct mm_struct * next,struct task_struct * tsk){int cpu = smp_processor_id();if (likely(prev != next)) {cpu_clear(cpu, prev->cpu_vm_mask);
#ifdef CONFIG_SMPper_cpu(cpu_tlbstate, cpu).state = TLBSTATE_OK;per_cpu(cpu_tlbstate, cpu).active_mm = next;
#endifcpu_set(cpu, next->cpu_vm_mask);load_cr3(next->pgd); // 将下一个进程页表的 pgd 装载进 CR3 寄存器if (unlikely(prev->context.ldt != next->context.ldt))load_LDT_nolock(&next->context, cpu);}
#ifdef CONFIG_SMPelse {per_cpu(cpu_tlbstate, cpu).state = TLBSTATE_OK;BUG_ON(per_cpu(cpu_tlbstate, cpu).active_mm != next);if (!cpu_test_and_set(cpu, next->cpu_vm_mask)) {load_cr3(next->pgd); // 将下一个进程页表的 pgd 装载进 CR3 寄存器load_LDT_nolock(&next->context, cpu);}}
#endif
}
这部分核心的代码是 load_cr3,这个函数加载下一个进程页表 pgd 地址加载进 CR3 寄存器。CR3 是 CPU 的一个寄存器,它存储了当前进程的顶级页表 pgd。
如果 CPU 要使用进程的虚拟内存,内核可以从 CR3 寄存器里面得到 pgd 在物理内存的地址,通过页表就可以得到虚拟内存对应的物理地址,这样就可以得到物理内存的数据。
3、switch_to
对于内核空间及寄存器的切换,switch_to 函数完成了这个任务。
switch_to 调用到 __switch_to,该宏函数定义在目录 arch/x86/include/asm/switch_to.h:
#define switch_to(prev, next, last) \
do { \/* \* Context-switching clobbers all registers, so we clobber \* them explicitly, via unused output variables. \* (EAX and EBP is not listed because EBP is saved/restored \* explicitly for wchan access and EAX is the return value of \* __switch_to()) \*/ \unsigned long ebx, ecx, edx, esi, edi; \\asm volatile("pushfl\n\t" /* save flags */ \"pushl %%ebp\n\t" /* save EBP */ \"movl %%esp,%[prev_sp]\n\t" /* save ESP */ \"movl %[next_sp],%%esp\n\t" /* restore ESP */ \"movl $1f,%[prev_ip]\n\t" /* save EIP */ \"pushl %[next_ip]\n\t" /* restore EIP */ \__switch_canary \"jmp __switch_to\n" /* regparm call */ \"1:\t" \"popl %%ebp\n\t" /* restore EBP */ \"popfl\n" /* restore flags */ \\/* output parameters */ \: [prev_sp] "=m" (prev->thread.sp), \[prev_ip] "=m" (prev->thread.ip), \"=a" (last), \\/* clobbered output registers: */ \"=b" (ebx), "=c" (ecx), "=d" (edx), \"=S" (esi), "=D" (edi) \\__switch_canary_oparam \\/* input parameters: */ \: [next_sp] "m" (next->thread.sp), \[next_ip] "m" (next->thread.ip), \\/* regparm parameters for __switch_to(): */ \[prev] "a" (prev), \[next] "d" (next) \\__switch_canary_iparam \\: /* reloaded segment registers */ \"memory");
} while (0)
switch_to 宏用于进程切换,给定了前一个进程结构体指针 prev,以及需要切换到的进程结构体指针 next,从 prev 切换到 next。
prev 和 next 是输入参数,分别表示被替换进程和新进程描述符的地址在内存中的位置。而 last 是输出参数,假设内核决定暂停进程 A 而激活进程 B,而后又激活进程 A(则必须暂停另一个进程 C,通常不同于进程 B),则它表示宏把进程 C 的描述符地址写在内存的什么位置(在 A 恢复执行后)。
在进程切换之前,宏把第一个输入参数 prev(即在 A 的内核堆栈中分配的 prev 局部变量)表示的变量的内容存入 CPU 的 eax 寄存器。在完成进程切换,A 已经恢复执行时,宏把 CPU 的 eax 寄存器的内容写入由第三个输出参数 last 所指示的 A 在内存中的位置。因为 CPU 寄存器不会在切换点发生变化,所以 C 的描述符地址也存在内存的这个位置。在 schedule() 执行过程中,参数 last 指向 A 的局部变量 prev,所以 prev 被 C 的地址覆盖。

三、观测进程上下文切换
systemtap 提供了跟踪进程释放执行权被切换出 CPU 的 probe 方法 scheduler.cpu_off ,这个 probe 的定义
如下:
/*** probe scheduler.cpu_off - Process is about to stop running on a cpu* * @name: name of the probe point* @task_prev: the process leaving the cpu(same as current)* @task_next: the process replacing current* @idle: boolean indicating whether current is the idle process** Context: The process leaving the cpu.**/
probe scheduler.cpu_off =kernel.trace("sched_switch") !,kernel.function("context_switch")
{name ="cpu off"task_prev = $prevtask next = $nextidle = __is_idle()
}
可以看到 cpu_off 时间其实是 sched_switch 内核 trace 事件和 context_switch 内核函数的封装,同时提供了 task_prev 和 task_next 两个有用的参数。
task_prev 表示当前进程的 task struct 结构体,也就是马上要释放执行权的 task struct,task_next 表示马上要执行的进程的 task struct 结构体。
注意,这里的进程是广义的进程,也可以是线程,本质是一个
task struct。
我们就可以通过 cpu_off 事件来统计一段时间内的进程切换情况,完整的 systemtap 脚本如下所示:
global csw_countprobe scheduler.cpu_off {csw_count[task_prev,task_next]++
}function fmt_task(task_prev, task_next){return sprintf("tid(%d)->tid(%d)",task_tid(task_prev), task_tid(task_next))
}function print_context_switch_top5() {fprintf("%45s %10s\n", "Context switch", "COUNT")foreach([task_prev,task_next] in csw_count- limit 5) {printf("%45s %10d\n", fmt_task(task_prev, task_next), csw_count[task_prev, task_next])}delete csw_count
}probe timer.s(1) {print_context_switch_top5()printf("-----------------------------------------------\n")
}
其中 csw_count 是 systemtap 的关联数组,虽然这名字叫数组,其实是一个字典,跟其它语言的 map/dict/hash 类似。csw_count[task_prev,task_next] 语法的含义是将 task_prev 和 task_next 两个值联合起来为字典的 key。
如果我们由进程 A 切换到 B,B 切换到 C,C 切换到 A,那么这个关联数组的形式如下:
csw_count[AB]=1
csw_count[BC]=1
csw_count[CA]=1
接下来我们来执行 4 个跑满 CPU 的单线程程序,在我双核机器上每个程序会占据 50% 的 CPU 左右,开启四个终端,执行四次下面的程序:
$ sha256sum /dev/zero
top 命令的输出如下,这四个进程分别为 27458、27460、27590、27636。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
27460 root 20 0 116664 1140 856 R 50.8 0.1 0:35.12 sha256sum
27636 root 20 0 116664 1140 856 R 50.3 0.1 0:24.84 sha256sum
27458 root 20 0 116664 1140 856 R 49.7 0.1 0:36.18 sha256sum
27590 root 20 0 116664 1140 856 R 49.7 0.1 0:28.66 sha256sum
然后使用 stap 执行上面的 systemtap 脚本:
Context switch COUNT
tid(27460)->tid(27636) 62
tid(27636)->tid(27460) 62
tid(27590)->tid(27458) 44
tid(27458)->tid(27590) 43
tid(27458)->tid(25116) 10
可以看到,1s 内这四个进程切换得非常频繁。
相关文章:
Linux 进程调度(二)之进程的上下文切换
目录 一、概述二、上下文切换的实现1、context_switch2、switch_mm3、switch_to 三、观测进程上下文切换 一、概述 进程的上下文切换是指在多任务操作系统中,当操作系统决定要切换当前运行的进程时,将当前进程的状态保存起来,并恢复下一个要…...
Oracle事物临时表
在Oracle数据库中,事务临时表是一种特殊的表类型,主要用于存储在事务处理过程中产生的临时数据。这些表的数据只对当前会话或事务可见,并且在事务结束时会自动清除。 事务临时表的特点 生命周期: 事务临时表中的数据在事务提交或…...
看图学sql之sql的执行顺序
学完前面的内容,我们已经掌握了基本的sql语法了,那我们学的 select, distinct, from, where,group by, having, order by, limit 他们具体的执行顺序是什么样的呢? 语法: SELECT distinct column1, column2 FROM table1 join …...
百日筑基第四十五天-从JAVA8走到JAVA9
JAVA9新特性 Java 9发布于 2017 年 9 月 21 日 。 快速创建不可变集合 增加了List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合(有点参考 Guava 的味道): List.of("Java", "C"); Set.of…...
力扣第五十七题——插入区间
内容介绍 给你一个 无重叠的 ,按照区间起始端点排序的区间列表 intervals,其中 intervals[i] [starti, endi] 表示第 i 个区间的开始和结束,并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval [start, end] 表示另一个区…...
跟《经济学人》学英文:2024年08月03日这期 India’s economic policy will not make it rich
India’s economic policy will not make it rich A new World Bank report takes aim at emerging-market growth plans 原文: The developing world has fallen back in love with economic planning. As protectionism sweeps the West, poor countries are n…...
js 深拷贝、浅拷贝深度解析
赋值操作: let obj{a:1,b:[1,2,3],c:{m:2}}let newObjobjnewObj.a2newObj.b.push(4)newObj.c.m3console.log(obj,newObj); 将一个对象赋值给一个变量,其实就是将这个对象在栈内存中的引用地址复制给了这个变量,这两个对象指向堆内存中的同一个…...
CSS文本两端对齐
背景 如果我们要写了列表或表单类的样式,名称长短不一,但是想要两端对齐,如下面这样的: 你是怎么写的? 是这样的吗,在HTML里调整加空格: <ul><li>用户名</li><li>账 …...
C#中的foreach和自定义比较
在C#中foreach不能修改集合里面的值 在C#中,使用 foreach 循环遍历集合时,通常不建议修改集合中的元素,因为 foreach 循环是针对集合的枚举器进行操作的,而枚举器通常不支持修改集合中的元素。如果尝试在 foreach 循环中修改集合…...
有序转化数组(LeetCode)
题目 给你一个已经 排好序 的整数数组 和整数 、 、 。对于数组中的每一个元素 ,计算函数值 ,请 按升序返回数组 。 解题 在时间复杂度为解决问题 def sortTransformedArray(nums, a, b, c):def f(x):return a * x * x b * x cn len(nums)result…...
大数据信用报告查询有什么作用?怎么选择查询平台?
随着互联网的快速发展,人们的金融行为越来越多地依赖于网络平台。然而,网络上的金融交易存在着一定的风险,为了有效地防范这些风险,金融机构采用了大数据技术进行风险控制,下面,小易大数据平台将详细介绍大…...
import cv2ModuleNotFoundError: No module named ‘cv2‘
import cv2 ModuleNotFoundError: No module named cv2 (base) PS D:\CAMERA-D861T\LabelImg> pip3 install cv2 ERROR: Could not find a version that satisfies the requirement cv2 (from versions: none) ERROR: No matching distribution found for cv2 办法1 试了无…...
[Modbus] Modbus协议开发-基本概念(一)
历史 ModBus官网是Modicon(Modicon早年已被施耐德收购)公司为其PLC通讯而开发的一种通讯协议。 概述 通过Modbus协议,控制器之间、或控制器经由网络(如以太网)可以和其它设备之间进行通信。 优点 免费、好用、成熟…...
爬虫代理的使用:提升爬虫效率
爬虫代理的基本概念 爬虫代理,简单来说,就是位于客户端和目标服务器之间的一个中转站。当爬虫发起请求时,不是直接发送给目标服务器,而是先发送给代理服务器,再由代理服务器转发给目标服务器。目标服务器响应后&#…...
【gcc】基于gpt和python的流程和延迟梯度分析
Core Flow and Algorithm Concepts of GCC (Google Congestion Control) 【TWCC 】基于gpt和python简化分析webrtc拥塞控制论文: Analysis and Design of the Google Congestion Contro for Web Real-time Communication (WebRTC)参考大神的理解发送码率(send bitrate)影响了网…...
前端CSS总结
目录 前言 正文 CSS基础介绍: CSS选择器: 元素选择器: id和class选择器: 后代选择器和群组选择器: 盒子模型 content: padding: border: margin: 字体样式 …...
Linux/C 高级——指针函数
1.概念 本质是函数,函数的返回值为指针。类比着指针数组。 指针数组:本质是数组,数组中存放指针。 数据类型 *数组名[元素个数]; int a[2][3]; int *arr[2] {a[0],a[1]}; //*(*(arri)j) *(arr[i]j) arr[i][j] 2.定义格式 格式: 数…...
GRU门控循环单元【数学+图解】
文章目录 1、简介2、门控机制3、公式4、图解GRU4.1、重置门和更新门4.2、候选隐藏状态和隐藏状态⭐ 5、LSTM与GRU的对比6、应用7、训练技巧 🍃作者介绍:双非本科大三网络工程专业在读,阿里云专家博主,专注于Java领域学习ÿ…...
代码随想录算法训练营第六十一天|Bellman_ford 队列优化算法(又名SPFA)、bellman_ford之判断负权回路
卡码网:94. 城市间货物运输 I from collections import dequeclass Edge:def __init__(self, to, val):self.to to # 链接的节点self.val val # 边的权重def main():n, m map(int, input().split())grid [list() for _ in range(n 1)] # 初始化邻接表for _…...
ArrayList集合源码解读(二)已完结
ArrayList集合源码解读(二) 前言 这篇文章已经把 ArrayList 更完了。各位还想看什么源码可以私信我~~ 上节课带大家阅读了 ArrayList 中的核心扩容代码,那么今天带大家阅读下List集合中我们常用的几个方法的底层实现逻辑! 常用…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
恶补电源:1.电桥
一、元器件的选择 搜索并选择电桥,再multisim中选择FWB,就有各种型号的电桥: 电桥是用来干嘛的呢? 它是一个由四个二极管搭成的“桥梁”形状的电路,用来把交流电(AC)变成直流电(DC)。…...
智能职业发展系统:AI驱动的职业规划平台技术解析
智能职业发展系统:AI驱动的职业规划平台技术解析 引言:数字时代的职业革命 在当今瞬息万变的就业市场中,传统的职业规划方法已无法满足个人和企业的需求。据统计,全球每年有超过2亿人面临职业转型困境,而企业也因此遭…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能
指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...
