哈工大李治军老师操作系统笔记【8】:用户级线程(Learning OS Concepts By Coding Them !)

30 篇文章 78 订阅

0 回顾

多进程图像是操作系统当中最重要的图像。


在这里插入图片描述


  • 进程每次执行一堆指令,那么这些指令从哪里获得呢?通过映射表获得,这就是进程执行指令的样子
  • 多进程图像指的是,一个进程在执行指令的过程当中,可能启动了IO,启动了磁盘,执行不下去了,或者不愿意往下执行了,就进行切换,从一个执行指令序列,切换到另一个执行指令队列
  • 话题引出:只切换指令可不可以,不切换映射表可不可以?

1 用户级线程

1.1 线程切换


在这里插入图片描述


  • 为什么讲进程切换,要讲线程呢?
  • 因为进程=资源+指令执行序列,资源包括内存映射表等等
  • 是否可以资源不变,而只切换指令执行序列呢?
  • 实现了多段程序交替执行,而目不需要切换资源这种消耗时间的操作
  • 实质就是映射表不变,而程序计数器PC变
  • 所以这种特点就很好,既是多道程序交替执行,且切换起来还快
  • 所以通过这来引出线程的概念
  • 线程,保留了并发的优点,避免了进程切换代价

在这里插入图片描述

  • 如图就是线程的切换,从一道程序切换到另一道程序
  • 映射表怎么切换?与内存有关,可以看看内存管理
  • 所以进程的切换大致分为两个部分,一个是指令的切换;另一个是映射表资源的切换
  • 为什么要分开来呢?分治的思想

1.2 线程切换是否有价值?

  • 之前我们讲了多进程带来的内存地址冲突问题,但在有些情况下,实际上多段代码执行序列是需要合作与共享资源的,使用线程技术就很方便
  • 以网页浏览器举例,为了用户体验,需要多段程序交替执行,但是这些程序又需要共享资源

在这里插入图片描述


  • 线程当中的切换是指令的切换,是进程切换当中非常重要的一个部分,所以线程切换是很有价值的

1.3 实现线程切换


在这里插入图片描述

  • 多个指令交替执行

  • 实现浏览器的核心骨干伪代码
  • GetData用于从服务器获取数据
  • Show用于展示数据
  • 进程切换需要调用yeild函数进行切换
  • yield,礼让,让当前线程从运行态进入就绪态,重新与其他线程争夺进入运行态

在这里插入图片描述


  • 实现yield的目的是能完成切换
  • 切换的时候样子是怎么样的呢?
  • create就是能制造出第一次切换时应该的样子
  • 所以说,要想切换必须知道长成什么样子

在这里插入图片描述


  • 这里的栈中装的是返回地址
  • 栈方向好像画反了,不过从最下开始出栈也可以
  • 本质问题:两个线程共用了一个栈,如上图,esp都是顺序存储的
  • 这里关于为什么404之后要切换到204的一点解释
  • 老师默认的执行顺序是(1)(2)(3),所以400处的yield执行完操作系统应该返回到204
  • 否则就只能执行完400地址之后通过回掉一步步返回,显然这不是管理线程希望看到的情况
  • 现在跳回了204,继续执行会怎么样?
  • 碰到了右大括号,右大括号怎么样,又有了个汇编指令ret,闭函数返回了,要进行弹栈
  • 注意刚刚加进来的数据在栈中都没有动过,所以要进行弹出,第一个数据就是404,就接着404继续执行了,出问题了
  • 因为本来204之后ret要返回104的,因为这是在一个线程里的函数调用
  • 所以204应该跑到104的地方,因为这两个是一个线程,但是现在204跑到了404的线程,这就不对了
  • 因为两个线程共用了一个栈,所以一个栈也会导致ret跑来跑去,这是不对的,会出错,所以需要多个栈进行分开维护
  • 因为一个函数调用是在一个指令序列的内部发生的事,所以每个指令序列函数调用应该用自己的栈
  • yield就是暂停当前线程,跳到另外一个线程

右边红色yield回到204只是为了满足左边的箭头,两个yield不是一个函数


