7.用户级线程
Posted PacosonSWJTU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了7.用户级线程相关的知识,希望对你有一定的参考价值。
【README】
1.本文内容总结自 B站 《操作系统-哈工大李治军老师》,内容非常棒,墙裂推荐;
2.本文会介绍进程与线程的区别,线程切换,用户态线程,内核级线程等;
【1】多进程回顾
问题:把进程pid1 切换到进程 pid2时,是否可以不切换进程内逻辑内存与物理内存地址映射表;
【1.1】把资源和指令分开
- 进程表示:执行中的程序,包含了计算机资源,执行指令;
- 资源指的是计算机资源,如内存;
- 指令指的是进程对应的程序指令;
1)引入线程:
- 保留了并发优点;避免了进程切换代价高的问题;
2)实际情况是:
- 进程内存与物理内存地址映射表不变,而PC指针变;
2) 进程与线程区别
- 2.1)进程:进程更大;在一个资源下面,启动了多个轻巧的指令序列,这几个指令序列还可以来回切换(交替执行);
- 2.2)线程:指令序列;
- 2.3)切换成本:进程切换成本高,需要保存的东西很多;而线程切换很简单,因为线程仅保存了指令,不涉及计算机资源;线程切换不需要切换内存地址映射表,仅需要切换从一段指令序列跳转到另一段指令序列;
补充:
- 进程切换包含两部分,包括 指令切换(线程切换),计算机资源切换;而线程仅切换指令即可;这就是典型分治思想;
3) 多个执行序列(指令序列或线程)+一个地址空间是否实用?
一个网页浏览器,是一个进程包含多个线程,涉及的是线程切换;
- 线程1:接收服务器数据;
- 线程2:显示文本,把接收到的数据送入显存;
- 线程3:处理图片,解压缩;
- 线程4:显示图片,把图片数据送入显存;
【补充】进程切换与线程切换不同(多次强调,非常重要)
- 进程切换,包括资源和指令切换;如切换内存映射表(或内存地址空间);
- 线程切换,指的是指令切换,所以它比较轻量;
【1.2】实现线程切换的浏览器
1)pthread_create: 创建线程;
2)线程切换(交替执行)实现方式:
- getData() 线程下载数据后, 调用 yield() 让出cpu,让cpu执行其他线程如show();
3)yield线程切换函数
【2】线程切换与栈的关系(栈是否切换?)
线程切换与栈的关系;
线程切换,栈是否也需要切换,不切换有没有问题?
1)执行序列:
线程1 | 线程2 |
// 1 100:A() B(); 104: // 3 200:B() Yield(); 204: // 右大括号是一条汇编指令 ret,地址404出栈,这是有问题的 | // 2 300:C() D(); 304: 400:D() Yield(); 404; |
上述执行过程是:
- 线程1 先执行,先后把内存地址 104 204 压栈;然后执行 yield;
- 同时 线程2也在执行,先后把内存地址 304 404 压栈;然后执行yield;
- 得到的栈内容如下:
2)栈:
序号 | 内存地址 |
1// 栈底 | 104 |
2 | 204 |
3 | 304 |
4 | 404 |
问题:
- 线程1执行完 yield() ,就应该执行204,结果栈顶弹出的元素是 404内存地址,这与指令预期不符,导致程序执行错误(同时线程2也有这个问题);
3)如何解决问题
- 问题:因为2个线程共用同一个栈;
- 解决方法:所以1个线程单独1个栈;2个线程就是2个栈;
【2.1】从一个栈到两个栈(每个线程各自1个栈)
1)执行序列
线程1 | 线程2 |
// 1 100:A() B(); 104: // 3 200:B() Yield(); 204:
| // 2 Void yield() 找到300; Jmp 300 400:D() Yield(); 404; |
2)栈:
线程1 | 线程2 |
栈1: 104 204 | 栈2: 304 404 |
3)Yield进行线程切换时,还需要把栈1切换到栈2
void yield() TCB2.esp= esp; esp = TCB1.esp; jmp 204; // 右大括号把指令地址弹出栈,接着执行地址保存的指令; |
TCB:
- thread control block 线程控制块; 是一个全局结构体,用于存放每个线程对应栈的起始地址;
TCB1.esp:
- 指的是线程1的栈指针;
esp:
- 指的是物理扩展栈寄存器,用于存放栈的起始内存地址(线程栈地址);
【2.2】两个线程的样子
1)两个线程的样子:
- 两个TCB,两个栈,切换的PC在栈中;
- pc指的是 程序计数器,即下一条要执行指令的地址;
2) 线程创建函数 threadCreate的核心就是用程序实现这3样东西
- TCB,线程控制块;
- 栈;
- 栈与TCB关联;
3)代码如下:
Void threadCreate(A) TCB *tcb = malloc(); // 申请内存赋值给TCB *stack = malloc(); // 申请内容赋值给栈; *stack = A; //100 // 为栈赋值 函数A的内存基址(起始地址); tcb.esp = stack; // 把栈内容与TCB关联起来; |
4)把线程切换的所有东西组合起来
【注意】上述内容介绍的是用户级线程的切换,内核级线程的切换放在后面讲;
线程分为用户级线程,内核级线程;
【3】引入核心级线程
1)为什么说用户级线程-yield是用户程序
线程1 是用户态线程;
线程1调用网卡io,网卡io阻塞,无法切换到 用户态线程2;而是切换到 其他进程2;
- 因为网卡io属于内核态程序,无法识别用户态线程,也就无法切换到用户线程2;
2)线程切换
- 内核态线程的切换:叫做schedule,调度;
- 用户态线程的切换:yield;
【总结】用户线程切换内容;
- 1个用户线程1个栈,1个TCB;
- TCB:线程控制块,用于存储当前线程的栈地址;
- 栈与TCB关联;
线程切换过程:
- 在用户线程切换时,首先切换TCB;
- 通过TCB.esp 可以获得栈;
- 从栈中弹出pc指针获得下一条要执行指令的地址,然后执行下一条指令;
以上是关于7.用户级线程的主要内容,如果未能解决你的问题,请参考以下文章