項目一:
截圖瞬間+暫存器 ax cx bx 文字敘述
截圖瞬間如下:

kernel執行到0x7c00,當下暫存器:
項目二:
重要概念總結
Chapter 0
Operating system interfaces
作業系統為程式(programs)管理并abstract底層硬體,為程序提供系列更有用的服務與受控的interact方式,使得計算機資源可在多個程式間共享、多個程序可同時運行。計算機作業系統通過向用戶程式提供接口,使其能夠訪問硬體。
進程(process)通過system calls使用kernel services,kernel提供的system calls就是用戶程式可見的interfaces。xv6 kernel提供了部分Unix傳統的system calls,這些system calls分別提供了管理進程和記憶體(memory)、I/O和文件描述符(file descriptors)、pipe和文件系統的服務。
Processes and memory
xv6在進程和記憶體管理方面為程式提供了fork、exit、wait、exec、sbrk等system calls。
其中*fork()*為一個進程創建一個子進程,并向父進程return子進程id,向子進程return 0;*exit()*會導致進程停止運行,并release掉memory和open files等資源;*wait()*等待一個子進程退出,并return其id;*exec(filename,argv)從file system的file中讀取memory image并將其替換到calling process的記憶體中;當一個系統需要額外的記憶體時,可以通過sbrk(n)*來增加n個記憶體的bytes,并返回新memory的地址。
I/O and File descriptors
File descriptor是一個integer,它代表著一個能夠被kernel管理并能夠被進程讀寫的object,file descriptor的interface是對 files、pipes、devices等的abstract,每個進程都有一張table,每個進程都有一個從0開始的file descriptor space。按照慣例,一個進程從file descriptor 0讀入,從1輸出,從2輸出錯誤。
system call read(fd,buf,n)從fd中讀出最多n個bytes,并將從fd讀出的內容copy到buf中,return讀出的byte數量,read從當前file offset讀取data并把offset增加讀出的byte數;當無data可讀時,read會return0,文件讀取結束。
write(fd, buf, n) 將buf中的n個bytes寫入fd,并return 實際write的byte數,同樣,write也從當前offset開始寫入并增加其offset。
close(fd) 會release一個file descriptor,使得其將能夠被 open,pipe,dup 等system call重用(reuse)。
dup(fd) 複製一個已有file descriptor,return一個指向同一輸入/輸出object的新descriptor。
Pipes
Pipe是一個kernel buffer,它向process提供一個用於讀操作的file descriptor和一個用於寫操作的file descriptor。pipe(p) 創建一個新的pipe并將讀寫file descriptor記錄在array p中。
File system
xv6文件系統提供data file和directories。chdir 用於改變當前進程的目錄(directories);mkdir 創建一個新的目錄;open 加上O_CREATE標誌打開一個新文件;mknod 創建一個新的無內容device文件;fstat 獲取一個file descriptor指向的文件的信息,填充struct stat;link 為文件系統創建另一個名稱,指向同一個inode;unlink 從文件系統移除一個文件名
Chapter 1
項目三:
Chapter 3 Traps, interrupts, and drivers
總結
運作原理
由於大多數情況下只有kernel擁有處理interrupt所需的特權和狀態(privilege&state),所有的interrupt都由kernel管理。并執行作業系統處理system call、interrupt和exception都採用相同的硬件處理機制:系統需要保存處理器的暫存器(process’s register)、做好在kernel中執行的準備并選擇一個kernel開始執行的地方;kernel需要獲得該事件的信息、保證安全性并保持user process和kernel的獨立。
此次代碼文件包括trapasm.S,trap.c,syscall.c,initcode.S,usys.S, vector.S,lapic.c, ioapic.c, picirq.c,每個文件的主要作用為:
trapasm.S: 建立trap frame,呼叫trap(tf),并在呼叫trap后恢復暫存器的值。
trap.c: 包含建立并加載IDT、中斷處理程序的function。
syscall.c: 包含syscall()和獲取syscall()參數的相function。
initcode.S:初始化進程
usys.S: define SYSCALL_name
vector.S:define IDT 的256個entries。
lapic.c:處理內置(non-I/O)interrupt。
ioapoc.c:為SMP系統管理hardware interrupt。
picirq.c:可編程interrupt controller及其相關functions。
程式流程
僅為有call function的程式繪製流程
trapasm.S

