# 6. Process 管理 :::warning 爲了能在 qemu8 上面運作,我改了蠻多東西的。 請直接看 day6 的 source code。 這邊僅節錄部分程式碼。 ::: ## 用 S mode 來執行 Process :::danger 這是不安全的,它可以輕易的修改 satp 來訪問任意記憶體。 ::: ### proc.h ```c= #pragma once #include "vm.h" #include "param.h" #include "lock.h" #include "sys.h" #include "mem.h" #define STACK_SIZE 8192 #define TRAMPOLINE (MAXVA - PAGE_SIZE) #define TRAPFRAME (TRAMPOLINE - PAGE_SIZE) struct cpu { struct proc* proc; // The process running on this cpu, or null. }; enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; struct proc { lock_t lock; // p->lock must be held when using these: enum procstate state; // Process state int killed; // If non-zero, have been killed int pid; // Process ID // wait_lock must be held when using this: struct proc *parent; // Parent process // these are private to the process, so p->lock need not be held. void* instrs; void* stack; // Virtual address of kernel stack uint64_t sz; // Size of process memory (bytes) pte_t* page_table; // User page table struct context *context; // sys_switch() here to run process char name[16]; // Process name (debugging) }; int allocpid(); void proc_init(); struct proc* create_proc(); int exec(struct proc* process, const char* filename); void set_cpu_proc(struct proc* next); void proc_exec(struct proc* cur, struct proc* nxt); void scheduler(); extern struct cpu cpus[NCPU]; extern struct proc* os_proc; ``` ### proc.c ```c= #include "proc.h" #include "printf.h" #include "string.h" #include "fs.h" #include "mem.h" #include "vm.h" #include "riscv.h" #define PROGRAM_SIZE 1024 * 1024 int nextpid = 1; lock_t pid_lock; lock_t sched_lock; struct cpu cpus[NCPU]; struct proc processes[NPROC]; struct proc* os_proc; int allocpid() { int pid; lock_acquire(&pid_lock); pid = nextpid; nextpid = nextpid + 1; lock_free(&pid_lock); return pid; } void proc_init() { lock_init(&pid_lock); lock_init(&sched_lock); struct proc *p; for(p = processes; p < &processes[NPROC]; p++) { lock_init(&p->lock); p->state = UNUSED; p->stack = malloc(STACK_SIZE); p->page_table = malloc(PAGE_SIZE); } } struct proc* create_proc() { struct proc *p; for(p = processes; p < &processes[NPROC]; p++) { lock_acquire(&p->lock); if(p->state == UNUSED) { p->state = USED; p->pid = allocpid(); struct context *ctx = malloc(sizeof(struct context)); ctx->ra = (reg_t) 0; ctx->sp = (reg_t) p->stack + STACK_SIZE - 1; ctx->satp = MAKE_SATP(p->page_table); p->context = ctx; lock_free(&p->lock); map_pages(p->page_table, (uint64_t)p->stack,(uint64_t)p->stack, STACK_SIZE, PTE_R | PTE_W); map_pages(p->page_table, (uint64_t)p->context, (uint64_t)p->context, PAGE_SIZE, PTE_R | PTE_W); // here might be a security issue that userspace programs can access part of kernel code. map_pages(p->page_table, (uint64_t)sys_switch, (uint64_t)sys_switch, PAGE_SIZE, PTE_R | PTE_X); return p; } lock_free(&p->lock); } return NULL; } int exec(struct proc* process, const char* filename) { lock_acquire(&process->lock); char * instrs = malloc(PROGRAM_SIZE); size_t size = fs_read(filename, instrs, PROGRAM_SIZE); printf("%llx, %llx\n", process->pid, instrs); process->context->ra = instrs + 0x1042; process->instrs = instrs; map_pages(process->page_table, (uint64_t)instrs, (uint64_t)instrs, PROGRAM_SIZE, PTE_R | PTE_X); map_pages(process->page_table, UART, UART, PAGE_SIZE, PTE_R | PTE_W); process->state = RUNNABLE; lock_free(&process->lock); return 0; } void set_cpu_proc(struct proc* next) { cpus[r_tp()].proc = next; } void proc_exec(struct proc* cur, struct proc* next) { set_cpu_proc(next); cur->state = RUNNABLE; sys_switch(cur->context, next->context); trap_init(); } void scheduler() { struct proc *p; for(p = processes; p < &processes[NPROC]; p++) { lock_acquire(&sched_lock); lock_acquire(&p->lock); if(p->state == RUNNABLE) { p->state = RUNNING; proc_exec(cpus[r_tp()].proc, p); } lock_free(&p->lock); lock_free(&sched_lock); } } ``` :::danger 這邊的程式 entry point 0x1042 是 hardcode 的,只是爲了測試 context-switch 和 scheduler。 ::: ### sys.s ```riscv= # ============ MACRO ================== .macro ctx_save base sd ra, 0(\base) sd sp, 8(\base) sd s0, 56(\base) sd s1, 64(\base) sd s2, 136(\base) sd s3, 144(\base) sd s4, 152(\base) sd s5, 160(\base) sd s6, 168(\base) sd s7, 176(\base) sd s8, 184(\base) sd s9, 192(\base) sd s10, 200(\base) sd s11, 208(\base) .endm .macro ctx_load base ld ra, 0(\base) ld sp, 8(\base) ld s0, 56(\base) ld s1, 64(\base) ld a0, 72(\base) ld s2, 136(\base) ld s3, 144(\base) ld s4, 152(\base) ld s5, 160(\base) ld s6, 168(\base) ld s7, 176(\base) ld s8, 184(\base) ld s9, 192(\base) ld s10, 200(\base) ld s11, 208(\base) .endm .macro reg_save base # save the registers. sd ra, 0(\base) sd sp, 8(\base) sd gp, 16(\base) sd tp, 24(\base) sd t0, 32(\base) sd t1, 40(\base) sd t2, 48(\base) sd s0, 56(\base) sd s1, 64(\base) sd a0, 72(\base) sd a1, 80(\base) sd a2, 88(\base) sd a3, 96(\base) sd a4, 104(\base) sd a5, 112(\base) sd a6, 120(\base) sd a7, 128(\base) sd s2, 136(\base) sd s3, 144(\base) sd s4, 152(\base) sd s5, 160(\base) sd s6, 168(\base) sd s7, 176(\base) sd s8, 184(\base) sd s9, 192(\base) sd s10, 200(\base) sd s11, 208(\base) sd t3, 216(\base) sd t4, 224(\base) sd t5, 232(\base) sd t6, 240(\base) csrr t0, satp # Read satp into temporary register t0 sd t0, 248(\base) # Save satp csrr t0, mepc # Read mepc into temporary register t0 sd t0, 256(\base) # Save mepc .endm .macro reg_load base ld t0, 248(\base) # Load satp into temporary register t0 sfence.vma zero, zero csrw satp, t0 # Write satp from temporary register t0 sfence.vma zero, zero ld t0, 256(\base) # Load mepc into temporary register t0 csrw mepc, t0 # Write mepc from temporary register t0 # restore registers. ld ra, 0(\base) ld sp, 8(\base) ld gp, 16(\base) # not this, in case we moved CPUs: ld tp, 24(\base) ld t0, 32(\base) ld t1, 40(\base) ld t2, 48(\base) ld s0, 56(\base) ld s1, 64(\base) ld a0, 72(\base) ld a1, 80(\base) ld a2, 88(\base) ld a3, 96(\base) ld a4, 104(\base) ld a5, 112(\base) ld a6, 120(\base) ld a7, 128(\base) ld s2, 136(\base) ld s3, 144(\base) ld s4, 152(\base) ld s5, 160(\base) ld s6, 168(\base) ld s7, 176(\base) ld s8, 184(\base) ld s9, 192(\base) ld s10, 200(\base) ld s11, 208(\base) ld t3, 216(\base) ld t4, 224(\base) ld t5, 232(\base) ld t6, 240(\base) .endm # ============ Macro END ================== # Context switch # # void sys_switch(struct context *old, struct context *new); # # Save current registers in old. Load from new. .globl sys_switch .align 4 sys_switch: ctx_save a0 # a0 => struct context *old ctx_load a1 # a1 => struct context *new ret .global trap_vector .align 4 trap_vector: # Save the context addi sp, sp, -256 reg_save sp # call the C timer_handler(reg_t epc, reg_t cause) csrr a0, mepc csrr a1, mcause mv a2, sp call trap_handler # timer_handler will return the return address via a0. csrw mepc, a0 # Restore the context reg_load sp addi sp, sp, 256 mret # back to interrupt location (pc=mepc) .global atomic_swap .align 4 atomic_swap: li a5, 1 amoswap.w.aq a5, a5, 0(a0) mv a0, a5 ret ``` ### timer.c ```c= void timer_handler() { int id = r_mhartid(); *(reg_t *)(uintptr_t)CLINT_MTIMECMP(id) = *(reg_t *)(uintptr_t)CLINT_MTIME + interval; proc_exec(cpus[r_mhartid()].proc, os_proc); } ``` ### syscall.c ```c= void do_syscall(struct context *ctx) { uint32_t syscall_num = ctx->a7; switch (syscall_num) { case 1: ctx->a0 = (reg_t)malloc(ctx->a0); break; case 2: free((void *)ctx->a0); break; case 3: printf("program has exitted with code %d on core %d.\n", ctx->a0, r_tp()); w_mstatus(r_mstatus() | MSTATUS_MIE); proc_exec(cpus[r_mhartid()].proc, os_proc); /* while(1); */ break; default: printf("Unknown syscall no: %d\n", syscall_num); ctx->a0 = -1; } } ``` ## 自動獲取 elf entrypoint ### proc.h ```c= /* ELF define */ #define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian // File header struct elfhdr { uint32_t magic; // must equal ELF_MAGIC uint8_t elf[12]; uint16_t type; uint16_t machine; uint32_t version; uint64_t entry; uint64_t phoff; uint64_t shoff; uint32_t flags; uint16_t ehsize; uint16_t phentsize; uint16_t phnum; uint16_t shentsize; uint16_t shnum; uint16_t shstrndx; }; ``` ### proc.c ```c= uint64_t getELFEntryPoint(const char* file) { struct elfhdr ehdr; memcpy(&ehdr, file, sizeof(ehdr)); if(ehdr.magic != ELF_MAGIC) { printf("Not an ELF file\n"); return -1; } printf("Entry point: 0x%llx\n", ehdr.entry); return ehdr.entry + 0x1000; } int exec(struct proc* process, const char* filename) { lock_acquire(&process->lock); char * elf = malloc(PROGRAM_SIZE); size_t size = fs_read(filename, elf, PROGRAM_SIZE); uint64_t entryPoint = getELFEntryPoint(elf); if(entryPoint == -1) return 1; process->context->ra = (reg_t)elf + entryPoint; process->instrs = elf; process->state = RUNNABLE; map_pages(process->page_table, (uint64_t)elf, (uint64_t)elf, PROGRAM_SIZE, PTE_R | PTE_X); map_pages(process->page_table, UART, UART, PAGE_SIZE, PTE_R | PTE_W); lock_free(&process->lock); return 0; } ``` :::warning 由於我們在這邊做了 map pages,所以之前實作的 malloc 和 free 會壞掉。 ::: ## 參考資料 1. ChatGPT 2. [xv6-riscv](https://github.com/mit-pdos/xv6-riscv)