進程-3:fork()函數

1. 示例程序

#include <unistd.h>; 
#include <sys/types.h>; 
main () 
{ 
     pid_t pid; 
      pid=fork(); 
      if (pid < 0) 
              printf("error in fork!"); 
       else if (pid == 0) 
              printf("i am the child process, my process id is %dn",getpid()); 
        else 
               printf("i am the parent process, my process id is %dn",getpid()); 
} 

結果是:

i am the child process, my process id is 2139
i am the parent process, my process id is 2138

之前看過很多解釋,覺得一次調用fork()函數,但返回兩次,很神奇。下面我們來看看fork()函數源代碼,你就會豁然大明白了。

2. fork() 源代碼

fork()是一個系統調用,內核中sys_fork是其實現:

_sys_fork:    
        call _find_empty_process
	testl %eax,%eax
	js 1f
	push %gs
	pushl %esi
	pushl %edi
	pushl %ebp
	pushl %eax
	call _copy_process
	addl $20,%esp
1:	ret
這個函數先調用find_empty_process()函數,之後調用copy_process函數

I. 在進程一文中講到進程一定要有一個task_struct結構,find_empty_process()函數就是在全局結構數組task中找了一個空的task_struct結構和一個未使用的進程id。返回任務號,即在數組中的位置。下面是其代碼:

int find_empty_process(void)
{
	int i;

	repeat:
		if ((++last_pid)<0) last_pid=1;
		for(i=0 ; i<NR_TASKS ; i++)
			if (task[i] && task[i]->pid == last_pid) goto repeat;
	for(i=1 ; i<NR_TASKS ; i++)
		if (!task[i])
			return i;
	return -EAGAIN;
}

II. copy_process()函數 核心代碼如下所示

int copy_process()
{
	struct task_struct *p;
	int i;
	struct file *f;

	p = (struct task_struct *) get_free_page();
	if (!p)
		return -EAGAIN;
	task[nr] = p;
	*p = *current;	/* NOTE! this doesn't copy the supervisor stack */
	p->state = TASK_UNINTERRUPTIBLE;
	p->pid = last_pid;
	p->father = current->pid;
	p->tss.esp0 = PAGE_SIZE + (long) p;
	p->tss.ss0 = 0x10;
    p->tss.eax = 0;
    p->tss.eip = eip;
	p->tss.cs = cs & 0xffff;
	p->tss.ss = ss & 0xffff;
	p->tss.ds = ds & 0xffff;
	set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
	set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
	p->state = TASK_RUNNING;	/* do this last, just in case */
	return last_pid;
}
第7行:先在內核空間中申請一頁內存

第10行:把此頁的地址賦給全局數組中相應的位置,也就是find_empty_process函數的返回值。

第11行:把當前的任務數據結構複製到剛申請的內存p處。

第12-16行:給新進程結構做一些修改,如:狀態,進程id,父進程,內核棧等等

第17行:eax賦值爲0,這個0就是新進程(子進程)fork的返回值。

第18-19行:關鍵的兩行,cs:eip在當前進程指向的fork()的下一個指令:即示例程序的if(pid < 0)。當前進程將要執行此代碼。而把cs:eip指向的代碼賦值給新進程。因此新進程下一條指令也是if(pid < 0). 分別由兩個進程從if(pid < 0)分別向下執行,因此執行到最後有兩次返回。我說的清楚嗎?

第20-21行:設置cs,ss,ds等代碼段,堆棧段和數據段。

第22-23行:把task_struct中的tss結構與gdt相聯繫。在進程切換的時候,要保持原任務的上下文,也就是保存到了tss中的結構中。

第24行:設置新進程的狀態爲running

第25行:返回新進程的進程id,此值也是當前進程(父進程)fork對應的返回值,爲子進程的進程id。

總的來說fork()做的工作就是創建了一個新的task_struct結構,對新進程結構做了一些修改。關鍵的就是對cs:eip的複製,讓新老進程都共同指向了if(pid < 0) 指令,靠進程切換,分別向下執行,返回兩次。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章