Lec 6,7 - Lab 4 trap

trap執(zhí)行流程

  1. write通過(guò)執(zhí)行ECALL指令來(lái)執(zhí)行系統(tǒng)調(diào)用。ECALL指令會(huì)切換到具有supervisor mode的內(nèi)核中蔓腐。
  2. 內(nèi)核中執(zhí)行的第一個(gè)指令是一個(gè)由匯編語(yǔ)言寫(xiě)的函數(shù),叫做uservec刑巧。
  3. 在這個(gè)匯編函數(shù)中顽照,代碼執(zhí)行跳轉(zhuǎn)到了由C語(yǔ)言實(shí)現(xiàn)的函數(shù)usertrap中狼荞,這個(gè)函數(shù)在trap.c中辽装。
  4. 在usertrap這個(gè)C函數(shù)中,我們執(zhí)行了一個(gè)叫做syscall的函數(shù)相味。
  5. 這個(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。
  6. sys_write會(huì)將要顯示數(shù)據(jù)輸出到console上一死,當(dāng)它完成了之后肛度,它會(huì)返回給syscall函數(shù),再返回給usertrap函數(shù)
  7. usertrap()會(huì)調(diào)用一個(gè)函數(shù)叫做usertrapret,它也位于trap.c中投慈,這個(gè)函數(shù)完成了部分方便在C代碼中實(shí)現(xiàn)的返回到用戶空間的工作
  8. 匯編函數(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();
  1. 在內(nèi)核中執(zhí)行任何操作之前,usertrap中先將STVEC指向了kernelvec變量碘饼,這是內(nèi)核空間trap處理代碼的位置熙兔,而不是用戶空間trap處理代碼的位置。
  2. 我們通過(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寄存器
  3. 我們要把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)

  1. 隔離性。虛擬內(nèi)存使得操作系統(tǒng)可以為每個(gè)應(yīng)用程序提供屬于它們自己的地址空間肛鹏。所以一個(gè)應(yīng)用程序不可能有意或者無(wú)意的修改另一個(gè)應(yīng)用程序的內(nèi)存數(shù)據(jù)逸邦。
  2. 一層抽象.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是必須的?

  1. 出錯(cuò)的虛擬內(nèi)存地址,或者是觸發(fā)page fault的源
  2. 出錯(cuò)的原因(存在SCAUSE寄存器中)
  • 13表示是因?yàn)閘oad引起的page fault;
  • 15表示是因?yàn)閟tore引起的page fault裹芝;
  • 12表示是因?yàn)橹噶顖?zhí)行引起的page fault


    image.png
  1. 引起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

image.png

在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ě)


image.png

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)志位為只讀.

image.png

要寫(xiě)的時(shí)候,復(fù)制出來(lái)一份,2邊都改成讀寫(xiě).

image.png

使用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è)功能.


1699718206599.png

生成出來(lái)的文件大概長(zhǎng)這樣:


1699718241727.png

隨后就是讀取文件, 加載進(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了.

1699718737227.png

測(cè)試效果

1699718486261.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抗楔,隨后出現(xiàn)的幾起案子拦坠,更是在濱河造成了極大的恐慌连躏,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贞滨,死亡現(xiàn)場(chǎng)離奇詭異拍棕,居然都是意外死亡勺良,警方通過(guò)查閱死者的電腦和手機(jī)绰播,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)尚困,“玉大人,你說(shuō)我怎么就攤上這事谬泌÷咔” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵邦马,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我忱嘹,道長(zhǎng)耕渴,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任础米,我火速辦了婚禮添诉,結(jié)果婚禮上屁桑,老公的妹妹穿的比我還像新娘蘑斧。我一直安慰自己须眷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布花颗。 她就那樣靜靜地躺著,像睡著了一般扩劝。 火紅的嫁衣襯著肌膚如雪职辅。 梳的紋絲不亂的頭發(fā)上聂示,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天催什,我揣著相機(jī)與錄音宰睡,去河邊找鬼蒲凶。 笑死拆内,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的灵巧。 我是一名探鬼主播抹沪,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼融欧!你這毒婦竟也來(lái)了敏弃?” 一聲冷哼從身側(cè)響起噪馏,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤欠肾,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后刺桃,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡廓啊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年谴轮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吹埠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疮装。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粘都,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出樊展,到底是詐尸還是另有隱情堆生,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布涝婉,位于F島的核電站蔗怠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏寞射。R本人自食惡果不足惜桥温,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望策治。 院中可真熱鬧通惫,春花似錦、人聲如沸履腋。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)延旧。三九已至,卻和暖如春芦瘾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背近弟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工祷愉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人二鳄。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓泥从,卻偏偏與公主長(zhǎng)得像沪摄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杨拐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 操作系統(tǒng)的隔離性 如果沒(méi)有操作系統(tǒng)哄陶,應(yīng)用程序會(huì)直接與硬件交互。比如蜒谤,應(yīng)用程序可以直接看到CPU的多個(gè)核至扰,看到磁盤,...
    西部小籠包閱讀 217評(píng)論 0 0
  • 為什么需要虛擬內(nèi)存 shell進(jìn)程由于bug,引發(fā)了隨機(jī)寫(xiě)入某些內(nèi)存地址阶祭,這些內(nèi)存地址可能是其他進(jìn)程使用的直秆,而可能...
    西部小籠包閱讀 208評(píng)論 0 0
  • lab4 是實(shí)現(xiàn)多處理器支持以及搶占式任務(wù)調(diào)度,exercize代碼見(jiàn) 這里瑰剃。 1 多處理器啟動(dòng)流程 1.1 多處...
    __七把刀__閱讀 4,614評(píng)論 0 4
  • 環(huán)境 ubuntu 20.04 64 系統(tǒng) 正文 在本次實(shí)驗(yàn)將在多個(gè)同時(shí)運(yùn)行的用戶程序中實(shí)現(xiàn)搶占式多線程(Pree...
    扶桑與克里斯閱讀 414評(píng)論 0 0
  • 此篇是學(xué)習(xí)ucore操作系統(tǒng)lab1的實(shí)驗(yàn)報(bào)告筝野,參考了很多資料和文章鹃两,也學(xué)到了不少舀凛。 先看要求: 為了實(shí)現(xiàn)lab1...
    一斗水閱讀 373評(píng)論 0 0