trap.c

syscall.c

picirq.c

資料結構&代碼解釋
本部分資料結構部分將以代碼注釋形式呈現,代碼解釋部分以文字段落形式解釋
1.trapasm.S
IDT加載和entry建立完成后,xv6通過硬體建立一個task segment descriptor,給暫存器%esp賦值并加載一個target segment selector,switchuvm() 將kernel中的user process stack的頂部的stack address儲存在task segment descriptor中,若processor處於user模式,則從task segment descriptor中加載%ss和%esp,若在kernel模式,則不進行任何處理。接著,processor push %eflags、%cs、%eip,在某些trap中還會push error code。最後,processor從IDT入口加載%eip和%cs,call vectors.pl文件打開IDT入口并跳轉到該文件。
· Build trap frame
當processor push ss,esp,eflags,cs,eip 后,alltraps push ds,ed,fs,gs 和剩餘32個通用暫存器
alltraps:
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushal #push剩餘32通用暫存器
· Set up data segments
通過 alltraps ,暫存器 %ds, %es 含有指向data segment的指針值。
movw $(SEG_KDATA<<3), %ax
movw %ax, %ds
movw %ax, %es
· Call trap(tf)
此時暫存器已建立,可通過暫存器獲取指向segment的指針,暫存器esp存有指向trap frame的指針值。將該指針值傳入 trap 中,并call trap ,執行完 trap 后,將esp值+4,彈出棧頂元素后,esp指向trapret。
pushl %esp # tf=%esp,將指針傳入trap
call trap
addl $4, %esp #此時esp指向trapret
· 通過trapret恢復用戶進程現場
這段代碼在將32位通用暫存器及其它暫存器pop后,將esp值增加8,這樣將會pop trapno 和 errcode,全部出棧后,執行 iret ,跳轉到用戶程序
.globl trapret
trapret:
popal
popl %gs
popl %fs
popl %es
popl %ds
addl $0x8, %esp #彈出trapno 和 errcode
iret
2. trap.c
· tvinit()
用於建立IDT的256個入口,最後的 initlock init一個互斥鎖。
tvinit(void)
{
int i;
for(i = 0; i < 256; i++)
// idt[i] :要建立的interrupt gate,
//0 :istrap 位,0表示是一個interrupt gate
//SEG_KCODE<<3 :是一個code segment selector的值,操作后為8
//vectors[i] :offset的值,這裡為interrupt 程序的地址
//0 :descriptor privilege level
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");
}
· idtinit()
用於call x86.h中的lidt(),加載IDT。
void idtinit(void)
{
lidt(idt, sizeof(idt));
}
·trap(struct trapframe tf)
if(tf−>trapno == T_SYSCALL){
if(myproc()−>killed)
exit();
myproc()−>tf = tf;
syscall();
if(myproc()−>killed)
exit();
return;
}
case T_IRQ0 + IRQ_TIMER:
if(cpuid() == 0){
acquire(&tickslock); //互斥操作
ticks++; //如果cpu值為0 ,ticks值+1
wakeup(&ticks); //call wakeup,喚醒處於sleeping狀態的進程,并設置為RUNNABLE
release(&tickslock); //互斥操作
}
lapiceoi();
break;
b. IDE, keyboard,uarl interrupt
case T_IRQ0 + IRQ_IDE:
ideintr();
lapiceoi();
break;
case T_IRQ0 + IRQ_IDE+1:
break;
case T_IRQ0 + IRQ_KBD: //keyboard
kbdintr();
lapiceoi();
break;
case T_IRQ0 + IRQ_COM1: //uarl devices
uartintr();
lapiceoi();
break;
case T_IRQ0 + 7:
c. Spurious interrupt
case T_IRQ0 + IRQ_SPURIOUS:
cprintf("cpu%d: spurious interrupt at %x:%x\n",
cpuid(), tf−>cs, tf−>eip);
lapiceoi();
break;
d. Others
其他情況下,如果是在kernel產生的,則print相關信息;如果是在user space產生的,則kill該進程
default:
if(myproc() == 0 || (tf−>cs&3) == 0){
cprintf("unexpected trap %d from cpu %d eip %x (cr2=0x%x)\n",
tf−>trapno, cpuid(), tf−>eip, rcr2());
panic("trap");
}
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; //等於1,kill進程
最後檢查當前進程的myproc()−>killed
if(myproc() && myproc()−>killed && (tf−>cs&3) == DPL_USER)
exit();
// Force process to give up CPU on clock tick.
// If interrupts were on while locks held, would need to check nlock.
if(myproc() && myproc()−>state == RUNNING &&
tf−>trapno == T_IRQ0+IRQ_TIMER)
yield();
// Check if the process has been killed since we yielded
if(myproc() && myproc()−>killed && (tf−>cs&3) == DPL_USER)
exit();
3. syscall.c
· syscall()
syscall()將system call分配到不同的function中,處理后return結果儲存在暫存器eax中。
void 3701 syscall(void)
{
int num;
struct proc *curproc = myproc();
num = curproc−>tf−>eax; //加載system call number
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) { //判斷是否在範圍內并有效,
curproc−>tf−>eax = syscalls[num](); //如果是,則return至eax
} else { //否,則print錯誤信息并return -1
cprintf("%d %s: unknown sys call %d\n",
curproc−>pid, curproc−>name, num);
curproc−>tf−>eax = −1;
}
}
尋找system call argument
此過程分為三個部分:獲取int類型argument、檢驗argument和將argument轉換為指針,由三個function組成。
· argint
該function取得system call的第n個int類型argument,將其保存在ip中,通過fetchint 檢驗地址是否有效,若有效則將地址值轉換為指針。
argint(int n, int *ip)
{
return fetchint((myproc()−>tf−>esp) + 4 + 4*n, ip); // return第n個int類型的argument
}
由於system call argument在proc->tf->esp的後一位,即 proc->tf->esp + 4,則第n個argument的地址為 proc->tf->esp + 4 + 4 * n。
fetchint 檢驗地址值是否有效并在process address space內,若不在,則return -1;并引導一個segmentation trap并kill 該進程。
· argptr
該function通過 argint 返回值對argument進行檢驗,取得第n個argument后對其進行檢驗,若有效,則將其轉換為指針。
argptr(int n, char **pp, int size)
{
int i; 3614 struct proc *curproc = myproc();
if(argint(n, &i) < 0)
return −1; //不在user space
if(size < 0 || (uint)i >= curproc−>sz || (uint)i+size > curproc−>sz)
//check argument
return −1;
*pp = (char*)i;
return 0;
}
· argstr
argstr()獲取第n個argument,并通過fetchstr將其轉換為string pointer并return。
int
argstr(int n, char **pp)
{
int addr;
if(argint(n, &addr) < 0) //檢查是否有效
return −1; //無效返回-1
return fetchstr(addr, pp); //有效通過fethstr轉換為string pointer
}
4. usys.S
將 SYS_name的數據移到暫存器eax中,調用int指令處理eax中的指令,并通過ret指令返回user process’s space。
#define SYSCALL(name)
.globl name; //將name定義為global variable
name:
movl $SYS_ ## name, %eax; //將name放入%eax
int $T_SYSCALL;
ret
4. vectors.pl & vectors.S
vectors.pl為一個Perl scrip,用於啟動IDT入口的entry points,vectors.S由vectors.pl生成,其中define了每個interrupt的入口程式和地址。vectors.S push一個error code(對於沒有error code的,push 0),push結束后,interrupt跳轉到alltraps。
print "# generated by vectors.pl − do not edit\n";
print "# handlers\n"; 3259 print ".globl alltraps\n";
for(my $i = 0; $i < 256; $i++){
print ".globl vector$i\n";
print "vector$i:\n"; //global variable vertors[i]
if(!($i == 8 || ($i >= 10 && $i <= 14) || $i == 17)){
print " pushl \$0\n";
}
print " pushl \$$i\n";
print " jmp alltraps\n";
}