在这里插入图片描述


  • esp两个栈分开存储,引出了TCB的概念
  • TCB和栈要相互配合
  • 每个线程维护一个TCB,TCB保存了栈顶指针esp,栈里保存了返回地址, y e i l d ( ) yeild() yeild(),需要切换TCB
  • 所以说,当执行红色的yield()的时候,是要暂停当前线程,然后切换回对方线程去执行的,现在除了这样直接切换,还得把对应的栈也切换回去
  • 因为栈切回去后,才能在自己的线程当中调用
  • 现在有两种情况会将返回地址放入栈中
  • 一个线程内函数的调用
  • 调用yeild(),yeild(也可以看成是一个普通的函数
  • 要注意入栈和出栈的操作要匹配:切换的入栈和出栈匹配,函数的调用与返回匹配
  • 要注意入栈和出栈的操作要匹配:切换的入栈和出栈匹配,函数的调用与返回匹配
  • 分析当前例子发现,在yeild方法体最后的右括号结束后,就会弹出栈中的最后一个地址,并跳转到那个地址,这个地址刚好也是yeild调用的时候入栈的,这样就匹配上了,所以yeild(的最后不需要主动jmp,只需要切换TCB即可,那么yeild()也就和普通函数一样了
  • 总结:yeild()的调用和返回,与普通函数的行为一致,调用时,会将PC压入栈,返回时,会弹出栈中地址并跳转到那个地址;在这中间,yeild方法体内只需要进行TCB的切换
  • 再换一种说法:yeild()首先将当前线程的yeild()语句后的地址作为返回地址压入当前线程的栈中,然后切换TCB为下一个占用CPU的线程,然后从切换后的线程的栈中弹出一个地址,并跳转到这个地址去执行指令
  • PC为什么不切换?因为PC已经在栈里了

在这里插入图片描述


详细讲解create做了什么:

  1. 分配TCB
  2. 分配栈
  3. 栈顶压入线程的初始地址
  4. TCB中保存栈顶指针
  5. Create返回的时候,从栈里弹出的地址就是线程的初始地址
  • GetData和Show和Yield合在一起

在这里插入图片描述


  • 把上面讲的东西组合起来,浏览器代码+自己实现的用户级线程(create-+yeild)
  • 完全没有进入操作系统内核,在用户级实现了线程的逻辑
  • 老师原话:实际上,用户级线程是内核级线程切换的子部分,而且这个子部分是可以单独使用的(怎么理解?)
  • yield中包含next0,它是用来线程调度的,即选择下一个占用CPU的线程
  • 把这些全写完了,与操作系统没一点关系,就是一个多线程的程序

在这里插入图片描述


1.4 用户级与内核级

  • 为什么说上面的内容是用户级线程?有什么缺点?
  • 因为yield是用户程序,完全没有进入内核,操作系统完全感知不到这种线程的存在
  • 疑问:读写TCB和其关联的栈指针,不需要进入内核吗?TCB存在于用户段中吗?(蛛丝马迹:确实经常提到内核栈和用户栈这两个相对的概念,那么TCB也会在用户段和内核段各有一份吗?)
  • 缺点举例:在浏览器获取数据的函数GetData中,等待网络IO阻塞(这是内核级别的阻塞),因为操作系统完全不知道还有用户级线程的存在,所以不会线程切换到展示数据的函数Show,而是直接在内核中进行了进程的调度与切换(切换到了浏览器进程之外的别的进程),如下图所示
  • 一旦用户级线程卡了,干什么都不好使,CPU没事可干了

在这里插入图片描述


  • 引出核心级线程的概念
  • 按照老师的意思,这里核心级线程的ThreadCreate和用户级线程的ThreadCreate是两个不同的函数
  • 核心级线程的ThreadCreate是系统调用,会进入内核
  • 内核级线程不再能由用户主动yield,而是由操作系统自动调度

在这里插入图片描述


  • 内核级线程并发性好一点

2 总结

从一个栈到两个栈,完成了线程的切换,但这是用户态的线程切换,更多的还得看下一节的核心级线程,用户级线程是核心级的基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值