输出倒逼输入系列之线程上下文切换的知识

背景

继上次讲完了什么是线程"等待"状态(即操作系统线程挂起状态),这里想再详细讲一讲,线程挂起的时候,背后具体会进行哪些操作

这里我们主要分三个部分来进行阐述

一 进程挂起具体执行逻辑

操作系统提供分层的权限机制,把区域分为 用户态 和 内核态

线程挂起的时候,其中核心逻辑为-用户态主动执行操作系统提供的schedule() 方法,从用户态进入内核态

1 用户态的寄存器信息据压入 内核栈

操作系统寄存器:

CPU 是真正干活的,它干活时用很多寄存器来存储它的控制信息,计算信息,以及数据信息 数据信息:通用寄存器 来存储计算过程中暂存数据

控制信息:

CS :代码段寄存器,代码在内存的初始位置

DS: 数据段的寄存器,初始数据等存储位置

SS: 栈寄存器,函数调用栈存储位置

IP:指令指针寄存器

ESP: 栈顶指针寄存器

代码段的偏移量在 IP 寄存器中,数据段的偏移量会放在 通用寄存器中

业务开发的时候,会经常碰到函数调用,这里详细解释一下用户态函数栈

用户态函数栈 SS:

栈帧如图:

组成机构:

1 上一个 栈帧的 ebp 地址

2 局部变量

3 调用方法所用的参数

4 返回地址

这里可以重点了解下:

假如 A 调用了 B,B 通过它的栈帧中的上一个栈帧的EBP地址,找到 A 传进来的参数,且当 B 运行完毕,返回值会保存在 EAX 寄存器中

把上述用户态所有寄存器信息全部压入 内核stack

内核栈 stack 数据结构:

如图:

pt_regs 里有当时用户态运行的所有上下文信息

内核 stack最关键的作用,其实就是用来保存此时这个用户态的上下文。且当执行完操作系统的调用函数时,可以从这里拿取信息恢复用户态上下文,继续运行上次停下的逻辑

2 从队列中选出继任者进程

调度任务策略:

先进先出

轮流调度

完全公平等策略

这里主要介绍,普通进程的安全公平策略

首先每一个任务都有一个 vruntime ,vruntime 代表可分配的运行时间。把任务想关信息封装成一个 scheduler_entity,再放进红黑树进行排序,调度时每次选出最小的 vruntime 任务,放入CPU去执行

CPU 运行时:

线程挂起的时候,从 CPU的相应的队列中,选出下一个任务

每一个 CPU都有一个rq ,rq 里分实时队列和 cfs 队列,实时进程任务信息放在 实时队列中,普通进程任务信息放在 cfs 队列中。

且在 task_struct 数据结构里,有对应的策略 rt,与策略Entity,这样可相互引用

如图:

3 切换 CPU 上下文以及进程空间

读取下一个任务的task_struct,切换成下一个选中任务的上下文以及进程空间

而刚刚运行的任务的相关寄存器信息,CPU自动存储到 TSS中

TSS:

每个 进程都有一个 TSS (Task State Segement,任务状态段),这里面有所有的寄存器

CPU 运行时。

还有一个 特殊的寄存器 TR (Task Register,任务寄存器),指向某个进程的 TSS,更改 TR的值时,将会触发硬件保存CPU所有寄存器的值到当前进程的 TSS中,然后从新进程的 TSS中读出所有的寄存器,加载到CPU对应的寄存器中

4 真正睡着

进入调度队列中,之前的任务没有了 CPU执行,只是变成一个 数据结构

5 等待调度,被唤醒

沉睡的任务等待被调度策略选中,读取 它的TSS 信息,然后获得CPU去运行,且从 schedule() 方法返回

其实也可以理解成,回到了 步骤 2 从队列中选出继任者进程

二 主动引发的挂起

主动调用操作系统提供的挂起的方法 - scheduler()

如:IO 操作的时候,让出 CPU

上一节文章提到的 所有挂起的方法,如 park(),sleep() 等

三 被动引发的挂起

被动挂起场景

1 调度策略

当前 CPU 运行中的进程,运行的时间太长,被 定时的 tick函数检测到需要休息

2 有优先级更高的任务

刚刚被唤醒的进程任务,优先级比当前运行的任务优先级高,则需要被换下来

注意⚠️:被动引发的挂起,并不立即执行,而是先打上 rescheduler 标签,等待时机执行 scheduler()函数

等待时机

1 从内核态返回 用户态时

2 从中断返回时

总结

一个简单的线程休息场景,在操作系统层面居然有这么多复杂的设计,细细咀嚼,别有一番风味,bingo!

附上一张简单的总结图:

相关推荐
CryptoPP12 分钟前
springboot 对接马来西亚数据源API等多个国家的数据源
spring boot·后端·python·金融·区块链
Source.Liu26 分钟前
【学Rust写CAD】27 双线性插值函数(bilinear_interpolation.rs)
后端·rust·cad
yinhezhanshen30 分钟前
理解rust里面的copy和clone
开发语言·后端·rust
uhakadotcom37 分钟前
Helm 简介与实践指南
后端·面试·github
栗筝i1 小时前
Spring 核心技术解析【纯干货版】- XIX:Spring 日志模块 Spring-Jcl 模块精讲
java·后端·spring
企鹅不耐热.1 小时前
Scala基础知识6
开发语言·后端·scala
程序员一诺2 小时前
【Django开发】前后端分离django美多商城项目第15篇:商品搜索,1. Haystack介绍和安装配置【附代码文档】
后端·python·django·框架
冷琅辞2 小时前
Go语言的嵌入式网络
开发语言·后端·golang
跟着珅聪学java4 小时前
spring boot +Elment UI 上传文件教程
java·spring boot·后端·ui·elementui·vue
徐小黑ACG5 小时前
GO语言 使用protobuf
开发语言·后端·golang·protobuf