操作系统用户级线程(协程)执行原理(程序计数器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函数内的指令。
五、多用户级线程程序执行原理
如图所示,包含左、右两个线程,先执行左线程。
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线程线程与异步编程协程与异步