L10:进程,线程和调度

lab通关记录

MIT-6.828实验通关记录

计划:

为什么需要进程/线程的调度
调度实现

为什么需要进程/线程的调度

过程
一个抽象的虚拟机,好像它有自己的 CPU 和内存,
不会意外地受到其他进程的影响。
但是这也导致了似乎没有其他进程能进行并行的切换调度

进程生命周期中的API调用:
fork
exec
exit
wait
kill
sbrk
get pid

挑战:进程多于处理器
你的笔记本电脑有两个处理器
你要运行三个程序:窗口系统、编辑器、编译器
我们需要在 M 个进程之间复用 N 个处理器
称为分时、调度、上下文切换

解决目标:
对用户进程透明
先发制人的用户进程
先发制人的内核,方便的地方
有助于保持系统响应

xv6 解决方案:
每个进程 1 个用户线程和 1 个内核线程
每个处理器 1 个调度程序线程
n 个处理器

什么是线程?
正在执行的 CPU 内核(带有寄存器和堆栈),或
未执行但是存在的一组保存的寄存器和一个可以执行的堆栈

xv6 处理切换概述(单用户没实现多线程,而是内核的多线程)
用户 -> 内核线程(通过系统调用或定时器)
内核线程产量,由于抢占或等待 I/O
内核线程 -> 调度程序线程
调度程序线程找到一个 RUNNABLE 内核线程(切换进程的细节在此)
调度线程 -> 内核线程
内核线程 -> 用户

每个 xv6 进程都有一个 proc->state
running
runnable
sleep
zombie
unused(还记得我们的env数组吗)

笔记:
xv6 有很多内核线程共享单个内核地址空间
xv6 每个进程只有一个用户线程
更严重的操作系统(例如 Linux)支持每个进程多个用户线程

上下文(context)切换是 xv6 中最难做到的事情之一
多核
锁定
中断
进程终止

调度实现

swtch()

hog.c – 两个受 CPU 限制的进程
我的 qemu 只有一个 CPU
让我们看看 xv6 如何在它们之间切换

使用定时器中断被动进入调度
列出 trap.c:124
设置yield() 上的断点
【堆栈图】
print myproc()->name
print myproc()->pid
print/x tf->cs
print/x tf->eip
print tf->trapno (T_IRQ0+IRQ_TIMER = 32+0)
step into yield
state = RUNNABLE – 放弃 CPU 但想再次运行
step intosched

swtch – 切换到调度后的线程
上下文保存了内核线程的寄存器
xv6将其保存在对栈中,通过特定位置保存的esp来寻找上下文
(不要忘记了user环境保存是在trapframe和堆栈上)
proc.h, struct context
两个参数: from and to contexts
在 *from 保存esp
从 to得到esp, pop registers, return
确保swtch能够切换(返回)到scheduler()//为什么?因为我们在swtch并没有恢复代码段的环境
p/x *mycpu()->scheduler
p/x &scheduler
stepi – and look at swtch.S
[两个栈]
eip (saved by call instruction)
ebp, ebx, esi, edi
save esp in *from
load esp from to argument
x/8x $esp
pops
ret
现在 – 我们返回到了scheduler的堆栈上

scheduler()

print p-> 状态
print p-> 名称
print p->pid
swtch 刚从前一个调度程序->进程切换返回
scheduler释放旧页表,cpu->proc
vm.c 中的 switchkvm()
接下来几次 – scheduler() 找到其他进程
print p->pid
vm.c 中的 switchuvm()
让我们通过 swtch() stepi看看情况
线程堆栈上有什么?上下文(根据esp找到)/调用记录(保存esp)/trapframe
从定时器中断返回到用户空间
其中显示trap/yield/sched

Q:调度策略是什么?
调用 yield() 的线程会立即再次运行吗?不会

Q:为什么scheduler()循环找到进程后释放锁,并且立即重新获取?
让其他处理器有机会使用 proc 表
否则两核CPU,单进程=死锁

问:为什么 scheduler() 会短暂启用中断?
可能没有 RUNNABLE 线程
它们可能都在等待 I/O,例如磁盘或控制台
启用中断,以便设备有机会发出完成信号
从而唤醒一个线程

Q:为什么yield()获取ptable.lock,而scheduler()释放它?
不寻常:锁是由与获取它的不同的线程释放的!
为什么锁必须保持在 swtch() 上?
如果另一个核心的 scheduler() 立即看到 RUNNABLE 进程怎么办?

sched() 和 scheduler() 是“协同程序
调用者知道 swtch() 正在做什么
被调用者知道 switch 来自哪里
例如 yield() 和 scheduler() 关于 ptable.lock 的合作
不同于普通的线程切换,其中既不
派对通常知道哪个线程在之前/之后出现

问:我们怎么知道 scheduler() 线程已经准备好让我们 swtch() 进入?
它可以是 swtch() 以外的任何地方吗?

这些是 ptable.lock 保护的一些不变量:
如果 RUNNING,处理器寄存器保存值(不在上下文中)
如果 RUNNABLE,则上下文保存其保存的寄存器
如果 RUNNABLE,则没有处理器正在使用其堆栈
保持从 yield() 一直到调度程序的锁强制执行:
中断关闭,因此计时器不能使 swtch 保存/恢复无效
另一个 CPU 直到堆栈切换后才能执行

Q:内核线程有抢占式调度吗?使用关中断
如果在内核中执行时定时器中断怎么办?
内核线程堆栈是什么样的?

问:为什么在让出 CPU 时禁止持有锁?
(除了 ptable.lock)
即 sched() 检查 ncli == 1
获取可能会浪费大量时间旋转
更糟糕的是:死锁,因为在中断关闭的情况下等待,记得我们的自旋锁和关中断区别!

线程清理

1.看 kill(pid)
目的:停止目标进程
kill() 可以释放被杀进程的资源吗?内存、FD 等?
太难了:它可能正在运行、持有锁等。
所以一个进程必须杀死自己,而不是kill能够直接解决的!
trap() 检查 p->killed
并调用 exit()
2.看看 exit()
被杀死的进程运行 exit()
线程可以释放自己的堆栈吗?
否:它正在使用它,并且需要它以 swtch() 到 scheduler()。
所以 exit() 设置 proc->state = ZOMBIE; 父母完成清理
ZOMBIE 子进程保证 正在执行/使用堆栈!
3.wait() 进行最后的清理
预计父级会调用 wait() 系统调用
堆栈、分页表、proc[]

简单来说,kill->trap->exit->wait:当你使用kill,只是将p->killed设置,然后trap检查,将会调用exit,其中exit只是设置proc->state = ZOMBIE,然后由swtch和scheduler,切换到父进程,父进程的wait在一开始等待到此刻,将会清理内存的堆栈,页表,进程表。

因此xv6中大部分内存都是wait来清理的,其余的调用只是确定flag来保证某些东西不被使用,锁释放等麻烦的状态。

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2020-2024 环烷烃
  • Visitors: | Views:

我很可爱,请我喝一瓶怡宝吧~

支付宝
微信