MIT6828 学习笔记 004 (read code 00)
目前只是看代码里的注释试图理解代码,后面如果有了更深的理解再来改。
1. kernel/proc.h
hart (RISC-V): hardware thread (The RISC-V Instruction Set Manual Sec 1.2)
1.1. struct context
为内核 context 切换保存的寄存器
大概用在 CPU 在进程和内核的
scheduler之间切换。大概过程是先把当前寄存器值放进旧struct context里,然后把新的struct context放进寄存器里,简单来说就是先把当前寄存器值放进某块内存里,在把新的值从另一块内存里读进寄存器。
1 | // Saved registers for kernel context switches. |
1.2. struct cpu
每个 CPU
1 | // Per-CPU state. |
1.3. struct trapframe
在 003:5 里首次出现,处理 trap 的代码 (kernel/trampoline.S) 要用到的每个进程的数据?
1 | // per-process data for the trap handling code in trampoline.S. |
1.4. enum procstate
进程状态
1 | enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; |
UNUSED:allocproc时会去进程列表里找这种状态的进程并占用USED:allocproc产生的新进程首先会进入这个状态SLEEPING: 调用wait时有子进程但没有ZOMBIE状态时进程会调用sleep进入这个状态让出 CPURUNNABLE:fork出的新进程准备就绪后会进入这个状态,内核的scheduler会不断让 CPU 寻找这种状态的进程去执行RUNNING:scheduler中 CPU 找到了RUNNABLE的进程后会把这个进程标为RUNNINGZOMBIE: 进程调用exit后会进行一定的清理,然后进入这个模式等待父进程调用wait
1.5. struct proc
每个进程
1 | // Per-process state |
2. kernel/defs.h
里面是源文件 bio.c, console.c, exec.c, file.c, fs.c, ramdisk.c, kalloc.c, log.c, pipe.c, printf.c, proc.c, swtch.c, spinlock.c, sleeplock.c, string.c, syscall.c, trap.c, uart.c, vm.c, plic.c, virtio_disk.c的声明
3. kernel/entry.S
- CSR: Control and Status Register
- mhartid: hart ID
1 | # qemu -kernel 把内核加载到 0x80000000 并使每个 hart 都跳转到这 |
4. kernel/main.c
- PLIC: the riscv Platform Level Interrupt Controller
1 | /* includes */ |
5. user/initcode.S
1 | # 最初的进程,它 exec /init (运行在 user mode ) |
6. user/init.c
最初的用户进程
1 | /* includes */ |
7. 简单看一眼
7.1. kernel/proc.c
1 | struct cpu cpus[NCPU]; |
原来 CPU 和进程的存储方式这么硬核
| 行 | 函数格式 | 实现 |
|---|---|---|
| 65 | int cpuid(); | 从寄存器 tp 读取 |
| 74 | struct cpu* mycpu(); | 用 cpuid() 从 struct cpu cpus[NCPU] 里拿 |
| 83 | struct proc* myproc(); | 通过 cpuid 拿到 struct cpu 里面的 proc 就是当前进程 |
| 93 | int allocpid(); | 从 1 开始递增分配 |
| 110 | static struct proc* allocproc(); | 遍历 struct proc proc[NPROC] 找 UNUSED 进程,没有则返回 0 ,否则给这个进程分配一个 pid ,然后设置成 UNUSED 状态,并给各个成员分配空间,然后使这个新进程的入口为 forkret |
| 280 | int fork(); | 读取当前的 struct proc 复制出一个新的进入 RUNNABLE 状态 |
| 331 | void reparent(struct proc* p); | 把 p 的所有子进程交给 init 进程 |
| 347 | void exit(int status); | 拿到当前进程(拿到 init 时 panic ),关闭所有打开的文件,把所有子进程交给 init ,进入 ZOMBIE 状态,最后 sched() 回到 scheduler |
| 391 | int wait(uint64 addr); | 循环遍历 struct proc proc[NPROC] 找到当前进程的状态为 ZOMBIE 的子进程,释放并返回它的 pid 。如果当前进程没有子进程则返回 -1 。如果没有处于 ZOMBIE 状态的子进程则把当前进程设置为 SLEEPING 后通过 sched() 使当前 CPU 回到 scheduler |
| 445 | void scheduler(); | 每个 CPU 都进入 scheduler 不返回,不断寻找下一个 RUNNABLE 进程,设置为 RUNNING 后 swtch 进去执行 |
| 482 | void sched(); | 暂存当前 CPU 的 intena (interrupt enabled, bool) , swtch 到当前 CPU (回到 scheduler ),恢复 intena |
| 503 | void yield(); | 把自己设置为 RUNNABLE 后 sched() 回 scheduler |
| 536 | void sleep(void* chan, struct spinlock* lk); | 把自己设为 SLEEPING 后回到 scheduler |
7.2. kernel/exec.c
主要是 exec 的实现。首先读取应用程序的 ELF header (struct elfhdr, kernel/elf.h:6) ,比如:
1 | head -c 4 user/_sh | hexdump -C |
可以看到应用程序的 magic bytes :
1 | 00000000 7f 45 4c 46 |.ELF| |
然后把程序加载进内存,分配一个用户栈,把参数压进去,然后把 argv 放进 sp , argc 放进 a0