SICP_Chapter1 Building Abstractions with Functions

1.2 Elements of Programming 1.2.6 The Non-Pure Print Function >>> print(print(1), print(2)) 1 2 None None 注意 print(1)和 print(2)的返回值是 None。 1.4 Designning Functions 1.4.1 Documentation docstring。在函数名后在``````中描述函数信息。第一行用来描述函数,接着空一行,接着可以描述参数等。 help(pressure)可以查看函数帮助信息。 >>> def pressure(v, t, n): """Compute the pressure in pascals of an ideal gas. Applies the ideal gas law: http://en.wikipedia.org/wiki/Ideal_gas_law v -- volume of gas, in cubic meters t -- absolute temperature in degrees kelvin n -- particles of gas """ k = 1....

2024-01-22 · 2 min

xv6_chapter3 Page tables

3.1 Paging hardware xv6 runs on Sv39 RISC-V, 使用低39位来表示虚拟内存, 高25位没有使用。 39位中27位用作index来寻找PTE(Page table entry), 低12位表示在某个页表中的偏移地址, 正好对应4KB。每个PTE包含44bits的PPN(physical page number)和一些控制位。 实际的RISC-V CPU翻译虚拟地址到物理地址使用了三层。每层存储512个PTE,分别使用9个bit来索引。上一层的一个PTE对应下一层包含512个PTE的Page table地址。所以总共有512*512*512=2^27 PTE。每个pte占8bytes,所以需要占用的内存最多是2^30=1G, 因为没有访问到的pte是不会分配pagetable的,所以实际占用的内存会更少。 每个CPU需要把顶层的page directory物理地址加载到 satp 寄存器中, 第一个Page Directory的地址是已知的。 然后通过L2索引到第一个Page directory的PTE,读出PTE的PPN, 即第二个Page directory的起始物理地址。再根据L1索引到第二个Page directory的PTE, 以此类推。 3.2 Kernel address space 上图PHYSTOP为0x88000000, 见memlayout.h QEMU模拟RAM从0x80000000物理地址开始,至多到0x80000000+12810241024=0x88000000,xv6称这个地址为PHYSTOP。 Kernel使用RAM和device registers是直接映射的,虚拟地址和物理地址相等。 不过有一部分kernel虚拟地址不是直接映射的: Trampoline page. 在虚拟地址的最顶部。这边有意思的是物理内存中的trampoline code被映射到了两个地方,一个对应直接映射的虚拟内存中的kernel text,另一个是虚拟地址最顶部地址的一个page size。有关Trampoline page请参考第四章。 Kernel stack pages. 每个进程都有自己的kernel stack。如果访问超过了自己的kernel stack。会有guard page保护,guard page的PTE valid位置为0,导致访问异常。 3.3 Code: creating an address space TLB. 每个进程有自己的页表,切换进程时需要flush TLB, 因为之前VA-PA对应已经不成立了。通过RISC-V指令sfence.vma可以flush TLB。...

2024-01-11 · 1 min

xv6_chapter2 Operating system organization

2.1 Abstracting physical resources 2.2 User mode, supervisor mode, and system calls 2.3 Kernel Organization monolithic kernel vs microkernel xv6 和 Unix-like OS 属于 monolithic kernel. 2.4 Code: xv6 organization kernel 接口都在 def.h 中声明. 2.5 Process overview 进程的地址空间: RISC-V 64 只使用 64 位地址中的 39 位, 所以用户空间和内核空间各占 2^39 字节. xv6 只使用 39 位地址中的 38 位, 所以最大地址为 2^38 - 1 = 0x3fffffffff, MAXVA 在 riscv.h 中定义. 每个进程都有两个栈, 一个是内核栈, 一个是用户栈. 2.6 Code: starting xv6, the first process and system call bootloader 把 xv6 kernel code 加载到内存 0x80000000 地址, 首先执行的代码为entry....

2024-01-04 · 2 min

xv6_lab2 System calls

