1.线程的引入
- 进程 = 资源 + 指令执行序列
- 线程:一个资源 + 多个指令执行序列
资源(映射表)和指令分开
线程中,一个映射表 对应 多个指令序列,会加快切换速度,切换指令需序列,只要改PC值就可以了
线程切换,是从一段程序,切换到 另一段程序
2.多个执行序列 + 一个地址空间 的应用举例
浏览器显示网页
我们平时用的浏览器 打开一个网页时,都是 先 加载文字,过一会,加载了 图片,最后 出现了动画等特效。
单线程实现:先下载,文字、图片、动画 全部下载完,再显示文本、图片、动画,会很慢
多线程实现:下载一段数据,切换到 显示指令,显示文本;再下载一段数据,再切换到 显示指令,显示图片。
多线程是 多段程序 同时触发,轮流执行
这些线程要共享数据吗?
下载数据 与 显示数据,对应的是 一块内存,一个映射表。如果是多个映射表,会设计 映射表间的数据处理,会很麻烦,易出错。多线程 不需要做数据隔离。
启动多个执行序列(create),关键是 要交替执行,多个执行序列切换 需要 用Yield 实现跳转,主动释放执行权
3. 两个执行序列与一个栈
100:A()
{
B();
104:
}
200:B()
{
Yield();
204:
}
void Yield()
{
找到300;
jmp 300;
}
300()
{
D();
304:
}
400:D()
{
Yield2();
404:
}
Yield2()
{
//预期的执行顺序是 B的Yield执行完,要回到A,此时要跳到 B结束的地址 204
找到 ? 204
跳到 ? 204
}
1.程序开始执行,执行函数A,A调用B,将 104 压栈,跳到B执行
2.B调用了 Yield,将 204 压栈(此时栈中是 204,104),修改PC指针,跳到 Yield 执行,Yield 跳到 300,执行300 处的 C函数,
3.C调用了D,将304压栈(此时栈是 304, 204, 104),跳到D执行
4.D调用了 Yield2,将 404压栈(此时栈是 404, 304, 204, 104),执行 Yield2,跳到 204执行,204是 B的结束符 },} 会转化为 ret,要弹栈,弹出 404,会跳转到 404 执行。
预期 204 结束之后,应该回到 A执行,应该回到 104,上边的例子回到了 404
如果2个线程 对应2个栈呢?
4.两个执行序列 与 两个栈
1.A执行,A调用B,将 104压栈,跳到 B 执行
2.B调用了 Yield,将 204 压栈(栈1 中 204, 104),用TCB 保存栈,esp保存栈的指针
Yield(){
找到300
跳到300
}
3.Yield执行,跳到 300 执行,C 调用 D,将 304 压入栈2 中,跳到D
4.D执行,将 404压入栈2(栈2 中 404, 304),执行 D 中的Yield
按照 预期,图中的 (2)执行完,应该到(3),此时要 切换 执行序列 和对应的栈
此时的Yield,要实现 CPU中的物理寄存器esp = TCB1 的 esp,PC = 204
Yield()
{
TCB2.esp = esp;// esp是物理寄存器
esp = TCB1.esp;
jmp 204;// 应该去掉
}
此时Yield中 ,是否需要 jmp 204:
- 如果有 jmp 204,当前栈1 是 204, 104,执行 jmp 204 不会执行 Yiled的 },跳到 204, 是B的 } ,弹出的是204
- 如果没有 jmp 204,当前栈1 是 204, 104,执行 Yield的 } ,204出栈,204处是 B的 },104出栈,与我们预期相符
所以,Yield中应该只切换栈,不要加 jmp
5. 创建线程 ThreadCreate
核心是要 创建 TCB,栈,指令的首地址,关联 TCB与栈(TCB的esp保存 栈的指针)
voidThreadCreate(A)
{
TCB *tcb=malloc(); // 申请一块内存作为TCB
*stack=malloc(); //申请一块内存作为栈
*stack = A; // 栈中贴上内容,即程序的初始地址
tcb.esp = stack;// 栈 和 TCB 关联起来
}
切换时,先找到 目标线程的 TCB,找到 目标TCB对应的esp,物理 esp = 目标TCB.esp,通过 Yield的 } 弹栈
6. 代码实现浏览器显示网页
两个线程:GetData,Show
创建线程:创建对应的 TCB,esp,栈,关联TCB和栈
切换线程:执行GetData,调用Yield,先切换栈,再通过 Yield的 } 弹栈
GetData下载完文本,就调用 Yield,切换到show去显示文本,
show显示完文本,再切换到 GetData 去下载图片
// main
void WebExplorer()
{
ThreadCreate(GetData, URL, buffer);
...
while(1)
Yield();// GetData下载完文本,就调用 Yield,切换到show去显示文本
}
void ThreadCreate(func, arg1)
{
申请栈;
申请TCB;
func等信息 入栈;
关联TCB与栈;
...
}
void GetData(char *URL, char *p)
{
连接URL;
下载;
Yield();
...
}
void Yield()
{
当前线程的栈 = 当前 物理内存的现场;
当前线程的TCB.esp = 物理的esp;
Next();//调度
esp = 下个线程的TCB.esp;
弹栈 切换线程;
}
7.用户态切换 实现不了 硬件调用
用户程序 完全 运行在用户态,切换也在用户态进行,内核感知不到 这种切换。
Yield的用户程序,在用户态切换
GetData()
{
连接URL发起请求;
等待网卡 IO ...
进程阻塞
}
show()
{
显示文本和链接等
...
}
GetData要进程 网卡 IO等操作,调用硬件 需要进入内核,内核操作网卡,GetData阻塞。
内核不知道 Show 的存在,调用其他进程 继续执行,此时浏览器上 什么都不显示
如果 网卡 下载网页的 一个标签卡住了,内核已经切换到了其他进程,后边的 标签 运行不了
8.核心级线程
创建核心级线程时,由系统创建线程,ThreadCreate在内核中,TCB在内核中,如果GetData阻塞了,内核知道 Show的存在,可以调用Show 显示数据
切换线程,用户态是 Yield,内核态是 Schedule