陸毅男 B10515036
register :
ax 0xaa55
bx 0x0
cx 0x0
dx 0x80
sp 0x6f2c
bp 0x0
si 0x0
eip 0x7c00
eflags 0x202
cs 0x0
ss 0x0
ds 0x0
es 0x0
fs 0x0
gs 0x0
定義:
Trap 又稱為exception,是ㄧ種軟體的中斷,它是因為錯誤(ex.除以0 或不正確的記憶體存取)或是由使用者程式提出需要作業系統服務的特定要求所產生。
interrupts 現在的作業系統是中斷驅動式(interrupt driven)。如果沒有行程要執行,沒有I/O裝置要服務和沒有使用者需要回應,則作業系統將安靜的進入等待事件的發生。
Systems calls, exceptions, and interrupts
There are three cases when control must be transferred from a user program to the kernel:
1.system call:當程序要求操作系統服務時。
2.exceptions:當一個程序形成非法行為。(e.g.除以零,嘗試訪問不存在的memory等)
3.interrupts:當device發出信號表示需要OS的注意。(e.g.當disk在讀取disk中的block時,它生成一個interrupts去警告OS disk中的block可以被檢索。)
在這三種情況下,OS設計必須保存processor’s registers、OS必須在Kernel中執行、系統一定要選擇一個地方給kernel開始執行、kernel必須可以檢查相關事件資訊。
當interrupts產生時,基本動作如下:
1.暫停正常processor loop並開始執行新的sequence名為interrupt handler
2.儲存被暫停的processor’s registers(以便障礙排除後回到user mode繼續執行)
interrupt handler中最大的困難是由user mode切換到kernel mode再切回user mode
X86 protection:
x86具有4個保護級別,編號為0(最高權限)至3(最低權限)。大多數操作系統只使用2個級別:0和3,對應為kernel mode和user mode。
x86的interrupt descriptor table (IDT)有256個entry
在x86中進行system call,系統會由int n指令產生interrupt(n為IDT中的index)!
上圖顯示了int指令完成後的stack,而且包含一個privilege-level change,如果int指令不需要privilege-level change時,x86不會保存%ss和%esp。
%eip會指向下個指令,OS可以使用iret指令pop出int n指令存在stack的值,並並在保存的%eip處繼續執行。
Code: The first system call
xv6 source code(8407-8414)
#exec(init, argv)
.globl start
start:
pushl $argv //將exec的參數push到 stack
pushl $init
pushl $0 // where caller pc would be
movl $SYS_exec, %eax //push system call number to %eax
int $T_SYSCALL
system call的號碼會對應整個syscalls array(一個function pointers),並且會int指令將processor由user mode轉換到kernel mode,kernel才可以調用正確的kernel function。(i.e. sys_exec)
Code: Assembly trap handlers
xv6必須設置x86硬件,以便在遇到int指令時做一些合理的事情,這會導致處理器生成trap。x86可以允許256個不同的interrputs
在main中用tvinit建立一有256個entry的IDT
xv6 source code(3367-3376)
tvinit(void)
{
int i;
for(i = 0; i < 256; i++)
SETGATE(idt[i], 0, SEG_KCODE<<3, vectors[i], 0);
SETGATE(idt[T_SYSCALL], 1, SEG_KCODE<<3, vectors[T_SYSCALL], DPL_USER);
initlock(&tickslock, “time”);
}
kernel將system call權限設為DPL_USER,讓使用者程式用明確的int指令產生trap,xv6不允許使用int引發其他interrputs,如果processer嘗試這樣做,將會成為例外指向vector 13。
當保護等級從user mode轉換為kernel mode,kernel不能使用user process的stack,因為可能會無效,因此設置一個task segment descriptor來執行trap的stack切換
switchuvm會將user process的kernel stack的最頂端的位址儲存進task segment descriptor
xv6 source code(1860-1881)
switchuvm(struct proc *p)
{
if(p == 0)
panic(“switchuvm: no process”);
if(p−>kstack == 0)
panic(“switchuvm: no kstack”);
if(p−>pgdir == 0)
panic(“switchuvm: no pgdir”);pushcli();
mycpu()−>gdt[SEG_TSS] = SEG16(STS_T32A, &mycpu()−>ts,
sizeof(mycpu()−>ts)−1, 0);
mycpu()−>gdt[SEG_TSS].s = 0;
mycpu()−>ts.ss0 = SEG_KDATA << 3;
mycpu()−>ts.esp0 = (uint)p−>kstack + KSTACKSIZE;
// setting IOPL=0 in eflags and iomb beyond the tss segment limit
// forbids I/O instructions (e.g., inb and outb) from user space
mycpu()−>ts.iomb = (ushort) 0xFFFF;
ltr(SEG_TSS << 3);
lcr3(V2P(p−>pgdir)); // switch to process’s address space
popcli();
}
當trap出現時:
1.如果processor正在執行user mode:從task segment descriptor中存取%ss和%esp,再將舊user的%ss和%esp放進新的stack
2.如果processor正在kernel mode中執行:nothing happens
然後將%eflags、%cs、%eip registers放進stack
某些traps(e.g. page fault),processor也輸入一些error word,然後加載IDT中的%EIP和%CS
Code: C trap handler
在檢查system calls之後,trap尋找硬體中斷。除了預期的硬體設備之外,trap還可能由spurious interrupt、不想要的硬體中斷引起。如果trap不是system calls和硬體的信號所出現,則trap則是由錯誤行為(e.g. 除以零)引起的。
xv6 source code(3401-3480)
trap(struct trapframe *tf)
{
if(tf−>trapno == T_SYSCALL){
if(myproc()−>killed)
exit();
myproc()−>tf = tf;
syscall();
if(myproc()−>killed)
exit();
return;
}
// trap尋找hardware interrupts
switch(tf−>trapno){
case T_IRQ0 + IRQ_TIMER:
if(cpuid() == 0){
acquire(&tickslock);
ticks++;
wakeup(&ticks);
release(&tickslock);
}
lapiceoi();
break;
case T_IRQ0 + IRQ_IDE:
ideintr();
lapiceoi();
break;
case T_IRQ0 + IRQ_IDE+1:
// Bochs generates spurious IDE1 interrupts.
break;
case T_IRQ0 + IRQ_KBD:
kbdintr();
lapiceoi();
break;
case T_IRQ0 + IRQ_COM1:
uartintr();
lapiceoi();
break;
case T_IRQ0 + 7:
case T_IRQ0 + IRQ_SPURIOUS:
cprintf(“cpu%d: spurious interrupt at %x:%x\n”,
cpuid(), tf−>cs, tf−>eip);
lapiceoi();
break;
// 不是system call和hardware interrupts
default:
if(myproc() == 0 || (tf−>cs&3) == 0){
// In kernel, it must be our mistake.
cprintf(“unexpected trap %d from cpu %d eip %x (cr2=0x%x)\n”,
tf−>trapno, cpuid(), tf−>eip, rcr2());
panic(“trap”);
}
// In user space, assume process misbehaved.
cprintf("pid %d %s: trap %d err %d on cpu %d "
“eip 0x%x addr 0x%x−−kill proc\n”,
myproc()−>pid, myproc()−>name, tf−>trapno,
tf−>err, cpuid(), tf−>eip, rcr2());
myproc()−>killed = 1;
}
// proc()->killed的用途為clean up the user process
// 終止process
if(myproc() && myproc()−>killed && (tf−>cs&3) == DPL_USER)
exit();if(myproc() && myproc()−>state == RUNNING &&
tf−>trapno == T_IRQ0+IRQ_TIMER)
yield();// 確定process是否終止
if(myproc() && myproc()−>killed && (tf−>cs&3) == DPL_USER)
exit();
}
Code: System calls
syscall會加載trap frame中的包含%eax的system call number,而%eax的index會對應到system call tables。
在第一個system call中,%eax會包含SYS_exec的值,syscall將調用system call tables中的SYS_exec’th entry,並且會對應到sys_exec上。
xv6 source code(3701-3714)
syscall(void)
{
int num;
struct proc *curproc = myproc();
num = curproc−>tf−>eax; // 當trap回到user space,他會載入cp->tf的值到registers
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
curproc−>tf−>eax = syscallsnum; // 當exec return,return system call handler的值
} else {
cprintf("%d %s: unknown sys call %d\n",
curproc−>pid, curproc−>name, num);
curproc−>tf−>eax = −1;
}
}
Code: Interrupts
interrupts類似system calls,除了設備都可以隨意生成interrputs。
當底板向cpu發出信號時(e.g.鍵盤輸入),我們必須對device產生interrputs,並要求cpu接收interrputs。
Drivers
Drivers是管理特定設備的操作系統中的代碼:它告訴設備硬件執行操作,配置設備在完成時生成interrputs,並處理產生的interrputs。
Drivers必須了解設備的接口(e.g.哪些I / O端口做什麼),並且該接口可能很複雜而且資訊很少。
Code: Disk driver
IDE設備提供對連接到PC標準IDE控制器的磁盤的訪問。
IDE現在已經不再支持SCSI和SATA,但界面很簡單,讓我們專注於驅動程序的整體結構而不是特定硬件的細節
data structure:
xv6 source code(3850-3860)
struct buf {
int flags;
uint dev;
uint blockno; // block number
struct sleeplock lock;
uint refcnt;
struct buf *prev; // LRU cache list上一個buffer
struct buf *next; // 下一個buffer
struct buf *qnext; // 先後
uchar data[BSIZE]; // data
};