Trace 实现系统调用int trace(int mask);, 当调用mask中包含的系统调用号,打印出来。 在Makefile中增加trace用户程序的编译 UPROGS=\ ... $U/_trace 在user/user.h中增加函数声明 int trace(int); 在usys.pl中增加user space trace函数的入口。可以看到user space调用的系统调用, 是由这个脚本生成的函数。 以trace为例, 提供了trace函数的入口.global trace, 随后将定义在syscall.h中的SYS_trace编号存入寄存器a7, 通过ecall命令进入内核态。 sub entry { my $name = shift; print ".global $name\n"; print "${name}:\n"; print " li a7, SYS_${name}\n"; print " ecall\n"; print " ret\n"; } entry("trace"); 在syscall.c的syscall函数中通过获取a7寄存器中的编号,找到我们添加的系统调用函数,sys_trace。 函数具体实现如下: // 在proc结构体中增加mask成员 struct proc { //... int mask; } // 将user space传入的mask,传递给当前进程的mask变量 uint64 sys_trace(void) { if(argint(0, &myproc()->mask) < 0) return -1; return 0; } // 随后执行的系统调用number如果 (1 << num == mask), 则打印 syscall(void) { // ....

2024-01-04 · 1 min

xv6_chapter1 Operating system interfaces

XV6 实现的所有系统调用: int fork() //Create a process, return child’s PID. void exit(int status) //Terminate the current process; status reported to wait(). No return. int wait(int *status) //Wait for a child to exit; exit status in *status; returns child PID. int kill(int pid) //Terminate process PID. Returns 0, or -1 for error. int getpid() //Return the current process’s PID. int sleep(int n) //Pause for n clock ticks. int exec(char *file, char *argv[]) //Load a file and execute it with arguments; only returns if error....

2023-12-30 · 3 min

risc-v Lookup

CSRs 参考riscv-privileged-20211203.pdf sstatus SPP: 在进入S mode之前hart处于什么mode。 用户态trap进入S mode,SPP被设置为0,其他情况为1。 如果SPP为0,执行SRET后,返回U mode。如果SPP为1,执行SRET后,返回S mode。随后SPP置0。 SIE: 1开启/0关闭 S mode的所有中断 in S mode。 在U mode时,SIE被忽略,S mode 的中断都是打开的。 SPIE: 当trap进入S mode,SIE会置0禁止S mode所有中断,SIE的旧值会保存到SPIE中。 执行SRET后,SIE被设置为SPIE中之前保存的旧值,SPIE置1。 stvec Supervisor Trap Vector Base Address Register, 保存S mode异常/中断的跳转地址。 MODE为0,直接跳转到BASE;MODE为1,跳转到BASE + cause * 4。 在linux entry.S中的做法为直接设置stvec为handle_exception地址,地址的后两位肯定是4bytes对齐的,所以为00。跳转到handle_exception后,分开处理中断、系统调用、异常。根据异常cause再跳转到不同的异常处理函数。 SIP SIE Supervisor Interrupt pending/enable Registers SIP中断挂起(待处理的中断)和SIE中断使能。每一位代表的中断与scause中的为每个中断分配的异常码一致。 当sstatus.SIE=1, SIP[x]=1, SIE[x]=1,表示系统能够处理某个中断。 SIP中有的位是只读的,不能通过直接写0来清除: SEIP is read-only in sip, and is set and cleared by the execution environment, typically through a platform-specific interrupt controller....

2023-05-26 · 1 min

uCore_Chapter3 多道程序与分时多任务

