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

30 篇文章 52 订阅

0 回顾

用户级线程切换的核心就是,从一个栈变到两个栈,每个栈有自己的TCB,在切换的时候首先切换自己的TCB再切换栈,然后创建的时候就是将要切换的PC指针放到自己的栈中,然后再创建好TCB,将来在切换的时候,首先通过TCB一切换,再切换到相应的栈,然后从栈中弹出PC指针去执行。

  • 为什么要讲线程?
  • 本来要讲进程的切换,但是将进程的切换分为几个部分,切换指令,切换指令流以及切换资源,切换资源将会在内存管理当中进行,而切换指令流,实际上就是切换线程
  • 进程必须在内核当中,所以切换进程实际上是切换内核级线程,但是切换用户级线程有助于对切换内核级线程的理解
  • 为什么没有用户级进程,而进程都在内核里呢?
  • 因为进程要分配资源,要访问内存,内存要访问文件,这些都是系统资源,计算机硬件,这些在用户态操作是不行的,必须到内核态才能操作

1 内核级线程


在这里插入图片描述


  • 多处理器架构,每个处理器有自己的缓存、内存映射(MMU)

  • 单个处理器多核架构,只有一套缓存+内存映射,有多个运算部件

  • 这个架构仍然是现阶段个人PC最常见的

  • 这不就正好对应了线程的概念吗?多个指令序列对应多个运算部件,一份资源对应一套缓存+内存映射

  • 如果没有线程,只有多进程,那么MMU内存映射在多进程切换的时候就必须跟着切换,共享Cache、MMU就失去了意义

  • 如果没有内核级线程,只有用户级线程,那么操作系统内核就无法感知这些线程,也就无法把这些线程分配到多个核上,多核就失去了意义

  • 单处理器多核系统必须要实现内核级线程,因为只有进入到内核中,才可以将线程分配到不同的核心(运算部件)上

  • 多核要想充分发挥作用,必须支持核心级线程

  • 多核与多处理器的区别?

  • 多处理器:每个CPU有自己的一套缓存寄存器( c a c h e cache cache)和映射( M M U MMU MMU

  • 多核:多个执行序列共用一套缓存寄存器( c a c h e cache cache)和映射( M M U MMU MMU

  • 多个线程可以使用多个核,所以多线程到内核里才能充分利用这个内核

  • 以上是并行(你执行的时候我也执行)( 同时触发交替执行 同时触发交替执行 同时触发交替执行),与并发不同

  • 并发:有处理多个任务的能力,但不一定得同时执行( 同时触发交替执行,只有一套资源 同时触发交替执行,只有一套资源 同时触发交替执行,只有一套资源

  • 所以多个内核级线程可以同时让多核并行起来(如果是用户级线程,那么操作系统是看不到的,所以不能分配硬件资源,因为核是操作系统管理的)

  • 所以多进程无法充分发挥多核的价值,用户级线程也无法发挥多核的价值

  • 所以内核级线程是有其优点的,可以发挥多核的价值

  • 问:多进程无法充分发挥多核的价值?(查阅资料,觉得老师讲的太绝对了点)

  • 答:

  1. linux下并未对进程线程分别做抽象,都是利用task_struct来描述具体调度的一个单元
  2. 也就是说创建进程、线程的时候其实都是调用的clone
  3. 如果clone传参共享资源则为创建线程反之则为进程
  4. 所以“多进程在多核上的情况”,其关键就在于MMU是否共享
  5. 具体参考i7存储系统框图,每个core都有一个MMU

1.1 内核级线程与用户级线程有什么不同?


在这里插入图片描述


  • 用户级线程是每一个用户都有自己的栈,内核级线程是有两套栈
  • 进入内核态,创建内核级线程,这就需要内核栈
  • 但是回到用户态,也可以创建线程,这就是用户栈
  • 所以是两套栈
  • ThreadCreate是系统调用,内核管理TCB,内核负责切换线程
  • 只要TCB同时关联用户栈和内核栈,那么内核级线程在切换的时候,切换TCB,就做到了同时切换用户栈和内核栈(往下看就知道,实际上是TCB关联内核栈内核栈又保存了同一个线程的用户栈的地址)
  • 用户级线程切换:根据TCB切,然后根据TCB切换用户栈
  • 内核级线程切换:根据TCB来切换一套栈,内核栈要切,用户栈也要切

1.2 内核栈


在这里插入图片描述


  • 进入内核的时候,就要出内核栈
  • 一旦有了中断,int,键盘鼠标之类的都会引起用户栈到内核栈的切换
  • 只要一有中断,就启用内核栈
  • 用户指令就是用户态,中断就是内核态
  • 每个线程都对应一个用户栈和一个内核栈
  • 进入内核要进行压栈,压什么呢?
  • 压刚才用户态的ss和sp,cs和ip等,记录好现场,例如中断会保存现场
  • 用户栈与内核栈之间的关联
  • 从用户态进入内核态时(INT中断指令),需要在内核栈中先依次压入源SS、源SP、EFLAGS、源PC、源CS等内容
  • 源SS和源SP是指向用户栈的指针,也就是说内核栈中存放了指向用户栈的指针
  • 源PC和源CS是用户栈中的返回地址
  • CS(Code Segment):代码段寄存器
  • DS(Data Segment):数据段寄存器
  • SS(Stack Segment):堆栈段寄存器
  • SP(Stack Pointer ):栈指针
  • ES(Extra Segment):附加段寄存器
  • 从内核态返回用户态的时候(IRET中断返回指令),会从内核栈弹出这些信息,根据这些信息就可以恢复到用户栈

在这里插入图片描述


  • 系统调用的例子说明用户栈和内核栈
  • 如上图,内核栈按次序保存了
  • SS:SP,指向用户栈的指针
  • EFLAGS
  • 304(IP),返回用户程序的偏移地址
  • CS,返回用户程序的段基址100
  • 系统调用返回(IRET)的时候,就会根据SS:SP切换回用户栈,根据CS:IP切换到用户程序的指令

在这里插入图片描述

  • 阻塞后找到下一个线程,next表示下一个线程的TCB,cur表示当前线程的TCB
  • 找到TCB,根据TCB切换用户栈,然后根据栈里面的东西一弹,就切换了PC指针
  • 现在的切换与用户态切换一样,仍然是用TCB找到栈指针,这个时候的指针应该是内核栈(因为进入了内核),找到了内核栈的指针,也就是说刚才在线程S当中执行,所以应该把esp指向当前的TCB当中,即cur,在下一个线程中会找到esp然后存入next当中
  • 用当前的S线程的TCB,把当前S的栈顶地址esp存下来
  • 存下来之后,进入到next下一个线程T
  • 这时要把T线程的TCB中的栈顶地址拿出来赋给esp寄存器,这样才能把T线程的栈利用起来

  • 总的来说
  • 先调用next(),调度找到下一个占用CPU的内核级线程T(拿到了线程T的TCB)
  • switch_to()中做了什么
    • 在进入switch_to().之前,将s的返回地址压入S的内核栈中
    • 切换TCB(TCBcur:=物理寄存器esp,物理寄存器esp=TCBnext)
    • switch to()结束,则从T的内核栈中弹出一个地址,并跳转到这个地址,去执行T的内核代码
  • 接着,T的内核代码不断运行,伴随着一些栈的操作,直到碰到RET指令(这就是老师PPT上的第二级切换),这个时候就根据内核栈中的CSIP切换回线程T的用户程序(用户态),根据SS:SP切换回用户栈
  • 所以真正线程T的代码是在用户态的
  • 疑问:这里举得是系统调用的例子,如果是其它中断,例如时间片到了触发的中断,会有细节上的区别吗?

1.3 四个问号


在这里插入图片描述


  • 能从中断返回的代码意思是这里放了一个csip,这个csip指向某个中断返回程序
  • 然后这里终端程序调用ret,会先返回到这四个问号指向的中断返回程序
  • 而这个中断处理程序内又有一个iret,当执行了中断处理程序的iret后,程序才真正的被返回会用户栈
  • 综上,利用中断进入内核,找到TCB,进入内核栈,内核栈的切换,再返回iret指令,把用户栈给切换过来,所以是从一套栈切换到另一套栈,这是两套栈的操作

1.4 五段论


在这里插入图片描述


  • 内核线程switch_to的五段论
  • 这一段和上一段讲的实际上是一回事情,算是总结和补充
  • 从用户代码到内核代码,可能是主动的系统调用,也可能是时钟中断(时间片轮转)等情况,不过本质上都是中断
  • 在系统调用进入阻塞或者时间片轮转等情况下,会调用schedule()
  • schedule()会首先调用next(),进行调度,找到下一个占用CPU的内核级线程
  • schedule()然后调用switch_to(),进行内核级线程的切换(第一级切换)
  • 在切换后的线程的内核态下执行指令,直到iret指令,会回到用户态(第二级切换)

在这里插入图片描述


  • 需要注意如果S和T不属于同一进程,还需要切换资源,例如内存映射等,这个在内存管理里面讲
  • linux0.11实际上是没有线程的,但是有进程的切换
  • 内核级线程的切换再加上资源的切换(内存映射表)就是进程的切换,原理是差不多的,所以可以通过iux0.11的进程切换代码来学习
  • 用户级线程、核心级线程的对北比,用户级线程和核心级线程搭配的效果最好
  • 代价指的是,进入内核,会消耗系统资源?例如用户级线程完全不需要进入内核,也就不需要额外的内核数据结构,用户想起多少个用户级线程都行,就像浏览器想开多少个标签都行,如果用内核级线程来启动刘览器标签,那么启动多了之后就卡了
  • 用户灵活性,用户级线程可以由用户自己实现调度,自己决定什么时候切换,而核心级线程的调度与切换是在内核中提前写好的

2 用户级,内核级线程的对比


在这里插入图片描述


3 总结

加油

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值