雖然篇幅很長,但大多是易懂的代碼,不用擔心看不完

這里的所有操作,都將在下面的代碼中有所體現
fork會拷貝當前進程的內存,並創建一個新的進程。如上圖,fork函數會將整個進程的內存鏡像拷貝到新的內存地址,包括代碼段、數據段、堆棧以及寄存器內容。之后,我們就有了兩個擁有完全一樣內存的進程。fork系統調用在兩個進程中都會返回,在父進程中,fork系統調用會返回子進程的pid。而在新創建的進程中,fork系統調用會返回0。所以即使兩個進程的內存是完全一樣的,我們還是可以通過fork的返回值區分舊進程和新進程。
某種程度上來說這里的拷貝操作浪費了,因為所有拷貝的內存都被丟棄並被exec替換。在大型程序中這里的影響會比較明顯。實際上操作系統會對其進行優化。(比如使用COW(copy on write)技術)
fork創建的新進程從fork語句后開始執行,因為新進程也繼承了父進程的PC程序計數器。
在xv6中唯一不是通過fork創建進程的場景就是創建第一個進程的時候,之后的所有進程都是通過fork創建的。
以下內容以xv6(教學用Linux操作系統)源代碼為例
代碼解析
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
int i, pid;
struct proc *np;
//獲取當前進程(進程只運行在用戶態)
//fork函數是系統調用,實際上運行在內核態,用戶態的寄存器內容、內存狀態都會被保留下來,所以不用擔心fork函數的運行影響原進程
struct proc *p = myproc();
// Allocate process.
//allocproc實際上從操作系統進程隊列中找到一個未在使用狀態的空進程,
//為其分配trapframe(用來存儲寄存器內容),page table頁表,並設置了一些寄存器狀態。代碼見附
if((np = allocproc()) == 0){
return -1;
}
// Copy user memory from parent to child.
//將父進程的頁表內容拷貝到子進程頁表中
//uvmcopy user virtual memory copy
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.
//a0為返回值寄存器,fork函數結束會將a0的值返回,因此子進程fork的返回值為0
np->trapframe->a0 = 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);
safestrcpy(np->name, p->name, sizeof(p->name));
//在這里實現了父進程fork返回子進程pid
pid = np->pid;
//下面設置子進程的父進程,將子進程的狀態設置為可運行
//之后,操作系統會在進程調度時,執行子進程
release(&np->lock);
acquire(&wait_lock);
np->parent = p;
release(&wait_lock);
acquire(&np->lock);
np->state = RUNNABLE;
release(&np->lock);
return pid;
}
附:
alloproc代碼
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;
}
// 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;
}
Trapframe代碼:
struct trapframe {
/* 0 */ uint64 kernel_satp; // kernel page table
/* 8 */ uint64 kernel_sp; // top of process's kernel stack
/* 16 */ uint64 kernel_trap; // usertrap()
/* 24 */ uint64 epc; // saved user program counter
/* 32 */ uint64 kernel_hartid; // saved kernel tp
/* 40 */ uint64 ra;
/* 48 */ uint64 sp;
/* 56 */ uint64 gp;
/* 64 */ uint64 tp;
/* 72 */ uint64 t0;
/* 80 */ uint64 t1;
/* 88 */ uint64 t2;
/* 96 */ uint64 s0;
/* 104 */ uint64 s1;
/* 112 */ uint64 a0;
/* 120 */ uint64 a1;
/* 128 */ uint64 a2;
/* 136 */ uint64 a3;
/* 144 */ uint64 a4;
/* 152 */ uint64 a5;
/* 160 */ uint64 a6;
/* 168 */ uint64 a7;
/* 176 */ uint64 s2;
/* 184 */ uint64 s3;
/* 192 */ uint64 s4;
/* 200 */ uint64 s5;
/* 208 */ uint64 s6;
/* 216 */ uint64 s7;
/* 224 */ uint64 s8;
/* 232 */ uint64 s9;
/* 240 */ uint64 s10;
/* 248 */ uint64 s11;
/* 256 */ uint64 t3;
/* 264 */ uint64 t4;
/* 272 */ uint64 t5;
/* 280 */ uint64 t6;
};