10.线程的引出与实现——用户级线程

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

这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值