操作系统用户级线程(协程)执行原理(程序计数器PC,寄存器EBPESP,线程控制块TCB的变化)

Posted The Gao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统用户级线程(协程)执行原理(程序计数器PC,寄存器EBPESP,线程控制块TCB的变化)相关的知识,希望对你有一定的参考价值。

一、程序计数器PC

程序计数器PC指示的当前要执行的程序在CPU中的地址

二、寄存器EBP、ESP

每个线程执行时,操作系统会为其分配一个栈帧,存储局部变量、返回地址等要素。

描述这个栈帧用到了两个寄存器:

EBP存储着当前函数栈底的地址,栈底通常作为基址,我们可以通过栈底地址和偏移相加减来获取变量地址。

ESP是一个始终指向栈顶的指针。

三、线程控制块TCB

与进程控制块PCB相似,线程控制块TCB中存储着与该线程有关的信息,如寄存器ESP等的值。

四、单用户级线程程序执行原理

执行A程序,即将存储在磁盘中的A程序的指令通过总线读入CPU中。

A程序的逻辑为main函数中又调用了func函数,其执行过程如图(假设main函数的地址为a*,func函数为b*):
在这里插入图片描述

1.调用函数func时,将main函数的下一条指令地址压栈,并修改PC为func函数的地址。

2.将EBP中的值压栈,更新EBP使得空间能够满足func函数的需要。然后将EBP赋给ESP,即初始时栈顶栈底地址是一样的。

3.执行函数func,并将其局部变量等要素压栈。

4.调用的func函数执行完毕之前,先将func函数存储的数据弹出,并将EBP复原为EBP寄存器中的数据。

5.执行ret指令,弹出栈中的返回地址数据,程序跳转到函数下方。ESP也回到函数栈顶部,函数调用结束。

6.继续执行main函数内的指令。

参考视频:函数调用时栈帧布局与函数跳转_bilibili

五、多用户级线程程序执行原理

在这里插入图片描述
如图所示,包含左、右两个线程,先执行左线程。

1.左线程在执行过程中,在执行Yield()函数之前的过程与第四章所述过程一致,即执行A函数,A函数调用了B函数,并将104压入左线程的栈帧。

2.左线程调用Yield()函数,并将返回地址204压入左线程的栈帧中(特别注意:每个线程独享一个栈帧!【这里可以深入想一下两个线程如果使用同一个栈帧会发生什么情况】)。Yield()函数将左线程栈帧的ESP寄存器的值存入左线程的TCB,并将右线程栈帧ESP寄存器的值赋予ESP。这样就实现了线程的交替,切换到右线程。

3.右线程的栈帧为空栈,因此从右线程的第一行指令开始执行,C函数调用D函数,并将304压入右线程的栈帧中。在D函数中调用Yield()函数,并将返回地址404压入右线程的栈帧中。此时切换线程,左线程栈帧弹出204返回地址,即PC=204,从204地址开始执行指令。(【为什么只修改ESP寄存器的值而不修改PC?】因为PC已经压栈了,弹出即可

4.遇到B函数的右括号,即B函数执行完毕,弹出104,PC=104,从104地址开始执行指令。

5.遇到A函数的右括号,A函数执行完毕,整个进程结束。

【拓展】

如果左线程中A函数在104后增加一条指令,再次调用Yield()函数,会有什么效果?

此时左线程栈帧中将Yield()函数的返回地址入栈,并切换到右线程执行。

右线程栈帧弹出404,PC=404,从404地址开始执行指令。

遇到D函数的右括号,即D函数执行完毕,弹出304,PC=304,从304地址开始执行指令,即执行C函数剩余指令。

遇到C函数的右括号,C函数执行完毕,整个进程结束。

参考视频:操作系统(哈工大李治军老师)32讲_10

以上是关于操作系统用户级线程(协程)执行原理(程序计数器PC,寄存器EBPESP,线程控制块TCB的变化)的主要内容,如果未能解决你的问题,请参考以下文章

「理解C++20协程原理」从Linux线程线程与异步编程协程与异步

「理解C++20协程原理」从Linux线程线程与异步编程协程与异步

Golang 之协程详解

协程的实现原理

进程线程协程的区别

进程_线程 之 --- 协程