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函数
yield
函数中加??了,因为在之后的CPU选择需要调度的线程的函数中需要去查看线程状态,也会加??
- 这里的检测用来保证在调用swtch的时候,线程只能够获取p-lock这一把??,这样做是为了防止死锁,见13.1
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
区了 -
这里两次acquire??,不会发生aa型死锁吗?不会,调度器线程从swtch函数返回继续执行到下面的的
c->proc = 0;
与release(&p->lock);
-
没理解fs的初始化?
-
第一个进程的用户态栈sp怎么会在4096?init进程执行这段程序,是因为是汇编程序吗?
为什么系统调用的exec不用la a0 init, la a1 argv?