xv6ProcessMemory - ccc-sp/riscv2os GitHub Wiki
xv6: 行程的記憶體管理
在 user mode,第一個被啟動的是只佔一個分頁的 init 行程,然後就會啟動 shell,接著再透過 fork()+exec() 創建並載入新的行程。
kernel/proc.c
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void) // 行程 fork()
{
int i, pid;
struct proc *np;
struct proc *p = myproc();
// Allocate process.
if((np = allocproc()) == 0){ // 分配新的子行程
return -1;
}
// Copy user memory from parent to child.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){ // 將分頁表複製給子行程。
freeproc(np);
release(&np->lock);
return -1;
}
np->sz = p->sz; // 子行程大小和父行程相同
// copy saved user registers.
*(np->trapframe) = *(p->trapframe); // 暫存器也相同
// Cause fork to return 0 in the child.
np->trapframe->a0 = 0; // 子行程的 fork 傳回值應為 0 (注意,父行程傳回值沒修改)
// increment reference counts on open file descriptors.
for(i = 0; i < NOFILE; i++)
if(p->ofile[i])
np->ofile[i] = filedup(p->ofile[i]); // 複製檔案表
np->cwd = idup(p->cwd); // 複製 cwd 目前目錄
safestrcpy(np->name, p->name, sizeof(p->name)); // 複製名稱
pid = np->pid;
release(&np->lock);
acquire(&wait_lock);
np->parent = p;
release(&wait_lock);
acquire(&np->lock);
np->state = RUNNABLE; // 設定子行程為 RUNNABLE
release(&np->lock);
return pid;
}
fork() 透過呼叫 allocproc() 從 proc[NPROC] 陣列中分配一個新的行程結構,新行程一開始只有分配一頁 (彈跳床),其他的分頁會在 MMU 的控制下,當存取失敗時引發中斷才真正去載入,這樣可以節省記憶體,不用一次載入整個程式到記憶體中。
// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
static struct proc*
allocproc(void) // 取得行程表中未使用的一格分配出去
{
struct proc *p;
// 尋找未使用的一格
for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state == UNUSED) {
goto found;
} else {
release(&p->lock);
}
}
return 0;
found:
p->pid = allocpid(); // 分配行程代號
p->state = USED; // 狀態改為已使用
// Allocate a trapframe page. // 分配彈跳床頁
if((p->trapframe = (struct trapframe *)kalloc()) == 0){
freeproc(p);
release(&p->lock);
return 0;
}
// An empty user page table. // 初始化分頁表
p->pagetable = proc_pagetable(p);
if(p->pagetable == 0){
freeproc(p);
release(&p->lock);
return 0;
}
// 初始化內文區域,設定內文中的堆疊 sp 與 ra
// 返回位址 ra 設為 forkret(),這樣才會初始化檔案表等結構
// Set up new context to start executing at forkret,
// which returns to user space.
memset(&p->context, 0, sizeof(p->context));
p->context.ra = (uint64)forkret;
p->context.sp = p->kstack + PGSIZE;
return p;
}