# 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)