原文点这里
1.硬件现在需要关注(attention
)! 软件必须搁置当前的工作并做出响应
2.为什么hw现在需要关注?
MMU
无法翻译地址
用户程序除0
用户程序想要执行内核代码(INT
)
网络硬件(Network hardware
)想要传递一个数据包(deliver a packet
)
计时器硬件希望提供一个“tick”
内核cpu到cpu的通信,例如刷新
(flush)TLB
(IPI)
3.这些 “traps
” 大致分为三类:
Exceptions (page fault, divide by zero)
System calls (INT, intended exception)
Interrupts (devices want attention)
4.设备中断从何而来?
diagram:
CPUs, LAPICs, IOAPIC, devices
data bus
interrupt bus
中断告诉内核设备:硬件需要注意(attention)
驱动程序(the driver
)(在内核中)知道如何告诉设备去做事情
中断处理程序通常调用相关的驱动程序
但其他安排是可能的 (schedule a thread; poll)
5.trap()
如何知道哪个设备被中断了?
即tf->trapno == T_IRQ0 + IRQ_TIMER从哪里来?
内核告诉LAPIC/IOAPIC
使用哪个向量号,例如timer是向量32
page faults &c也有向量(正如我们在上节课中看到的)
LAPIC / IOAPIC是PC硬件的标准部件
每个CPU有一个LAPIC
IDT
将指令地址与每个向量相关联
IDT格式由Intel定义,内核配置
每个向量都跳转到 alltraps
CPU通过IDT发送多种陷阱
low 32
IDT entries具有特殊的固定含义
xv6设置系统调用
(IRQ)来使用IDT条目64 (0x40)
要点:vector number
揭示了中断的来源
6.diagram:
IRQ
or trap
, IDT table
, vectors
, alltraps
IDT:
0: divide by zero
13: general protection
14: page fault
32-255: device IRQs
32: timer
33: keyboard
46: IDE
64: INT
7.让我们看看xv6如何设置中断向量机制
lapic.c / lapicinit()——告诉LAPIC硬件使用vector 32作为定时器
trap.c / tvinit()——初始化IDT
,因此条目i指向vector[i]处的代码。
这主要是纯机械的,IDT项盲目地对应于向量
但是T_SYSCALL的1 (vs . 0)
告诉CPU在系统调用期间启用中断,但不是在设备中断期间
问:为什么在系统调用期间
允许中断?
可能系统调用只是用户请求内核帮忙,这时如果来一个硬件中断明显优先级更高
问:为什么在中断处理期间(interrupt handling
)禁用中断?
中断处理的时候一般是一连贯的操作,如果被其他中断打断,可能导致系统崩溃,具体例子等后面遇到了再来添加把
vectors.S (由vectors.pl生成)
首先push “error
”槽在trapframe
,因为h/w不推一些traps
第二个push就是vector number
这在trapframe中显示为tf->trapno
8.硬件如何知道使用什么堆栈来中断?
当它从用户空间切换到内核时
硬件定义的TSS(task state segment)
允许内核配置(configure)CPU
one per CPU
所以每个CPU可以运行不同的进程,在不同的堆栈上设置traps
proc.c /调度器()
每个CPU一个
vm.c / switchuvm ()
告诉CPU使用什么内核堆栈
告诉内核要使用什么页表
9.问:当trap到内核时,CPU应该保存什么eip ?
执行指令的eip ?
下一条指令的eip ?
假设trap是页面错误?
我觉得应该是引发trap的指令的下一条指令吧
9.Let’s talk about homework
, which involves:
interrupts
+ system calls
challenges:
get it to work at all
maintain isolation
(unfortunately, not easy way to test!)
10.alarmtest.c
alarm(10, periodic)
要求内核在此进程中每10次“滴答”调用周期()一次
也就是说,这个进程消耗的CPU时间为10滴答
三个部分:
添加一个新的系统调用
计算程序运行时的滴答(计时器中断)
内核对periodic()的“upcall”
对periodic()的调用是一个简化的UNIX信号
glue(胶合?) for a new system call (like date homework)
syscall.h: #define SYS_alarm 22
usys.S: SYSCALL(alarm)
alarmtest.asm – mov $0x16,%eax – 0x16 is SYS_alarm
syscall.c syscalls[] table
sysproc.c sys_alarm()
break sys_alarm(比如homework)
在哪里
syscall如何知道哪个系统调用?
内核堆栈上的trapframe
保存了用户eax
打印myproc () - > tf - > eax
sys_alarm在哪里找到参数、刻度和处理程序?
on the user stack
x/4x myproc()->tf->esp
处理程序值有意义吗?看看alarmtest.asm
现在,每当timer h/w
中断时,我们需要采取一些操作
减量ticksleft
如果过期
向上调用处理程序(periodic()
)
重置ticksleft
11.设备中断(device interrupts
)就像INT和pagefault一样到达
h/w将esp和eip推送到内核堆栈上
s/w将其他寄存器保存到一个trapframe中
vector, alltraps, trap()
12.在没有实现的情况下执行trap
break vector32
where
print/x tf->eip
print/x tf->esp
x/4x tf->esp
此时用户程序在做什么?
tf - > eip在alarmtest.asm中
用户代码可能在任何地方被中断
所以我们不能依赖任何关于用户堆栈的东西
我们需要准确地恢复寄存器
,因为程序没有保存任何东西
Q: how to arrange for upcall to alarm handler?
call myproc()->alarmhandler() ?
tf->eip = myproc()->alarmhandler ? 这个是对的
Q: how to ensure handler returns to interrupted user code? 应该是iret指令吧
the x86 h/w does not
directly provide isolation
x86 has many separate features (page table, INT, &c)
it’s possible to configure these features to enforce isolation
but isolation is not the default!
问:如果trap()没有检查CPL 3
呢?
让我们试一下——似乎有效!
tf->cs&3 == 0怎么会产生于alarmtest?
让我们用(tf->cs&3)==0来强制执行这个情况
并使alarmtest永远运行
来自cpu 0的unexpected trap 14 eip 801067cb (cr2=0x801050cf)
内核.asm中的eip 0x801067cb是什么?
trap()中的tf->esp = tf->eip。
发生了什么事?
这是一个CPL=0到CPL=0的中断
所以h/w没有交换栈
所以它没有保存%esp
所以tf->esp包含垃圾
更重要的一点是,中断可以在内核中发生(在xv6中,而不是在JOS中)
Q: what if another timer interrupt goes off while in user handler?
works, but confusing, and will eventually run out of user stack
maybe kernel shouldn’t re-start timer until handler function finishes
13.中断介绍了并发性(concurrency
)
其他代码在我的代码之间运行
例如,我的代码是
- add %eax, %ebx
- add %ebx, %exc
问:其他代码可能在1和2之间运行吗? 是的!
对于用户代码来说可能没那么糟糕
内核将以相同的状态恢复用户代码
但是,alarmtest应该知道,periodic()可以在任意两条指令之间运行
对于内核代码可能很难
中断处理程序可以更新我的代码可以观察到的状态
my code: interrupt:
%eax = 0
if %eax = 0 then %eax = 1
f()
f()可能被执行,也可能不被执行!
要使代码块具有“atomic
”(原子性),请关闭中断
cli()
sti()
我们第一次遇到“并发性
”
我们将在讨论locking
时会回到这一点
14.Interrupt evolution
中断过去相对较快;现在它们变慢了
老方法:每个事件都会导致中断,简单的h/w,智能的s/w
新方法:h/w在中断之前完成大量工作
有些设备每秒生成事件的速度超过一微秒
例如,千兆以太网每秒可以传送150万个小数据包
中断的时间为一微秒
save/restore state
cache misses
如果中断的速度超过每秒1微秒,该怎么办?
15.轮询(Polling
):与设备交互的另一种方式
处理器旋转,直到设备需要关注
如果设备很慢,就会浪费处理器周期
但如果设备速度快,价格也不贵
不保存寄存器等。
如果事件总是在等待,则不需要一直提醒软件
Polling
versus(对比) interrupts
对于高速设备,轮询比中断好
低速率设备的话,中断好些,例如键盘
持续的轮询会浪费CPU
在轮询和自动中断之间切换
当速率较低时中断(因为轮询会浪费CPU周期)
当速率高时进行轮询(中断会浪费CPU周期)
更快地将中断转发到用户空间
用于页面错误和用户处理的设备
h/w直接交付给用户,w/o内核干预?
更快地通过内核转发路径?
我们会在课程的后面看到很多这样的论题