xv6 lec11 Thread switching

https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/lec11-thread-switching-robert/11.1-thread

11.1 线程(Thread)概述

  • 为什么需要线程?
    • 人们希望他们的计算机在同一时间不是只执行一个任务
    • 多线程可以让程序的结构变得简单?
    • 使用多线程可以通过并行运算,在拥有多核CPU的计算机上获得更快的处理速度
  • 线程的定义:线程就是单个串行执行代码的单元,它只占用一个CPU并且以普通的方式一个接着一个的执行指令。
  • 切换线程,需要保存状态:
    • 程序计数器
    • 保存变量的寄存器
    • 程序的Stack
  • 线程之间有共享内存,需要加🔒

11.2 XV6线程调度

  • 线程切换需要处理
    • 如何实现线程间的切换。XV6为每一个核创建了线程调度器
    • 切换线程需要保存哪些信息
    • 密集型计算线程需要被动的让出CPU,其他线程一般自己让出CPU,一般利用定时器中断,在每个CPU核上都会有一个硬件设备,定时产生中断,XV6与其他所有的操作系统一样,将这个中断传输到了内核中
  • 被动的让出CPU被叫做pre-emptive scehduling
  • XV6与其他OS中,线程调度的实现是:定时器中断会强制的将CPU控制权从用户进程给到内核,这里是pre-emptive scheduling,之后内核会代表用户进程(注,实际是内核中用户进程对应的内核线程会代表用户进程出让CPU),使用voluntary scheduling。
  • 线程在XV6中分为三种状态
    • RUNNING,线程当前正在某个CPU上运行
    • RUNABLE,线程还没有在某个CPU上运行,但是一旦有空闲的CPU就可以运行
    • SLEEPING,这节课我们不会介绍,下节课会重点介绍,这个状态意味着线程在等待一些I/O事件,它只会在I/O事件发生了之后运行
  • 将RUNNING线程变成RUNNABLE线程就是pre-emptive scheduling,换出时,需要将其pc和寄存器保存在内存某处。

11.3 XV6线程切换(一)

  • 当XV6从CC(C compiler)程序的内核线程切换到LS程序的内核线程时:
    • XV6会首先将CC程序的内核线程的内核寄存器保存在一个context对象中
    • XV6需要恢复LS程序的内核线程的context对象
    • 之后LS会继续在它的内核线程栈上,完成它的中断处理程序(注,假设之前LS程序也是通过定时器中断触发的pre-emptive scheduling进入的内核)。
    • 然后通过恢复LS程序的trapframe中的用户进程状态,返回到用户空间的LS程序中。
    • 最后恢复执行LS。

11.4 XV6线程切换(二)

  • 调度器线程也有自己的context
  • schedulder函数要恢复P2的时候,需要保存自己的context

11.5 XV6进程切换示例程序

  • proc结构体存有很多重要的字段
    • trapframe中保存了用户空间线程寄存器
    • context中保存了内核线程寄存器字段
    • kstack是进程内核栈的地址
    • state保存了当前进程状态,RUNNING,RUNABLE或者SLEEPING
    • lock字段保护了很多数据,比如,lock可以保护state字段的修改,这样一来就不会有两个CPU拉取同一个RUNABLE进程

11.6 XV6线程切换 --- yield/sched函数

  • 定时器中断,会陷入内核,执行devintr()函数,之后which_dev变量会被置为2,那么在usertrap函数中之后会去执行yield函数
  • yield函数中加🔒了,因为在之后的CPU选择需要调度的线程的函数中需要去查看线程状态,也会加🔒,因为这个yield将进程状态改成了RUNABLE,但是实际上这个进程还在运行,所以需要让其他CPU的上的调度线程先看不见这个修改
  • 这里的检测用来保证在调用swtch的时候,线程只能够获取p-lock这一把🔒,这样做是为了防止死锁,见13.1
  • sched中intena的作用是:acquire锁的时候要关中断,release的时候要开中断,但是有时候acquire之前中断就已经关了,release的时候就不能开中断。所以在第一次acquire的时候要用intena记录一下之前的中断是开的还是关的。

11.7 XV6线程切换 --- switch函数

  • 在swtch函数中交换当前内核线程与当前CPU的调度器线程的context
  • 对于swtch中保存ra寄存器的内存,那么当一个线程调回CPU的时候就可直接返回到sched函数

11.8 XV6线程切换 --- scheduler函数

11.9 XV6线程第一次调用switch函数

  • 线程创建之后第一次调用swtch,是将函数的ra寄存器,也就是交换了scheduler线程与该线程的寄存器之后返回执行的函数是forkret

问题

  • 内核线程?线程是调度单位,这里修改了运行时的程序的称呼,对于内核中运行的程序共享了内存

  • context对象可以保存在trapframe中!!!

  • 进程等待I/O,sleep函数会调用swtch函数让出CPU

  • 调度器线程到底时如何体现的?可以从11-8中看到,在调用完swtch函数之后,就进入了调度器线程,也就是切换了stack与context,并且在执行scheduler

  • 这里为什么通过SCAUSE寄存器查看中断类型SIP寄存器中存的才是中断类型?这里ssip bit的acknowledge是啥?

  • push_off(void)pop_off(void)是干嘛的?和intena, noff有什么关系

  • 为什么swtch函数只保存14个寄存器呢?因为caller register都被保存在内核线程的内核栈里了

  • 为什么swtch函数要用汇编来实现,而不是C?C语言难以修改寄存器

  • 如果这里用来放内核调度器的栈,可是这里不准写鸭,也没有guard page?a110已经是可以在kernel data区了但是scheduler究竟是什么时候被设置的?

  • 这里两次acquire🔒,不会发生aa型死锁吗?不会,调度器线程从swtch函数返回继续执行到下面的的c->proc = 0;release(&p->lock);

  • 没理解fs的初始化?

  • 第一个进程的用户态栈sp怎么会在4096?init进程执行这段程序,是因为是汇编程序吗?
    为什么系统调用的exec不用la a0 init, la a1 argv?

posted @ 2022-04-08 13:36  抿了抿嘴丶  阅读(72)  评论(0编辑  收藏  举报