深入理解Linux内核(第三版)- 进程切换

news2024/11/25 14:49:54

为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换(process switch)、任务切换(task switch)或上下文切换(context switch)。

硬件上下文

尽管每个进程可以拥有自己的地址空间,但所有进程必须共享CPU寄存器。因此,在恢复一个进程的执行之前,内核必须确保每个寄存器装入了挂起进程时的值。

进程恢复执行前必须装入寄存器的一组数据称为硬件上下文(hardware context)。硬件上下文是进程可执行上下文的一个子集,因为可执行上下文包含进程执行时需要的所有信息。在Linux中,进程硬件上下文的一部分存放在TSS段,而剩余部分存放在内核态堆栈中。

在下面的描述中,我们假定用prev局部变量表示切换出的进程的描述符,next表示切换进的进程的描述符。因此,我们把进程切换定义为这样的行为:保存prev硬件上下文,用next硬件上下文代替prev。

进程切换只发生在内核态。在执行进程切换之前,用户态进程使用的所有寄存器内容都已经保存在内核态堆栈上,这也包括ss和esp这对寄存器的内容(存储用户态堆栈指针的地址)。

任务状态段

80x86体系结构包括了一个特殊的段类型,叫任务状态段(Task State Segment,TSS)来存放硬件上下文。尽管Linux并不使用硬件上下文切换,但是强制它为系统中每个不同的CPU创建一个TSS。这样做出于两个目的:

  • 当80x86的一个CPU从用户态切换到内核态时,它就从TSS中获取内核态堆栈的地址。
  • 当用户态试图通过in或out指令访问一个I/O端口时,CPU需要访问存放在TSS中的I/O许可权位图(Permission Bitmap)以检查该进程是否有访问端口的权力。

更确切的说,当进程在用户态下执行in或out指令时,控制单元执行下列操作:

  1. 它检查eflags寄存器中的2位IOPL字段。如果该字段值为3,控制单元就执行I/O指令。否则,执行下一个检查。
  2. 访问tr寄存器以确定当前的TSS和相应的I/O许可权位图。
  3. 检查I/O指令中指定端口在I/O许可权位图中对应的位。如果该位清零,这条I/O指令就执行,否则控制单元产生一个"General protection"异常。

tss_struct结构描述TSS的格式。init_tss数组为系统上每个不同的CPU存放一个TSS。在每次进程切换时,内核都更新TSS的某些字段以便相应的CPU控制单元可以安全地检索到它需要的信息。因此,TSS反映了CPU上的当前进程的特权级,但不必为没有在运行的进程保留TSS。

每个TSS都有它自己的8字节的任务状态段描述符(Task State Segment Descriptor,TSSD)。这个描述符包括指向TSS起始地址的32位Base字段,20位Limit字段。TSSD的S标志位被清零,以表示相应的TSS是系统段。

Type字段置为11或9以表示这个段实际上是一个TSS。在Intel的原始设计中,系统中的每个进程都应当指向自己的TSS,Type字段的第二个有效位叫做Busy位;如果进程正由CPU执行,则该位置1,否则该位置0。在Linux的设计中,每个CPU只有一个TSS,因此,Busy位总置1。

由Linux创建的TSSD存放在全局描述符表(GDT)中,GDT的基地址存放在每个CPU的gdtr寄存器中。每个CPU的tr寄存器包含相应TSS的TSSD选择符,也包含了两个隐藏的非编程字段:TSSD的Base字段和Limit字段。这样,处理器就能直接对TSS寻址而不用从GDT中检索TSS的地址。

thread字段 

在每次进程切换时,被替换进程的硬件上下文必须保存在别处。不能向Intel原始设计那样把它保存在TSS中,因为Linux为每个处理器而不是为每个进程使用TSS。