分时多任务系统与抢占式调度 RISC-V架构中断 以内核所在的 S 特权级为例,中断屏蔽相应的 CSR 有 sstatus 和 sie 。sstatus 的 sie 为 S 特权级的中断使能,能够同时控制三种中断,如果将其清零则会将它们全部屏蔽。即使 sstatus.sie 置 1 ,还要看 sie 这个 CSR,它的三个字段 ssie/stie/seie 分别控制 S 特权级的软件中断、时钟中断和外部中断的中断使能。 当 Trap 发生时,sstatus.sie 会被保存在 sstatus.spie 字段中,同时 sstatus.sie 置零,这也就在 Trap 处理的过程中屏蔽了所有 S 特权级的中断; 当 Trap 处理完毕 sret 的时候, sstatus.sie 会恢复到 sstatus.spie 内的值。 也就是说,如果不去手动设置 sstatus CSR ,在只考虑 S 特权级中断的情况下,是不会出现 嵌套中断 (Nested Interrupt) 的。 嵌套中断可以分为两部分:在处理一个中断的过程中又被同特权级/高特权级中断所打断。默认情况下硬件会避免前一部分,也可以通过手动设置来允许前一部分的存在;而从上面介绍的规则可以知道,后一部分则是无论如何设置都不可避免的。 时钟中断与计时器 计数器保存在一个 64 位的 CSR mtime 另外一个 64 位的 CSR mtimecmp 的作用是:一旦计数器 mtime 的值超过了 mtimecmp,就会触发一次时钟中断。这使得我们可以方便的通过设置 mtimecmp 的值来决定下一次时钟中断何时触发。...

2023-05-17 · 2 min

uCore_Chapter1 应用程序与基本执行环境

Notes make build # 仅编译 make run # 编译+运行qemu make run LOG=trace # 其他选项可以看Makefile make clean # rm build/ make debug # 编译+运行gdb调试 每次在make run之前,尽量先执行make clean以删除缓存,特别是在切换ch分支之后。 退出 qemu 的方法 如果是正常推出,uCore 会自动关闭 qemu,但如果 os 跑飞了,我们不能通过 Ctrl + C 来推出。此时可以先按下 Ctrl+A ,再按下 X 来退出 Qemu。 ch1 流程 // entry.S _entry la sp, boot_stack_top //设置堆栈 call main // main.c clean_bss(); printf("hello wrold!\n"); consputc(); console_putchar(int c); sbi_call(SBI_CONSOLE_PUTCHAR, c, 0, 0); 怎么用gdb调试?`file kernel`然后? Makefile流程分析 根据make run 打印的信息:...

2023-04-25 · 1 min

uCore_Chapter2 批处理系统

make -C user clean # 在os目录,相当于cd user;make clean;cd .. make clean # 或者在user目录 git checkout ch2 make user BASE=1 CHAPTER=2 make run make test BASE=1 # make test 会完成 make user 和 make run 两个步骤(自动设置 CHAPTER) 流程 main(); printf("hello wrold!\n"); trap_init(); // 设置中断/异常处理地址 w_stvec((uint64)uservec & ~0x3); //把uservec地址传入,uservec在trampoline.S中定义 asm volatile("csrw stvec, %0" : : "r"(x)); // 设置stvec CSR loader_init(); run_next_app(); load_app(); usertrapret(trapframe, (uint64)boot_stack_top); w_sepc(trapframe->epc); r_sstatus(); w_sstatus(); userret(); sret // 返回到sepc中的值,0x80400000第一个app 应用程序系统调用ecall进入内核异常处理过程 // 进入应用程序 exit(MAGIC); syscall(SYS_exit, code); __syscall1(n, long(1234)); __asm_syscall("r"(a7), "0"(a0)); ecall //通过ecall 进入uservec uservec usertrap(); // ld t0, 16(a0) jr t0 r_scause(); csrr %0, scause // 应用层调用了ecall指令,所以scause自动被设置为8 syscall(); //在uservec中应用层传入eid到寄存器a7,这里读a7来判断是什么system call id = trapframe->a7; usertrapret(); // 这里回到第九行一样,循环,执行第二个应用程序 分析下uservec,注意:这里只是把stvec设置为uservec地址,并不会执行uservec下的代码,要等U mode的中断/异常到来时才会从uservec开始执行。...

2023-04-25 · 1 min