6.S081 附加Lab1 用户执行系统调用的过程(Trap)
6.S081 附加Lab011 用戶執行系統調用的過程(Trap)
文章目錄
- 6.S081 附加Lab011 用戶執行系統調用的過程(Trap)
- 0. 一些背景說明
- 1. 進入內核前的準備write() -> ECALL
- 2. 進入內核后的ECALL -> uservec()
- 3. 保存用戶寄存器內容uservec()
- 4. 處理trap——usertrap()
- 5. usertrapret()
- 6. userrret()
0. 一些背景說明
附加lab從011開始編號,下一個是012(為了避開lab10,lab11…)
注意,這里主要接 6.S081-5用戶空間和內核空間的切換–Trap機制
代碼流程:write() -> ECALL -> uservec()(Trampoline.s) -> usertrap() -> syscall() -> sys_write() -> syscall() -> usertrapret() -> usertrapret() ->ret
Trap的時候,我們需要做什么?(當前處于user mode,現在需要執行系統調用)
- 保存32個用戶寄存器
- 保存當前PC
- mode切換成supervisor
- SATP從user page table切換成kernel_pagetable
- Stack Frame和Stack Pointer都需要改變,因為需要一個stack來調用kernel的函數
- 跳入kernel
本實驗將主要關心的是,執行系統調用時計算機的狀態 – 可以用寄存器狀態來判斷,主要關心的寄存器有:
-
PC 程序計數器(Program Counter Register)
-
mode標志位(是supervisor mode 還是 user mode)
-
SATP(Supervisor Address Translation and Protection)寄存器,它包含了指向page table的物理內存地址
-
STVEC(Supervisor Trap Vector Base Address Register)寄存器,它指向了內核中處理trap的指令的起始地址。
-
SEPC(Supervisor Exception Program Counter)寄存器,在trap的過程中保存程序計數器的值。
-
SSRATCH(Supervisor Scratch Register)寄存器
后面的實驗內容和流程,主要是實驗內容參考-中文notes,實驗的流程和工具參考6.S081 Lab00 xv6啟動過程
總結一下:系統調用被刻意設計的看起來像是函數調用,但是背后的user/kernel轉換比函數調用要復雜的多。之所以這么復雜,很大一部分原因是要保持user/kernel之間的隔離性,內核不能信任來自用戶空間的任何內容。
1. 進入內核前的準備write() -> ECALL
打開sh.c,代碼如下,注意write(2, "$ ", 2);,它是我們接下來要追蹤的系統調用。-- shell 將 "$ " 通過 write系統調用輸出到文件描述符2。 – 注意,我這里其實原本是fprintf(2, "$ ");,需要修改成write ,然后make clean,然后再調試
int getcmd(char *buf, int nbuf) {// fprintf(2, "$ ");write(2, "$ ", 2);memset(buf, 0, nbuf);gets(buf, nbuf);if(buf[0] == 0) // EOFreturn -1;return 0; }int main(void) {static char buf[100];int fd;// Ensure that three file descriptors are open.while((fd = open("console", O_RDWR)) >= 0){if(fd >= 3){close(fd);break;}}// Read and run input commands.... }進入調試
- make qemu-gdb,并使用cpu個數 = 1(方便調試)
- 新建窗口,打開gdb并用gdb遠程鏈接 qemu的gdb
- 實際上被調用的write函數的實現如下(usys.s中可以看到) – 還有很多系統調用都是這樣實現的,比如pipe, close, read, kill …
? ecall會讓我們跳轉到內核,kernel執行完之后會返回,然后執行ret,最終返回到shell。
- 找到shell 的write對應的ecall地址(再shell.asm中找到如下代碼👇),可以看到ecall對應地址是0xd66
- 在0xd66處設置斷點,然后運行 (注意這里的display/i $pc在每次斷點的時候,都顯示下一條指令的反匯編)
- 可以看到,我們成功在shell的write對應的ecall處停下來了,然后我們刪除當前斷點,打印pc,打印全部32個用戶寄存器(info reg)。這里的a0, a1, a2是shell傳給write系統調用的參數(write(2, "$ ", 2)) ——所以a0是2(文件描述符),a1是字符串指針,a2是2(寫入字符數)。
- 打印a1的內容,確實是"$ "
-
此外,寄存器可以看出,sp和pc還都比較接近0,說明現在運行在用戶態。(-- 說明是虛擬地址),因為物理地址,至少都是從0x80000(OS啟動處)開始的了。 --xv6中 kernel的虛擬地址和物理地址是一樣的。
-
查看SATP寄存器(頁表地址)
-
查看頁表:qemu界面,按ctrl + a然后按c可以進入qemu console, 輸入info mem可以查看頁表的,但是我這里看不到。
尋找原因:在如下所示的路徑文件下,存在的代碼如下,說明我必須是I386機型,才能使用info mem,這里我進行了注釋的修改,最終make會報錯,因此后面關于page table的輸出,我都只能暫時省去了。
// /home/wc/OS_experiment/qemu-4.1.0/riscv64-softmmu/hmp-commands-info.h// changed by levi #if defined(TARGET_I386) { .name = "mem", .args_type = "", .params = "", .help = "show the active virtual memory mappings", .cmd = hmp_info_mem, }, #endif // { // .name = "mem", // .args_type = "", // .params = "", // .help = "show the active virtual memory mappings", // .cmd = hmp_info_mem, // },- page table的正確輸出如圖👇——后面幾頁地址很大,是因為位于彈簧床程序中。(這是trampoline page。trampoline page包含了內核的trap處理代碼)
2. 進入內核后的ECALL -> uservec()
- 繼續執行,ecall
- 打印pc,發現已經是很大的地址了 – 說明進入了內核 根據現在的程序計數器,代碼正在trampoline page的最開始,這是用戶內存中一個非常大的地址,所以現在我們的指令正運行在內存的trampoline page中。
- 查看當前的指令(查看的是pc - 4的內容):這些指令是內核在supervisor mode中將要執行的最開始的幾條指令,也是在trap機制中最開始要執行的幾條指令。
- 繼續查看寄存器的值,發現并沒有改變——因此在此之前,我們還不能往這些寄存器賦值,否則不能正確恢復
- 進入qemu,查看page table,發現還沒改變👇。ecall并不會切換page table,這是ecall指令的一個非常重要的特點。 —— trap處理代碼必須存在于每一個user page table中,因為ecall并不會切換page table,我們需要在user page table中的某個地方來執行最初的內核代碼。而這個trampoline page,是由內核小心的映射到每一個user page table中,以使得當我們仍然在使用user page table時,內核在一個地方能夠執行trap機制的最開始的一些指令。——這里的控制是通過STVEC寄存器實現的,在從內核空間進入到用戶空間之前,內核會設置好STVEC寄存器指向內核希望trap代碼運行的位置。
- 即使trampoline page是在用戶地址空間的user page table完成的映射,用戶代碼不能寫它,因為這些page對應的PTE并沒有設置PTE_u標志位。這也是為什么trap機制是安全的。
ecall指令都做了什么??
根據0. 一些背景說明中的要求,我們只完成了橘色部分,紅色部分還需要別的函數/指令完成👇
Trap的時候,我們需要做什么?(當前處于user mode,現在需要執行系統調用)
- 保存32個用戶寄存器
- 保存當前PC
- mode切換成supervisor
- SATP從user page table切換成kernel_pagetable
- Stack Frame和Stack Pointer都需要改變,因為需要一個stack來調用kernel的函數
- 跳入kernel
為什么ecall 不切換pagetable?——切換page table的代價比較高,不用在不必要的場景切換page table。
所以ecall的下一條指令的位置是STVEC指向的地址,也就是trampoline page的起始地址。(注,實際上ecall是CPU的指令,自然在gdb中看不到具體內容)
3. 保存用戶寄存器內容uservec()
接上面,由于我們的page table中存放了trampoline page(-- 這樣每個進程都有自己的trapframe page,并且此處的虛擬地址總是0x3ffffffe000)
trampframe存放的內容如下(proc.h 中的trapframe的結構體),剛開始有5個kernel實現存放在trapframe中的數據,后面是32個用戶寄存器的內容。
// per-process data for the trap handling code in trampoline.S. // sits in a page by itself just under the trampoline page in the // user page table. not specially mapped in the kernel page table. // the sscratch register points here. // uservec in trampoline.S saves user registers in the trapframe, // then initializes registers from the trapframe's // kernel_sp, kernel_hartid, kernel_satp, and jumps to kernel_trap. // usertrapret() and userret in trampoline.S set up // the trapframe's kernel_*, restore user registers from the // trapframe, switch to the user page table, and enter user space. // the trapframe includes callee-saved user registers like s0-s11 because the // return-to-user path via usertrapret() doesn't return through // the entire kernel call stack. struct trapframe {/* 0 */ uint64 kernel_satp; // kernel page table/* 8 */ uint64 kernel_sp; // top of process's kernel stack/* 16 */ uint64 kernel_trap; // usertrap()/* 24 */ uint64 epc; // saved user program counter/* 32 */ uint64 kernel_hartid; // saved kernel tp/* 40 */ uint64 ra;/* 48 */ uint64 sp;/* 56 */ uint64 gp;/* 64 */ uint64 tp;/* 72 */ uint64 t0;/* 80 */ uint64 t1;/* 88 */ uint64 t2;/* 96 */ uint64 s0;/* 104 */ uint64 s1;/* 112 */ uint64 a0;/* 120 */ uint64 a1;/* 128 */ uint64 a2;/* 136 */ uint64 a3;/* 144 */ uint64 a4;/* 152 */ uint64 a5;/* 160 */ uint64 a6;/* 168 */ uint64 a7;/* 176 */ uint64 s2;/* 184 */ uint64 s3;/* 192 */ uint64 s4;/* 200 */ uint64 s5;/* 208 */ uint64 s6;/* 216 */ uint64 s7;/* 224 */ uint64 s8;/* 232 */ uint64 s9;/* 240 */ uint64 s10;/* 248 */ uint64 s11;/* 256 */ uint64 t3;/* 264 */ uint64 t4;/* 272 */ uint64 t5;/* 280 */ uint64 t6; };查看trampoline.S的代碼,第一行就是csrrw a0, sscratch, a0這個指令交換了a0和sscratch兩個寄存器的內容。(為了清晰我這里給出了trampoline.S的完整代碼——后續也會用到)
#this is trampoline.S# code to switch between user and kernel space.## this code is mapped at the same virtual address# (TRAMPOLINE) in user and kernel space so that# it continues to work when it switches page tables.## kernel.ld causes this to be aligned# to a page boundary.#.section trampsec .globl trampoline trampoline: .align 4 .globl uservec uservec: ## trap.c sets stvec to point here, so# traps from user space start here,# in supervisor mode, but with a# user page table.## sscratch points to where the process's p->tf is# mapped into user space, at TRAPFRAME.## swap a0 and sscratch# so that a0 is TRAPFRAMEcsrrw a0, sscratch, a0# save the user registers in TRAPFRAMEsd ra, 40(a0)sd sp, 48(a0)sd gp, 56(a0)sd tp, 64(a0)sd t0, 72(a0)sd t1, 80(a0)sd t2, 88(a0)sd s0, 96(a0)sd s1, 104(a0)sd a1, 120(a0)sd a2, 128(a0)sd a3, 136(a0)sd a4, 144(a0)sd a5, 152(a0)sd a6, 160(a0)sd a7, 168(a0)sd s2, 176(a0)sd s3, 184(a0)sd s4, 192(a0)sd s5, 200(a0)sd s6, 208(a0)sd s7, 216(a0)sd s8, 224(a0)sd s9, 232(a0)sd s10, 240(a0)sd s11, 248(a0)sd t3, 256(a0)sd t4, 264(a0)sd t5, 272(a0)sd t6, 280(a0)# save the user a0 in p->tf->a0csrr t0, sscratchsd t0, 112(a0)# restore kernel stack pointer from p->tf->kernel_spld sp, 8(a0)# make tp hold the current hartid, from p->tf->kernel_hartidld tp, 32(a0)# load the address of usertrap(), p->tf->kernel_trapld t0, 16(a0)# restore kernel page table from p->tf->kernel_satpld t1, 0(a0)csrw satp, t1sfence.vma zero, zero# a0 is no longer valid, since the kernel page# table does not specially map p->tf.# jump to usertrap(), which does not returnjr t0.globl userret userret:# userret(TRAPFRAME, pagetable)# switch from kernel to user.# usertrapret() calls here.# a0: TRAPFRAME, in user page table.# a1: user page table, for satp.# switch to the user page table.csrw satp, a1sfence.vma zero, zero# put the saved user a0 in sscratch, so we# can swap it with our a0 (TRAPFRAME) in the last step.ld t0, 112(a0)csrw sscratch, t0# restore all but a0 from TRAPFRAMEld ra, 40(a0)ld sp, 48(a0)ld gp, 56(a0)ld tp, 64(a0)ld t0, 72(a0)ld t1, 80(a0)ld t2, 88(a0)ld s0, 96(a0)ld s1, 104(a0)ld a1, 120(a0)ld a2, 128(a0)ld a3, 136(a0)ld a4, 144(a0)ld a5, 152(a0)ld a6, 160(a0)ld a7, 168(a0)ld s2, 176(a0)ld s3, 184(a0)ld s4, 192(a0)ld s5, 200(a0)ld s6, 208(a0)ld s7, 216(a0)ld s8, 224(a0)ld s9, 232(a0)ld s10, 240(a0)ld s11, 248(a0)ld t3, 256(a0)ld t4, 264(a0)ld t5, 272(a0)ld t6, 280(a0)# restore user a0, and save TRAPFRAME in sscratchcsrrw a0, sscratch, a0# return to user mode and user pc.# usertrapret() set up sstatus and sepc.sret打印sscratch寄存器的內容,現在是2,其實這里就是a0之前的值,正如之前所說,write(2, "$ ", 2);函數調用的時候a0作為第一個傳入參數的保存者,保存的是文件描述符2。也就是說:在進入到user space之前,內核會將trapframe page的地址保存在這個寄存器中,也就是0x3fffffe000這個地址。更重要的是,RISC-V有一個指令允許交換任意兩個寄存器的值。而SSCRATCH寄存器的作用就是保存另一個寄存器的值,并將自己的值加載給另一個寄存器。,所以,現在的a0就是trapframe的首地址(0x3ffffffe000)
(gdb) p/x $sscratch $5 = 0x2 (gdb) p/x $a0 $6 = 0x3fffffe000所以trampoline.S的后續指令(第二三四五六七八…)都有意義了,也就是a0其實是trapframe的首地址,由第二條指令sd ra, 40(a0)可知,ra被保存在了trapframe + 40的位置…。注意代碼的最后(倒數第二條指令,又將a0和sscratch寄存器的內容互換了回去,然后執行sret就返回用戶空間了)
- 然后注意trampoline中的第一條load指令,因為a0是trapframe,所以a0 + 8是kernel_sp(kernel的Stack Pointer)所以這條指令的作用是初始化Stack Pointer指向這個進程的kernel stack的最頂端。
- 指向完這👆條指令之后,我們打印一下當前的Stack Pointer寄存器,這是這個進程的kernel stack。因為XV6在每個kernel stack下面放置一個guard page,所以kernel stack的地址都比較大。下一條指令是tp – 用來保存hartid(CPU編號) —— 保存當前運行在多核CPU的哪一個核上(因為我設置了單核,所以編號一定是0)。
- 接下來是t0(usertrap的函數地址),t1(kernel_pagetable的地址)——實際上嚴格來說,t1的內容并不是kernel page table的地址,這是你需要向SATP寄存器寫入的數據。它包含了kernel page table的地址,但是移位了(注,詳見4.3),并且包含了各種標志位。
下一條指令是交換SATP和t1寄存器。這條指令執行完成之后,當前程序會從user page table切換到kernel page table?,F在我們在QEMU中打印page table,可以看出與之前的page table完全不一樣👇?!晒η袚Q了page table——切換到了kernel page table——有了kernel_pagetable我們就可以讀取kernel的data
這里還有個問題,為什么代碼沒有崩潰?畢竟我們在內存中的某個位置執行代碼,程序計數器保存的是虛擬地址,如果我們切換了page table,為什么同一個虛擬地址不會通過新的page table尋址走到一些無關的page中?看起來我們現在沒有崩潰并且還在執行這些指令。有人來猜一下原因嗎?
學生回答:因為我們還在trampoline代碼中,而trampoline代碼在用戶空間和內核空間都映射到了同一個地址。
之所以叫trampoline page,是因為你某種程度在它上面“彈跳”了一下,然后從用戶空間走到了內核空間。
這就是本科的時候,柏軍老師說的彈簧床可以防止程序“跑飛”(這里的彈簧床和本實驗的彈簧床有所不同)——柏軍老師指的是操作系統啟動的時候,用彈簧床程序引導bootloader去main()的首地址去執行(而不是直接用bootloader執行main),這里的“彈簧床”意思就是,當main()里面出現bug了,就再去彈簧床處執行(再次調用main()),這樣程序就不會因為一次bug而panic。
最后一條指令是jr t0。執行了這條指令,我們就要從trampoline跳到內核的C代碼中。這條指令的作用是跳轉到t0指向的函數中。(前面已經說過,是usertrap函數),當然也可以打印一下:
(gdb) stepi 0x0000003ffffff08e in ?? () 1: x/i $pc => 0x3ffffff08e: jr t0 (gdb) x/3i $t00x8000276a <usertrap>: addi sp,sp,-320x8000276c <usertrap+2>: sd ra,24(sp)0x8000276e <usertrap+4>: sd s0,16(sp)接下來我們就要以kernel stack,kernel page table跳轉到usertrap函數。
4. 處理trap——usertrap()
usertrap某種程度上存儲并恢復硬件狀態,但是它也需要檢查觸發trap的原因,以確定相應的處理方式。代碼如下。
// // handle an interrupt, exception, or system call from user space. // called from trampoline.S // void usertrap(void) {int which_dev = 0;if((r_sstatus() & SSTATUS_SPP) != 0)panic("usertrap: not from user mode");// send interrupts and exceptions to kerneltrap(),// since we're now in the kernel.w_stvec((uint64)kernelvec);struct proc *p = myproc();// save user program counter.p->tf->epc = r_sepc();if(r_scause() == 8){// system callif(p->killed)exit(-1);// sepc points to the ecall instruction,// but we want to return to the next instruction.p->tf->epc += 4;// an interrupt will change sstatus &c registers,// so don't enable until done with those registers.intr_on();syscall();} else if((which_dev = devintr()) != 0){// ok} else if(r_scause() == 13 || r_scause() == 15){// printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);// printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());uvmalloc(p->pagetable, PGROUNDDOWN(r_stval()), PGROUNDDOWN(r_stval()) + 4096);}else {printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());printf("page down:%d\n",PGROUNDDOWN(r_stval()));// printf("r :%d\n",r_scause());// int sz = 0;// while( !(r_stval()>=sz && r_stval()<sz+4096) )// {// sz = sz + 4096;// }// printf("sz:%d\n",sz);p->killed = 1;}if(p->killed)exit(-1);// give up the CPU if this is a timer interrupt.if(which_dev == 2)yield();usertrapret(); }它做的第一件事情是更改STVEC寄存器。取決于trap是來自于用戶空間還是內核空間,實際上XV6處理trap的方法是不一樣的。目前為止,我們只討論過當trap是由用戶空間發起時會發生什么。如果trap從內核空間發起,將會是一個非常不同的處理流程,因為從內核發起的話,程序已經在使用kernel page table。所以當trap發生時,程序執行仍然在內核的話,很多處理都不必存在。
在內核中執行任何操作之前,usertrap中先將STVEC指向了kernelvec變量,這是內核空間trap處理代碼的位置,而不是用戶空間trap處理代碼的位置。
然后來一步步分析代碼,注意看注釋部分
// 找出當前正在運行的進程 -- 通過hartid (之前切換pagetable前已經保存到t0) struct proc *p = myproc(); // 把當前進程的pc保存到當前進程的trapframe(防止進程切換找不到了) // save user program counter. p->tf->epc = r_sepc();- 把當前進程的pc保存到當前進程的trapframe(防止進程切換找不到了);接下來檢查進程是否被killed(這里shell沒有被killed,所以可以繼續執行;記錄ret的地址 = 當前pc + 4——這樣我們會在ecall的下一條指令恢復,而不是重新執行ecall指令;打開中斷(因為XV6會在處理系統調用的時候使能中斷,這樣中斷可以更快的服務,有些系統調用需要許多時間處理。中斷總是會被RISC-V的trap硬件關閉,所以在這個時間點,我們需要顯式的打開中斷。);然后調用syscall。
- syscall函數——根據預定義的函數編號,找到函數地址sys_write
- 運行sys_write(參數保存在a0,a1和a2),現在需要返回了
- syscall之后再次返回usertrap函數,usertrap調用了一個函數usertrapret。
5. usertrapret()
處理返回用戶空間之前,內核要做的工作。
- 首先關閉中斷。我們關閉中斷因為當我們將STVEC更新到指向用戶空間的trap處理代碼時,我們仍然在內核中執行代碼。如果這時發生了一個中斷,那么程序執行會走向用戶空間的trap處理代碼,即便我們現在仍然在內核中,出于各種各樣具體細節的原因,這會導致內核出錯。所以我們這里關閉中斷。
- 在下一行我們設置了STVEC寄存器指向trampoline代碼,在那里最終會執行sret指令返回到用戶空間。位于trampoline代碼最后的sret指令會重新打開中斷。這樣,即使我們剛剛關閉了中斷,當我們在執行用戶代碼時中斷是打開的。
- 接下來的幾行填入了trapframe的內容
- kernel_pagetable
- 當前用戶進程的kernel stack
- usertrap地址——trampoline可以跳轉到這里
- tp寄存器中的hartid
- 要設置SSTATUS寄存器,這是一個控制寄存器。這個寄存器的SPP bit位控制了sret指令的行為,該bit為0表示下次執行sret的時候,我們想要返回user mode而不是supervisor mode。這個寄存器的SPIE bit位控制了,在執行完sret之后,是否打開中斷。因為我們在返回到用戶空間之后,我們的確希望打開中斷,所以這里將SPIE bit位設置為1。修改完這些bit位之后,我們會把新的值寫回到SSTATUS寄存器。我們在trampoline代碼的最后執行了sret指令。這條指令會將程序計數器設置成SEPC寄存器的值,所以現在我們將SEPC寄存器的值設置成之前保存的用戶程序計數器的值。在不久之前,我們在usertrap函數中將用戶程序計數器保存在trapframe中的epc字段。
- 根據current user process 的頁表,設置SATP寄存器,準備切換到user 頁表。
- 計算好我們要jmp到的地址(trampoline中的userret函數 —— 這個函數包含了所有能將我們帶回到用戶空間的指令。)
- 最后,就是執行userret函數(這個函數在trampoline中)
6. userrret()
這個函數包含了所有能將我們帶回到用戶空間的指令。
.globl userret userret:# userret(TRAPFRAME, pagetable)# switch from kernel to user.# usertrapret() calls here.# a0: TRAPFRAME, in user page table.# a1: user page table, for satp.# switch to the user page table.csrw satp, a1sfence.vma zero, zero# put the saved user a0 in sscratch, so we# can swap it with our a0 (TRAPFRAME) in the last step.ld t0, 112(a0)csrw sscratch, t0# restore all but a0 from TRAPFRAMEld ra, 40(a0)ld sp, 48(a0)ld gp, 56(a0)ld tp, 64(a0)ld t0, 72(a0)ld t1, 80(a0)ld t2, 88(a0)ld s0, 96(a0)ld s1, 104(a0)ld a1, 120(a0)ld a2, 128(a0)ld a3, 136(a0)ld a4, 144(a0)ld a5, 152(a0)ld a6, 160(a0)ld a7, 168(a0)ld s2, 176(a0)ld s3, 184(a0)ld s4, 192(a0)ld s5, 200(a0)ld s6, 208(a0)ld s7, 216(a0)ld s8, 224(a0)ld s9, 232(a0)ld s10, 240(a0)ld s11, 248(a0)ld t3, 256(a0)ld t4, 264(a0)ld t5, 272(a0)ld t6, 280(a0)# restore user a0, and save TRAPFRAME in sscratchcsrrw a0, sscratch, a0# return to user mode and user pc.# usertrapret() set up sstatus and sepc.sret-
首先是切換pagetable(從kernel pa tb到user pg tb)
-
然后是將之前保存在trapframe中的registers還原。user page table也映射了trampoline page,所以程序還能繼續執行而不是崩潰。 (在這里a0是trapframe的地址)
-
解釋 sfence.vma zero, zero是clear page table 的TLB
-
重新打印所有寄存器(和調用前一致) (除了a0 —— a0 被write的返回值覆蓋了)
-
打印pc可以看出來,現在已經回到用戶空間了 —— 以為pc很小,明顯是虛擬地址
最后總結一下,系統調用被刻意設計的看起來像是函數調用,但是背后的user/kernel轉換比函數調用要復雜的多。之所以這么復雜,很大一部分原因是要保持user/kernel之間的隔離性,內核不能信任來自用戶空間的任何內容。
Trampoline page之所以叫trampoline page,是因為你某種程度在它上面“彈跳”了一下,然后從用戶空間走到了內核空間。從內核空間彈了一下,又走出來?!?strong>trampoline代碼在用戶空間和內核空間都映射到了同一個地址。這樣不會讓程序在user和kernel pagetable切換的時候崩潰。
總結
以上是生活随笔為你收集整理的6.S081 附加Lab1 用户执行系统调用的过程(Trap)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Cesium 鼠标操作习惯设置
- 下一篇: 计算机毕业设计SpringBoot选题推