笔记012 - x86 v6 book | Chapter 3 Traps, interrupts, and drivers

Systems calls, exceptions, and interrupts

术语词:本章中trap和interrupt概念是可以互换的。但trap通常由当前在一个处理器上执行的程序所引起(例如当前程序调用系统调用并生成trap);interrupt通常由设备引起,与当前执行程序没有关系。比如,磁盘可能在为某个程序检索完一个block之后生成interrupt,但interrupt生成的时候可能处理器正在执行其他程序。interrupt同其他活动可能是并发发生的,所以比trap的情况更复杂。尽管x86的官方术语是interrupt,xv6源码将所有情况都参考为trap,很大原因是因为PDP11/40使用了这个术语,这也是传统的Unix术语。

X86 protection

xv6有4个保护级别,编号为0-3,0代表最高权限。实际上大多数OS只使用两个级别,0为内核模式,3为用户模式。xv6在执行指令的时候,当前特权级别保存在%cs中的CPL域。

IDT定义了xv6的中断处理程序,共有256项,处理相关中断的时候,IDT项给出了相应的%cs和%eip。

xv6通过int n的方式调用系统调用,n表示IDT的第n项,int指令包括以下步骤:
1、从IDT中找到第n个描述符,n是int指令的参数。
2、检查是否满足CPL <= DPL,CPL在%cs中,DPL是描述符的特权级别。
3、如果目标段选择子的PL < CPL,则将%esp和%ss保存到CPU的内部寄存器中。
4、从任务段描述符task segment descriptor中加载%esp和%ss。
5、Push %ss(old user’s)。
6、Push %esp(old user’s)。
7、Push %eflags。
8、Push %cs。
9、Push %eip。
10、清除%eflags的一些位。
11、设置%cs和%eip为IDT项给出的值。

int指令执行之后的内核栈变化:

xv6使用Perl脚本生成IDT项指向的处理程序入口点entry point。如果处理器没有将错误码进栈,entry会将错误码进栈,之后中断号进栈,然后跳转到alltraps。

Code: C trap handler

假如引发的trap是T_SYSCALL,调用系统调用处理函数syscall。如果trap不是系统调用,也不是硬件设备关注的,xv6假设它是由不正确行为引起的。如果是由用户程序引起的trap,xv6会打印出相关细节信息,并设置cp->killed确保用户程序被清理。如果是由内核引起的trap,则是内核bug,xv6打印出细节信息之后调用panic。

Code: System calls

参考之前的博客中关于xv6系统调用的介绍。

Code: Interrupts

主板上的设备可以产生中断,CPU需要设置硬件来处理这些中断。
interrupt类似于系统调用,除了设备可以在任何时候生成interrupt。主板上的硬件在需要的时候会给CPU发送信号,因此必须给设备编程使之可以产生中断,且CPU可以接收该中断。

例子:timer设备和timer中断。
假设timer设备每秒产生100次中断,保证内核可以追踪时间的流逝并在多个执行进程间进行时间分片。

xv6早期的主板有一个可编程中断控制器PIC,见picirq.c。
PIC全称Programmable Interrupt Controller,通常是指Intel 8259A双片级联构成的最多支持15个interrupts的中断控制系统。
而随着多处理器PC主板的出现,需要一种新方式来处理中断,因为每个CPU都需要一个中断控制器来处理发送给它的中断。同时,还需要一种将中断路由给CPU的方法,这种方法包括两部分,一部分位于I/O系统(the IO APIC, ioapic.c),一部分位于每个CPU(the local APIC, lapic.c)。xv6被设计为支持多处理器PC主板,需要编程每个处理器使之能接收到中断。
APIC全称Advanced Programmable Interrupt Controller,APIC是为了多核平台而设计的。它由两个部分组成IOAPIC和LAPIC,其中IOAPIC通常位于南桥中用于处理桥上的设备所产生的各种中断,LAPIC则是每个CPU都会有一个。IOAPIC通过APICBUS(现在都是通过FSB/QPI)将中断信息分派给每颗CPU的LAPIC,CPU上的LAPIC能够智能的决定是否接受系统总线上传递过来的中断信息,而且它还可以处理Local端中断的pending、nesting、masking,以及IOAPIC于Local CPU的交互处理。

为了能在单处理器机上正确运行,xv6对PIC进行编程。每个PIC最多可以处理8个设备中断,并在处理器的中断引脚上多路传输。为了能够支持多于8个设备,主板上通常级联至少两个PIC。xv6使用inb和outb指令规划master生成IRQ0-7,slave生成IRQ8-15。初始化PIC阶段屏蔽所有中断。timer.c设置定时器并使之在PIC上生效。
基于Intel 80x86的PC使用两片8259A级联的方式组成了可以管理15级中断向量的一个中断系统,下图是它的一个连接示意图。在每台电脑的系统中,是由一个中断控制器8259或是8259A的芯片(现在此芯片大都集成到其它的芯片内)来控制系统中每个硬件的中断控制。目前共有16组IRQ,去掉其中用来作桥接的一组IRQ,实际上只有15组IRQ可供硬件调用。两片8259A,一片为Master,另一片为Slaver。其中Slaver的INT接到Master的IRQ2上。8259A有两种工作模式分别为编程和操作模式。BIOS初始化的时候会先通过IO port对8259A进行编程配置,在此之后8259A就可以响应来自外部设备的中断请求了。Master的IO address是0x20 0x21; Slaver的IO address是0xA0 0xA1。

这16组IRQ的主要用途如下表:

多处理器环境下,xv6必须编程IOAPIC和每个CPU的LAPIC。IOAPIC有一张表,处理器通过内存映射I/O来编程表中的项,而不是使用inb和outb指令。初始化过程中,xv6映射 interrupt 0到IRQ 0,以此类推,但禁用它们。特定设备将允许对应的中断,并告知该中断将路由给哪个处理器,例如xv6将keyboard中断路由到处理器0。LAPIC内嵌定时器芯片,所以每个处理器都可以单独接收定时器中断,xv6在lapicinit函数中使该芯片起作用。
Intel APIC由一组中断输入信号,一个24*64bit的Programmable Redirection Table(PRT),一组register和用于从APIC BUS(FSB/QPI)上传送APIC MSG的部件组成,当南桥的IO device通过IOAPIC的interrupt lines产生interrupt,IOAPIC将根据内部的PRT table格式化成中断请求信息,并将该信息发送给目标CPU的LAPIC,再由LAPIC通知CPU进行处理。下图是一个基于Intel APIC的连接示意图,如下图所示IOAPIC上有24个interrupt pin,每一个pin都对应一个RTE,所以针对每一个interrupt pin都可以单独设定它的mask,触发方式(level,edge trigger),中断管脚的极性,传送方式,传送状态,目的地,中断向量等。

处理器通过设置eflags的IF位可以控制是否接收中断,cli指令清除IF,禁用中断,sti指令置位IF,允许中断。xv6在启动主处理器和其他处理器过程中关中断,每个处理器的scheduler将开中断。为了控制某些代码片段不被中断,xv6在执行这些代码片段过程中禁用中断。

定时器中断对应中断向量32(xv6选择中断向量32来处理IRQ 0),在idtinit进行设置。定时器中断向量32和系统调用中断向量64的唯一区别是定时器中断向量32是由interrupt gate进行处理而不是trap gate。所以处理定时器中断的时候会清除IF以屏蔽其他中断。trap函数在处理一次定时器中断时,做了两个工作:增加时间ticks和调用wakeup函数。

Drivers

Add a network driver

显示 Gitment 评论