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

背景

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

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

一 进程挂起具体执行逻辑

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

线程挂起的时候,其中核心逻辑为-用户态主动执行操作系统提供的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!

附上一张简单的总结图:

相关推荐
Estar.Lee13 分钟前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
新知图书1 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
盛夏绽放1 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
Ares-Wang1 小时前
Asp.net Core Hosted Service(托管服务) Timer (定时任务)
后端·asp.net
OpenAnolis小助手3 小时前
开源生态发展合作倡议
开源·操作系统·龙蜥社区·龙蜥·openanolis
Rverdoser3 小时前
RabbitMQ的基本概念和入门
开发语言·后端·ruby
Tech Synapse3 小时前
Java根据前端返回的字段名进行查询数据的方法
java·开发语言·后端
.生产的驴3 小时前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
微信-since811924 小时前
[ruby on rails] 安装docker
后端·docker·ruby on rails
代码吐槽菌6 小时前
基于SSM的毕业论文管理系统【附源码】
java·开发语言·数据库·后端·ssm