trap執(zhí)行流程
- write通過(guò)執(zhí)行ECALL指令來(lái)執(zhí)行系統(tǒng)調(diào)用。ECALL指令會(huì)切換到具有supervisor mode的內(nèi)核中蔓腐。
- 內(nèi)核中執(zhí)行的第一個(gè)指令是一個(gè)由匯編語(yǔ)言寫(xiě)的函數(shù),叫做uservec刑巧。
- 在這個(gè)匯編函數(shù)中顽照,代碼執(zhí)行跳轉(zhuǎn)到了由C語(yǔ)言實(shí)現(xiàn)的函數(shù)usertrap中狼荞,這個(gè)函數(shù)在trap.c中辽装。
- 在usertrap這個(gè)C函數(shù)中,我們執(zhí)行了一個(gè)叫做syscall的函數(shù)相味。
- 這個(gè)函數(shù)會(huì)在一個(gè)表單中拾积,根據(jù)傳入的代表系統(tǒng)調(diào)用的數(shù)字進(jìn)行查找,并在內(nèi)核中執(zhí)行具體實(shí)現(xiàn)了系統(tǒng)調(diào)用功能的函數(shù)丰涉。對(duì)于我們來(lái)說(shuō)拓巧,這個(gè)函數(shù)就是sys_write。
- sys_write會(huì)將要顯示數(shù)據(jù)輸出到console上一死,當(dāng)它完成了之后肛度,它會(huì)返回給syscall函數(shù),再返回給usertrap函數(shù)
- usertrap()會(huì)調(diào)用一個(gè)函數(shù)叫做usertrapret,它也位于trap.c中投慈,這個(gè)函數(shù)完成了部分方便在C代碼中實(shí)現(xiàn)的返回到用戶空間的工作
- 匯編函數(shù)中會(huì)調(diào)用機(jī)器指令返回到用戶空間承耿,并且恢復(fù)ECALL之后的用戶程序的執(zhí)行
ecall 指令
我們是通過(guò)ecall走到trampoline page的冠骄,而ecall實(shí)際上只會(huì)改變?nèi)虑椋?br>
第一,ecall將代碼從user mode改到supervisor mode加袋。
第二凛辣,ecall將程序計(jì)數(shù)器的值保存在了SEPC寄存器。我們可以通過(guò)打印程序計(jì)數(shù)器看到這里的效果职烧,
第三扁誓,ecall會(huì)跳轉(zhuǎn)到STVEC寄存器指向的指令。
接下來(lái):
- 我們需要保存32個(gè)用戶寄存器的內(nèi)容蚀之,這樣當(dāng)我們想要恢復(fù)用戶代碼執(zhí)行時(shí)蝗敢,我們才能恢復(fù)這些寄存器的內(nèi)容。
- 因?yàn)楝F(xiàn)在我們還在user page table恬总,我們需要切換到kernel page table前普。
- 我們需要?jiǎng)?chuàng)建或者找到一個(gè)kernel stack,并將Stack Pointer寄存器的內(nèi)容指向那個(gè)kernel stack壹堰。這樣才能給C代碼提供棧拭卿。
- 我們還需要跳轉(zhuǎn)到內(nèi)核中C代碼的某些合理的位置。
uservec函數(shù)
為了能執(zhí)行更新page table的指令贱纠,我們需要一些空閑的寄存器峻厚,這樣我們才能先將page table的地址存在這些寄存器中,然后再執(zhí)行修改SATP寄存器的指令谆焊。
對(duì)于保存用戶寄存器惠桃,XV6在RISC-V上的實(shí)現(xiàn)包括了兩個(gè)部分:
第一個(gè)部分是,XV6在每個(gè)user page table映射了trapframe page辖试,這樣每個(gè)進(jìn)程都有自己的trapframe page辜王。
trapframe page第一個(gè)數(shù)據(jù)保存了kernel page table地址,這將會(huì)是trap處理代碼將要加載到SATP寄存器的數(shù)值罐孝。
第二部分是把trapframe page的地址加載到a0呐馆,也就是0x3fffffe000, 這是個(gè)常量莲兢。原來(lái)用戶空間的A0會(huì)被寫(xiě)到sscratch這個(gè)寄存器中汹来。所以,下面SD是存儲(chǔ)質(zhì)量改艇,讓每個(gè)寄存器被保存在了偏移量+a0的位置收班。
csrw sscratch, a0
li a0, TRAPFRAME
sd ra, 40(a0)
sd sp, 48(a0)
sd gp, 56(a0)
一直到
csrr t0, sscratch
sd t0, 112(a0)
把用戶態(tài)原來(lái)A0寄存器的值,存到p->trapframe->a0中
ld sp, 8(a0)
這條指令正在將a0指向的內(nèi)存地址往后數(shù)的第8個(gè)字節(jié)開(kāi)始的數(shù)據(jù)加載到Stack Pointer寄存器谒兄。第8個(gè)字節(jié)開(kāi)始的數(shù)據(jù)是內(nèi)核的Stack Pointer(kernel_sp)娱俺。
trapframe中的kernel_sp是由kernel在進(jìn)入用戶空間之前就設(shè)置好的妒蔚,它的值是這個(gè)進(jìn)程的kernel stack
ld tp, 32(a0)
XV6會(huì)將CPU核的編號(hào)也就是hartid保存在tp寄存器, 這個(gè)寄存器表明當(dāng)前運(yùn)行在多核處理器的哪個(gè)核上
# load the address of usertrap(), from p->trapframe->kernel_trap
ld t0, 16(a0)
# fetch the kernel page table address, from p->trapframe->kernel_satp.
ld t1, 0(a0)
# install the kernel page table.
csrw satp, t1
上面3條指令執(zhí)行完成之后挟炬,當(dāng)前程序會(huì)從user page table切換到kernel page table。
最后一條指令是jr t0瘦穆。執(zhí)行了這條指令,我們就要從trampoline跳到內(nèi)核的C代碼中赊豌。這條指令的作用是跳轉(zhuǎn)到t0指向的函數(shù)中扛或。
usertrap函數(shù)
// 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->trapframe->epc = r_sepc();
- 在內(nèi)核中執(zhí)行任何操作之前,usertrap中先將STVEC指向了kernelvec變量碘饼,這是內(nèi)核空間trap處理代碼的位置熙兔,而不是用戶空間trap處理代碼的位置。
- 我們通過(guò)調(diào)用myproc函數(shù)來(lái)得到當(dāng)前運(yùn)行的是什么進(jìn)程 艾恼。myproc函數(shù)實(shí)際上會(huì)查找一個(gè)根據(jù)當(dāng)前CPU核的編號(hào)索引的數(shù)組住涉,CPU核的編號(hào)是hartid,如果你還記得钠绍,我們之前在uservec函數(shù)中將它存在了tp寄存器
- 我們要把SEPC寄存器中的用戶程序計(jì)數(shù)器保存到進(jìn)程的數(shù)據(jù)結(jié)構(gòu)中
接下來(lái)我們需要找出我們現(xiàn)在會(huì)在usertrap函數(shù)的原因舆声。根據(jù)觸發(fā)trap的原因,RISC-V的SCAUSE寄存器會(huì)有不同的數(shù)字柳爽。數(shù)字8表明媳握,我們現(xiàn)在在trap代碼中是因?yàn)橄到y(tǒng)調(diào)用.
存儲(chǔ)在SEPC寄存器中的程序計(jì)數(shù)器,是用戶程序中觸發(fā)trap的指令的地址磷脯。但是當(dāng)我們恢復(fù)用戶程序時(shí)蛾找,我們希望在下一條指令恢復(fù),也就是ecall之后的一條指令赵誓。所以對(duì)于系統(tǒng)調(diào)用打毛,我們對(duì)于保存的用戶程序計(jì)數(shù)器加4,這樣我們會(huì)在ecall的下一條指令恢復(fù)俩功。
最后會(huì)進(jìn)入usertrapret
usertrapret函數(shù)
它首先關(guān)閉了中斷幻枉。我們之前在系統(tǒng)調(diào)用的過(guò)程中是打開(kāi)了中斷的,這里關(guān)閉中斷是因?yàn)槲覀儗⒁耂TVEC寄存器指向用戶空間的trap代碼诡蜓,而之前在內(nèi)核中的時(shí)候熬甫,我們指向的是內(nèi)核空間的trap代碼。
設(shè)置了STVEC寄存器指向user trampoline代碼万牺。 之前我們指向內(nèi)核的代碼,現(xiàn)在需要指回USER TRAP代碼洽腺。
intr_off();
// send syscalls, interrupts, and exceptions to uservec in trampoline.S
uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline);
w_stvec(trampoline_uservec);
下面把kernal要用到的一些變量寫(xiě)進(jìn)trapframe脚粟。因?yàn)楫?dāng)用戶態(tài)再次執(zhí)行ECALL或中斷時(shí),還需要用這里的值去恢復(fù)蘸朋。
p->trapframe->kernel_satp = r_satp(); // kernel page table
p->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stack
p->trapframe->kernel_trap = (uint64)usertrap;
p->trapframe->kernel_hartid = r_tp(); // hartid for cpuid()
接下來(lái)我們要設(shè)置SSTATUS寄存器核无,這是一個(gè)控制寄存器。這個(gè)寄存器的SPP bit位控制了sret指令的行為藕坯,該bit為0表示下次執(zhí)行sret的時(shí)候团南,我們想要返回user mode而不是supervisor mode噪沙。這個(gè)寄存器的SPIE bit位控制了,在執(zhí)行完sret之后吐根,是否打開(kāi)中斷正歼。因?yàn)槲覀冊(cè)诜祷氐接脩艨臻g之后,我們的確希望打開(kāi)中斷拷橘,所以這里將SPIE bit位設(shè)置為1局义。修改完這些bit位之后,我們會(huì)把新的值寫(xiě)回到SSTATUS寄存器冗疮。
unsigned long x = r_sstatus();
x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode
x |= SSTATUS_SPIE; // enable interrupts in user mode
w_sstatus(x);
我們?cè)趖rampoline代碼的最后執(zhí)行了sret指令萄唇。這條指令會(huì)將程序計(jì)數(shù)器設(shè)置成SEPC寄存器的值,所以現(xiàn)在我們將SEPC寄存器的值設(shè)置成之前保存的用戶程序計(jì)數(shù)器的值术幔。
// set S Exception Program Counter to the saved user pc.
w_sepc(p->trapframe->epc);
接下來(lái)另萤,我們根據(jù)user page table地址生成相應(yīng)的SATP值,這樣我們?cè)诜祷氐接脩艨臻g的時(shí)候才能完成page table的切換诅挑。把它當(dāng)作函數(shù)的第一參數(shù)傳給一會(huì)的匯編四敞。因?yàn)橹挥衪rampoline中代碼是同時(shí)在用戶和內(nèi)核空間中映射。但是我們現(xiàn)在還沒(méi)有在trampoline代碼中揍障,我們現(xiàn)在還在一個(gè)普通的C函數(shù)中目养,所以這里我們將page table指針準(zhǔn)備好。這個(gè)參數(shù)會(huì)存在a0寄存器中
// tell trampoline.S the user page table to switch to.
uint64 satp = MAKE_SATP(p->pagetable);
倒數(shù)第二行的作用是計(jì)算出我們將要跳轉(zhuǎn)到匯編代碼的地址毒嫡。我們期望跳轉(zhuǎn)的地址是tampoline中的userret函數(shù)癌蚁,這個(gè)函數(shù)包含了所有能將我們帶回到用戶空間的指令。
倒數(shù)第一行兜畸,將trampoline_userret指針作為一個(gè)函數(shù)指針努释,執(zhí)行相應(yīng)的函數(shù)(也就是userret函數(shù))并傳入?yún)?shù),參數(shù)存儲(chǔ)在a0寄存器中, 也就是page table指針咬摇。
uint64 trampoline_userret = TRAMPOLINE + (userret - trampoline);
((void (*)(uint64))trampoline_userret)(satp);
userret 函數(shù)
第一件事就是把之前a0存的pagetable指針?lè)诺絪atp寄存器中,隨后把trapframe的地址放進(jìn)a0;
csrw 用于將一個(gè)值寫(xiě)入到控制和狀態(tài)寄存器(CSR)
li 用于將一個(gè)立即數(shù)(即直接編碼在指令中的數(shù)值)加載到一個(gè)寄存器中
sfence.vma zero, zero
csrw satp, a0
sfence.vma zero, zero
li a0, TRAPFRAME
下面就是從TRAPFRAME開(kāi)始恢復(fù)之前用戶態(tài)的寄存器的值了.
最后我們把TRAPFRAME里存的函數(shù)返回值a0放進(jìn)a0寄存器
# restore user a0
ld a0, 112(a0)
# return to user mode and user pc.
# usertrapret() set up sstatus and sepc.
sret
sret是我們?cè)趉ernel中的最后一條指令伐蒂,當(dāng)我執(zhí)行完這條指令:
程序會(huì)切換回user mode
SEPC寄存器的數(shù)值會(huì)被拷貝到PC寄存器(程序計(jì)數(shù)器)
重新打開(kāi)中斷
利用虛擬內(nèi)存可以做的有趣的事
虛擬內(nèi)存有兩個(gè)主要的優(yōu)點(diǎn)
- 隔離性。虛擬內(nèi)存使得操作系統(tǒng)可以為每個(gè)應(yīng)用程序提供屬于它們自己的地址空間肛鹏。所以一個(gè)應(yīng)用程序不可能有意或者無(wú)意的修改另一個(gè)應(yīng)用程序的內(nèi)存數(shù)據(jù)逸邦。
- 一層抽象.trampoline page,它使得內(nèi)核可以將一個(gè)物理內(nèi)存page映射到多個(gè)用戶地址空間中在扰。guard page缕减,它同時(shí)在內(nèi)核空間和用戶空間用來(lái)保護(hù)Stack。
關(guān)鍵思想:在page fault時(shí)改變頁(yè)表
通過(guò)page fault芒珠,內(nèi)核可以更新page table桥狡,這是一個(gè)非常強(qiáng)大的功能.
什么樣的信息對(duì)于page fault是必須的?
- 出錯(cuò)的虛擬內(nèi)存地址,或者是觸發(fā)page fault的源
- 出錯(cuò)的原因(存在SCAUSE寄存器中)
- 13表示是因?yàn)閘oad引起的page fault;
- 15表示是因?yàn)閟tore引起的page fault裹芝;
-
12表示是因?yàn)橹噶顖?zhí)行引起的page fault
- 引起page fault時(shí)的程序計(jì)數(shù)器值(tf->epc)
這表明了page fault在用戶空間發(fā)生的位置.因?yàn)樵趐age fault handler中我們或許想要修復(fù)page table部逮,并重新執(zhí)行對(duì)應(yīng)的指令。理想情況下嫂易,修復(fù)完page table之后兄朋,指令就可以無(wú)錯(cuò)誤的運(yùn)行了。
Lazy page allocation
在XV6中炬搭,sbrk的實(shí)現(xiàn)默認(rèn)是eager allocation蜈漓。這表示了,一旦調(diào)用了sbrk宫盔,內(nèi)核會(huì)立即分配應(yīng)用程序所需要的物理內(nèi)存融虽。設(shè)想自己寫(xiě)了一個(gè)應(yīng)用程序,讀取了一些輸入然后通過(guò)一個(gè)矩陣進(jìn)行一些運(yùn)算灼芭。你需要為最壞的情況做準(zhǔn)備有额,比如說(shuō)為最大可能的矩陣分配內(nèi)存,但是應(yīng)用程序可能永遠(yuǎn)也用不上這些內(nèi)存.使用虛擬內(nèi)存和page fault handler彼绷,我們完全可以用某種更聰明的方法來(lái)解決這里的問(wèn)題巍佑,這里就是利用lazy allocation.
sbrk系統(tǒng)調(diào)基本上不做任何事情,唯一需要做的事情就是提升p->sz.之后在某個(gè)時(shí)間點(diǎn)寄悯,應(yīng)用程序使用到了新申請(qǐng)的那部分內(nèi)存萤衰,這時(shí)會(huì)觸發(fā)page fault,因?yàn)槲覀冞€沒(méi)有將新的內(nèi)存映射到page table猜旬。所以脆栋,如果我們解析一個(gè)大于舊的p->sz,但是又小于新的p->sz(注洒擦,也就是舊的p->sz + n)的虛擬地址椿争,我們希望內(nèi)核能夠分配一個(gè)內(nèi)存page,并且重新執(zhí)行指令熟嫩。
Zero Fill On Demand
當(dāng)你查看一個(gè)用戶程序的地址空間時(shí)秦踪,存在text區(qū)域,data區(qū)域掸茅,同時(shí)還有一個(gè)BSS區(qū)域(注椅邓,BSS區(qū)域包含了未被初始化或者初始化為0的全局或者靜態(tài)變量)。當(dāng)編譯器在生成二進(jìn)制文件時(shí)昧狮,編譯器會(huì)填入這三個(gè)區(qū)域景馁。text區(qū)域是程序的指令,data區(qū)域存放的是初始化了的全局變量陵且,BSS包含了未被初始化或者初始化為0的全局變量裁僧。
我只需要分配一個(gè)page,這個(gè)page的內(nèi)容全是0慕购。然后將所有虛擬地址空間的全0的page都map到這一個(gè)物理page上聊疲。這樣至少在程序啟動(dòng)的時(shí)候能節(jié)省大量的物理內(nèi)存分配。
在寫(xiě)入時(shí)復(fù)制頁(yè)面沪悲,并在應(yīng)用地址空間中映射它為讀/寫(xiě)
Copy On Write Fork
xv6 fork從父進(jìn)程復(fù)制所有頁(yè)面(參見(jiàn)fork());但fork經(jīng)常緊接著執(zhí)行exec, 就會(huì)直接丟棄那些復(fù)制了的頁(yè)面.
解決方案是在父子進(jìn)程之間共享地址空間,修改標(biāo)志位為只讀.
要寫(xiě)的時(shí)候,復(fù)制出來(lái)一份,2邊都改成讀寫(xiě).
使用PTEs中額外可用的系統(tǒng)位(RSW)
當(dāng)內(nèi)核在管理這些page table時(shí)获洲,對(duì)于copy-on-write相關(guān)的page,內(nèi)核可以設(shè)置相應(yīng)的bit位殿如,這樣當(dāng)發(fā)生page fault時(shí)贡珊,我們可以發(fā)現(xiàn)如果copy-on-write bit位設(shè)置了,我們就可以執(zhí)行相應(yīng)的操作了涉馁。
需要對(duì)物理頁(yè)面進(jìn)行引用計(jì)數(shù)
父進(jìn)程退出時(shí)我們需要更加的小心门岔,因?yàn)槲覀円袛嗍欠衲芰⒓瘁尫畔鄳?yīng)的物理page。如果有子進(jìn)程還在使用這些物理page,就不能釋放.
困難重重: https://lwn.net/Articles/849638/
Demand Paging
程序的二進(jìn)制文件可能非常的巨大烤送,將它全部從磁盤加載到內(nèi)存中將會(huì)是一個(gè)代價(jià)很高的操作寒随。又或者data區(qū)域的大小遠(yuǎn)大于常見(jiàn)的場(chǎng)景所需要的大小,我們并不一定需要將整個(gè)二進(jìn)制都加載到內(nèi)存中帮坚。
按需從文件中加載頁(yè)面
- 分配 page table entries妻往,但將它們標(biāo)記為on-demand
- 在錯(cuò)誤時(shí),從文件中讀入page并更新pagetable
- 需要保留一些元信息试和,說(shuō)明頁(yè)面在磁盤上的位置
- 這些信息通常在稱為virtual memory area(VMA)的結(jié)構(gòu)中
- 如果文件大過(guò)物理內(nèi)存, 把最遠(yuǎn)使用過(guò)的page置換進(jìn)磁盤. the A(cess) bit 在PTE 幫助 kernel 實(shí)現(xiàn) LRU.
Memory Mapped Files
將完整或者部分文件加載到內(nèi)存中讯泣,這樣就可以通過(guò)內(nèi)存地址相關(guān)的load或者store指令來(lái)操縱文件.
為了支持這個(gè)功能,一個(gè)現(xiàn)代的操作系統(tǒng)會(huì)提供一個(gè)叫做mmap的系統(tǒng)調(diào)用阅悍。
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
下面開(kāi)始講解這次的LAB
Alarm
test 0的核心就是修改EPC.
EPC(Exception Program Counter)寄存器扮演著重要的角色好渠,特別是在異常處理和中斷處理中。EPC存儲(chǔ)了發(fā)生異掣然或中斷時(shí)程序的地址晦墙,這允許處理器在處理完異常或中斷后返回到正確的位置繼續(xù)執(zhí)行程序肴茄。
p->trapframe->epc = (uint64)p->alarmhandler;
test1/2/3
what registers do you need to save and restore to resume the interrupted code correctly?
因?yàn)槲覀円俅位氐阶铋_(kāi)始用戶態(tài)執(zhí)行的地方.中間我們跳到了用戶態(tài)alarmhandler的地方.這里面的代碼執(zhí)行,會(huì)修改掉用戶態(tài)的寄存器的值.所以我們需要把之前用戶態(tài)的寄存器都要存下來(lái).
*p->alarmtrapframe = *p->trapframe;
Prevent re-entrant calls to the handler
增加一個(gè)isalarming的狀態(tài),如果是true,就不做跳轉(zhuǎn)
if (p->tickspassed >= p->alarminterval && !p->isalarming) {
....
}
Make sure to restore a0. sigreturn is a system call, and its return value is stored in a0
把之前在用戶態(tài)保存下來(lái)的實(shí)際返回值,當(dāng)作sys_sigreturn的返回值,就可以達(dá)到這個(gè)效果.
uint64
sys_sigreturn(void)
{
struct proc *p = myproc();
p->isalarming = 0;
*p->trapframe = *p->alarmtrapframe;
return p->trapframe->a0;
}
Print the names of the functions and line numbers in backtrace() instead of numerical addresses
核心思路: 首先修改make文件, 結(jié)合生成的asm文件提取所有命令的內(nèi)存地址,然后調(diào)用addr2line 獲得方法名 和 行號(hào). 寫(xiě)進(jìn)一個(gè)文件,并且讓文件系統(tǒng)映射這個(gè)文件進(jìn)xv6. 隨后在內(nèi)核啟動(dòng)的時(shí)候, 讀取文件內(nèi)容, 把地址和對(duì)應(yīng)的方法名和行號(hào),存進(jìn)2個(gè)數(shù)組.
當(dāng)調(diào)用backtrace() 函數(shù)的時(shí)候,可以用地址,在數(shù)組里做2分查找,找到對(duì)應(yīng)的方法名和信息,就可以實(shí)現(xiàn)這個(gè)功能.
生成出來(lái)的文件大概長(zhǎng)這樣:
隨后就是讀取文件, 加載進(jìn)數(shù)組. 我新建了一個(gè)debugtbl.c
這里用了一個(gè)緩存讀文件的技巧,來(lái)減少readi的調(diào)用次數(shù). 我是1次讀了一個(gè)PGSIZE. 如果cache里還有行,就直接從cache里讀出下一行.
2分查找的代碼,是找到最大的小于等于當(dāng)前輸入地址的那個(gè)索引,隨后去debugtbl_info
讀取方法名和行號(hào).
#include "types.h"
#include "riscv.h"
#include "defs.h"
#include "param.h"
#include "stat.h"
#include "spinlock.h"
#include "proc.h"
#include "sleeplock.h"
#include "fs.h"
#include "buf.h"
#include "file.h"
#define MAX_LINE_LENGTH 96
#define MAX_ROW_LENGTH 4096
uint debugtbl_addr[MAX_ROW_LENGTH];
char debugtbl_info[MAX_ROW_LENGTH][MAX_LINE_LENGTH];
int debugtbl_row = 0;
char cache[PGSIZE];
int cache_idx = 0;
int cache_cnt = 0;
int readline(struct inode *ip, uint *off, char *buf) {
int i = 0, n;
while (i < MAX_LINE_LENGTH) {
while (i < MAX_LINE_LENGTH && cache_cnt > cache_idx) {
buf[i] = cache[cache_idx++];
if (buf[i] == '\n') {
buf[i] = 0;
return i;
}
i++;
}
if (i == MAX_LINE_LENGTH) {
panic("line char overflow");
return -1;
}
n = readi(ip, 0, (uint64)cache, *off, PGSIZE);
if (n <= 0) break; // Error or end of file
*off += n;
cache_cnt = n;
cache_idx = 0;
}
return i; // Return the length of the line
}
int readfile(struct inode *ip) {
uint off = 0; // Offset in the file
int n;
char buf[MAX_LINE_LENGTH];
while ((n = readline(ip, &off, buf)) > 0) {
uint addr = 0;
if (n < 8) {
panic("debugtbl format error");
}
for(int i = 0; i < 8; i++) {
char c = buf[i];
if (c >= '0' && c <= '9') c = c - '0';
else if (c >= 'a' && c <= 'f') c = c - 'a' + 10;
else panic("invalid address content");
addr = (addr << 4) | c;
}
debugtbl_addr[debugtbl_row] = addr;
memmove(debugtbl_info[debugtbl_row++], buf + 9, n);
}
return n < 0 ? -1 : 0;
}
int initdebugtbl(){
struct inode *ip;
begin_op();
if((ip = namei("/debugtbl")) == 0){
end_op();
return -1;
}
ilock(ip);
if (readfile(ip) < 0)
goto bad;
iunlockput(ip);
end_op();
ip = 0;
return 0;
bad:
if(ip){
iunlockput(ip);
end_op();
}
return -1;
}
int get_debuginfo(uint64 address) {
uint left = 0;
uint right = debugtbl_row - 1;
while(left <= right){
int mid = (left + right) >> 1;
if(address < debugtbl_addr[mid]){
right = mid - 1;
}else{
left = mid + 1;
}
}
return left - 1;
}
void backtrace()
{
int idx;
printf("backtrace:\n");
uint64 fp = r_fp(); // get the frame pointer value
for (uint64 i = fp; PGROUNDDOWN(fp) == PGROUNDDOWN(i); i = *(uint64*)(i - 16)) {
uint64 addr = *(uint64*)(i - 8);
if ((idx = get_debuginfo(addr)) < 0)
printf("%p\n", addr);
else
printf("%p %s\n", addr, debugtbl_info[idx]);
}
}
這里關(guān)于initdebugtbl
什么時(shí)候去調(diào)用,我做了一些DEBUG. 一開(kāi)始我是打算放在main.c
中各種初始化時(shí).但是發(fā)現(xiàn)始終不能work,根本原因時(shí)那時(shí)還沒(méi)有進(jìn)程的結(jié)構(gòu)體維護(hù)在cpu, 第一個(gè)進(jìn)程是在初始化完后,在scheduler()
方法里 要去執(zhí)行userinit
被創(chuàng)建出來(lái). 這里會(huì)切到用戶態(tài),然后調(diào)用exec的系統(tǒng)調(diào)用,去執(zhí)行userinit命令.
那么我們可以在內(nèi)核態(tài)的exec處,判斷當(dāng)前proc->pid是不是為1 (第一個(gè)進(jìn)程), 是的話,就initdebugtbl
; 來(lái)在內(nèi)核態(tài)完成初始化debug table的工作.
隨后exec userinit 后,創(chuàng)建出來(lái)的shell進(jìn)程, pid為2了.