注:本文图片引用自慕课的PPT
b站:https://www.bilibili.com/video/BV1d4411v7u7
用户级线程
一个浏览器的模型:
两个执行序列与一个栈的弊端:
Yield()
是一个切换函数,在线程切换中,我们需要像Yield()
这样的函数允许B->C->D并直接返回 ->B,否则只能逐层调用返回,这不是线程切换想要的结果。
这种情况如果我们希望用Yield()
从D返回到B,即直接跳转到204,但当函数返回时(ret指令),我们发现此时栈已经乱了,ret将会导致意外,会使程序跳转到404.
从一个栈到两个栈
如果使用两个栈,那么我们自然需要在Yield()
中切换栈,把栈顶指针存入到该线程的TCB(线程控制块)中。
当程序在D中,调用Yield()
,当前栈2顶部压入404,调用Yield()
时,交换栈,此时Yield()
函数返回时弹出的栈是栈1,而此时栈1顶部为204,正好与我们跳转到204的目的相同,因此Yield()
不需要jmp 204。
两个线程:两个TCB+两个栈+切换的PC在栈中
用户级线程的弊端
当用户级线程调用内核时,进入内核态之后,若内核进程需要等待网卡IO,需要较长时间而导致进程阻塞,用户态的多线程将没有任何作用。
核心级线程
用户线程是Yield()
,内核线程切换Schedule()
,Schedule()
调度是由系统决定,具有强制性,Yield()
用户可主动释放。
如果实现了核心级线程,则有效解决了仅用户级线程的缺陷。
切换进程与切换线程:
- 进程需要分配资源,所以只有内核级的进程。
- 切换进程:切换指令流 + 切换资源
- 切换内核级线程即为切换指令流,是切换进程中的一部分。
多处理器与多核的区别,多处理器每个运算器都有自己的缓存和内存映射,多核心处理器共用一个。
多核处理器要想有作用,必须支持核心级线程。
在多核处理器中, 因为多个线程共用一个资源,多核被分配到多个线程,而只需一套MMU(内存管理单元)。
从一个栈到一套栈
每个用户级线程即需要一个栈,而实现用户级线程需要一套栈即两个栈:用户栈 + 内核栈
用户栈到内核栈的关联:
SS:SP为用户栈指针,CS:PC(IP)记录用户程序的位置。
esp 被赋值 为TCB1中的值,执行sys_read()
而阻塞,内核线程调度使用switch_to()
,并切换栈。
cur是当前线程的TCB,next是下一个线程的TCB。
PC:CS 为返回用户态的地址,SS:SP指向用户态栈。
要让以上PC,CS,SS,SP都弹出栈,那么? ? ? ? 的指令应为iret
(中断返回)
小结:内核线程switch_to的五段论
- 准备中断:为整个处理链条做准备,中断进入内核态
- 中断处理:当引发阻塞(IO 或时钟中断)时
- 引发切换,并找到下一个TCB(
next =...
) - 内核栈切换:交换指针完成栈交换后,执行 ret 指令。
- 中断出口:交换后将弹出当前栈顶即上文图中的
? ? ? ?
的命令,在上文已经解释到,? ? ? ?
为iret
(中断返回),使 CS,PC,SS,SP等弹出栈,恢复到用户态。 - (附加)因为进程切换,还要切换地址映射表,到内存管理部分时再研究。
ThreadCreate 实现模型
- 申请内存:
get_free_page()
作为TCB - 内核栈初始化
- 传入用户栈
- 并将两个栈通过指针连接起来,将内核函数地址(图中为500)和 CS (0F)压入栈,并压入参数。
- 设置TCB指向内核栈
- TCB就绪,入队。