因此,每个进程描述符包含一个类型为thread_struct的thread字段,只要进程被切换出去,内核就把其硬件上下文保存在这个结构中。这个数据结构包含的字段涉及大部分CPU寄存器,但不包括诸如eax、ebx等等这些通用寄存器,它们的值保存在内核堆栈中。

执行进程切换

进程切换可能只发生在精心定义的点:schedule()函数。这里,我们仅关注内核如何执行一个进程切换。

从本质上说,进程切换由两步组成:

1、切换页全局目录以安装一个新的地址空间

2、切换内核态堆栈和硬件上下文,因为硬件上下文提供了内核执行新进程所需要的所有信息,包含CPU寄存器。

switch_to 宏

进程切换的第二步由switch_to宏执行。它是内核中与硬件关系最密切的例程之一。

首先,该宏有三个参数,它们是prev,next和last。我们很容易猜到prev和next的作用:它们仅是局部变量prev和next的占位符,即它们是输入参数,分别表示被替换进程和新进程描述符的地址在内存中的位置。

那第三个参数last呢?在任何进程切换中,涉及到三个进程而不是两个。假设内核决定暂停进程A而激活进程B。在schedule( )函数中,prev指向A的描述符而next指向B的描述符。switch_to宏一但使A暂停,A的执行流就冻结。

随后,当内核想再次此激活A,就必须暂停另一个进程C(这通常不同于B),于是就要用prev指向C而next指向A来执行另一个switch_to宏。当A恢复它的执行流时,就会找到它原来的内核栈,于是prev局部变量还是指向A的描述符而next 指向B的描述符。此时,代表进程A执行的内核就失去了对C的任何引用。但是,事实表明这个引用对于完成进程切换是很有用的。

switch_to宏的最后一个参数是输出参数,它表示宏把进程C的描述符地址写在内存的什么位置了(当然,这是在A恢复执行之后完成的)。在进程切换之前,宏把第一个输入参数prev(即在A的内核堆栈中分配的prev局部变量)表示的变量的内容存入CPU的eax寄存器。在完成进程切换,A已经恢复执行时,宏把CPU的eax寄存器的内容写入由第三个输出参数——last所指示的A在内存中的位置。因为CPU寄存器不会在切换点发生变化,所以C的描述符地址也存在内存的这个位置。在schedule()执行过程中,参数last 指向A的局部变量prev,所以prev被C的地址覆盖。

下图1显示了进程A,B,C内核堆栈的内容以及eax寄存器的内容。必须注意的是:图中显示的是在被eax寄存器的内容覆盖以前的prev局部变量的值。

 图1:通过一个进程切换保留对进程C的引用

由于switch_to宏采用扩展的内联汇编语言编码,所以可读性比较差:实际上这段代码通过特殊位置记数法使用寄存器,而实际使用的通用寄存器由编译器自由选择。我们将采用标准汇编语言而不是麻烦的内联汇编语言来描述switch_to宏在80x86微处理器上所完成的典型工作。

1、在eax和edx寄存器中分别保存prev和next的值:

movl prev, %eax
movl next, %edx

2、把eflags和ebp寄存器的内容保存在prev内核栈中。必须保存它们的原因是编译器认为在switch_to结束之前它们的值应当保持不变。

pushfl
pushl %ebp

3、把esp的内容保存到prev->thread.esp中以使该字段指向prev内核栈的栈顶:

movl %esp, 484(%eax)

484(%eax)操作数表示内存单元的地址为eax内容加上484。

4、把next->thread.esp装入esp。此时,内核开始在next的内核栈上操作,因此这条指令实际上完成了从prev到next的切换。由于进程描述符的地址和内核栈的地址紧挨着,所以改变内核栈意味着改变当前进程。

movl 484(%edx), %esp

5、把标记为1的地址存入prev->thread.eip。当被替换的进程重新恢复执行时,进程执行被标记为1的那条指令:

movl $1f, 480 (%eax)

6、宏把next->thread.eip的值(绝大多数情况下是一个被标记为1的地址)压入next 的内核栈:

pushl 480(%edx)

7、跳到__switch_to () C函数(见下面):

jmp_ _switch_to

8、这里被进程B替换的进程A再次获得CPU:它执行一些保存eflags和ebp寄存器内容的指令,这两条指令的第一条指令被标记为1。

1:
    popl %ebp
    popfl

注意这些pop指令是怎样引用prev进程的内核栈的。当进程调度程序选择了prev作为新进程在CPU上运行时,将执行这些指令。于是,以prev作为第二个参数调用switch_to。因此,esp寄存器指向prev的内核栈。

9、拷贝eax寄存器(上面步骤1中被装载)的内容到switch_to宏的第三个参数last标识的内存区域中:

movl %eax, last

正如先前讨论的,eax寄存器指向刚被替换的进程的描述符(当前执行的schedule()函数重新使用了prev局部变量,于是汇编语言指令就是: movl %eax , prev)


参考文献:《深入理解Linux内核(第三版)》  中国电力出版社

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手把手教你Java实现栈和队列

目录 一、栈(Stack) 1、概念 2、栈的使用 3、栈的模拟实现 4、栈的应用场景 2. 队列(Queue) 1、概念 2、队列的使用 3、队列模拟实现 4、循环队列 三、双端队列 (Deque) 五、栈和队列的互相实现 用队列实现栈: 用栈实现队列: 一、栈(St…

【剑指offer】(2)

系列文章目录 剑指offer系列是一本非常著名的面试题目集,旨在帮助求职者提升编程能力和应对面试的能力。 文章目录 系列文章目录[TOC](文章目录) 前言一、 用两个栈实现队列🔥 思路🌈代码 二、青蛙跳台阶问题🔥 思路&#x1f308…

git从入门到卸载

git是什么? 从git的官网Git可以找到: Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency. Git is easy to learn and has a tiny footpr…

SANGFOR防火墙如何查看现网运行参数

环境: 防火墙 8.0.48 AF-1000BB1510 问题描述: 公司防火墙设备使用2年多了 AF-2000-FH2130B-SC;性能参数:网络层吞吐量:20G,应用层吞吐量:9G,防病毒吞吐量:1.5G,IPS吞…

python基础实战4-python基础语法

1、注释(Comments) 注释用来向用户提示或解释某些代码的作用和功能,它可以出现在代码中的任何位置。 Python解释器在执行代码时会忽略注释,不做任何处理,就好像它不存在一样。 1.1 代码注释介绍 注释就是对代码的解…

计算机组成原理 指令系统(1)

本文是HIT计算机组成原理上课笔记,由于唐书有些内容讲的比较抽象,添加了一些王道的图片加以补充。 回忆计算机的工作过程 代码被编译器翻译成与之对等的机器指令,除了指令之外还会有一些数据同时被放到主存里 机器指令 指令格式 一条指令是…

第十四章 代理模式

文章目录 前言一、静态代理完整代码接口 ITeacherDao (代理类和被代理类都需要实现这个接口)被代理类 TeacherDao代理类 TeacherDaoProxy测试类 Client 二、JDK动态代理完整代码接口 ITeacher实现类TeacherDao代理工厂 ProxyFacyoryclient 测试 三、Cgli…

Java阶段二Day09

Java阶段二Day09 文章目录 Java阶段二Day09DQLSELECT基础查询全部查询WHERE子句连接多个条件ORDER BY子句分页查询在SELECT子句中使用函数在WHERE中使用表达式别名聚合函数 教师总结DQL语言-数据查询语言语法基础查询语法例 WHERE子句例连接多个条件例AND的优先级高于OR IN(列表…

vue使用原生bootstrap-fileinput无效(未解决)

这篇只记录一下踩到的坑,由于时间关系,此问题未解决 起因:要求替换项目框架,原先jq要替换成vue。之前bootstrap中自带的文件上传插件自带很多功能,上传进度条、上传内容预览等非常方便(如图)&a…

Netty核心源码分析(四)心跳检测源码分析

文章目录 系列文章目录一、心跳检测案例二、源码分析1、Netty心跳的三个Handler2、IdleStateHandler源码(1)四个关键属性(2)handlerAdded方法(3)四个内部类 3、读事件的run方法——ReaderIdleTimeoutTask4、…

easyrecovery16最新数据恢复软件密钥使用方法教程

easyrecovery是一款专业的数据恢复软件,其最新版本为easyrecovery2023将于2022年底发布。总之,easyrecovery是一款功能齐全、性能稳定的专业数据恢复软件,无论删除文件、格式化分区或磁盘故障,它都可以提供最高的恢复成功率。值得个人用户选用。此版本在功能和性能上有较大提升…

支持中英双语和多种插件的开源对话语言模型,160亿参数

一、开源项目简介 MOSS是一个支持中英双语和多种插件的开源对话语言模型,moss-moon系列模型具有160亿参数,在FP16精度下可在单张A100/A800或两张3090显卡运行,在INT4/8精度下可在单张3090显卡运行。MOSS基座语言模型在约七千亿中英文以及代码…

HTB靶机-Lame-WP

Lame 简介: Lame is a beginner level machine, requiring only one exploit to obtain root access. It was the first machine published on Hack The Box and was often the first machine for new users prior to its retirement Tags: Injection, C…

Midjourney 注册 12 步流程教学

原文: https://bysocket.com/midjourney-register/ 先推荐一个 PromptHero 中文官网 https://promptheroes.cn/ :Prompt Heroes 官网是提供 AI 绘画相关提示词中文网站,包括 Midjourney(MJ)、 Stable Diffusion、DALL…

printf,echo,cat指令与输出重定向>,输入重定向<与追加重定向>>等

printf指令的功能(输出/追加重定向) 语法:printf “格式化数据” (>/>>重定向)功能:格式化输出(默认往显示器文件且不带换行符) 实例演示 echo指令的功能(输出/追加重定向) 语法&am…

使用chatgpt分析 too many open files 问题-未验证

java.io.IOException: Too many open files 怎么能定位到时哪行代码出的问题 ? 2023/4/25 19:46:33 当出现类似 "java.io.IOException: Too many open files" 的错误时,通常是因为程序打开了过多的文件句柄(File Handles&#xff…

【操作系统】第四章 文件管理

文章目录 知识体系4.1 文件系统基础4.1.1 文件的基本概念4.1.2 文件控制块和索引节点4.1.3 文件的操作4.1.4 文件保护4.1.5 文件的逻辑结构4.1.6 文件的物理结构 4.2 目录4.2.1 目录的基本概念4.2.2 目录结构4.2.3 目录的操作*4.2.4 目录实现4.2.5 文件共享 4.3 文件系统4.3.1 …

快速部署和测试API:使用APIfox的实战经验分享

最近发现一款接口测试工具--apifox,我我们很难将它描述为一款接口管理工具 或 接口自测试工具。 官方给了一个简单的公式,更能说明apifox可以做什么。 20分钟学ApiFox接口测试工具,结合30个项目实战讲解!_哔哩哔哩_bilibili20分…

十、v-model的基本使用

一、v-model的基本使用 表单提交是开发中非常常见的功能,也是和用户交互的重要手段: 比如用户在登录、注册时需要提交账号密码;比如用户在检索、创建、更新信息时,需要提交一些数据; 这些都要求我们可以在代码逻辑中…

LVS+KeepAlived高可用负载均衡集群

1. 高可用群集的相关知识 1. 1 高可用(HA)群集与普通群集的比较 普通群集 普通的群集的部署是通过一台度器控制调配多台节点服务器进行业务请求的处理,但是仅仅是一台调度器,就会存在极大的单点故障风险,当该调度器…