在init/main.c中的main函數中可以發現如下語句:
if (!fork()) {
init();//1號進程要運行的代碼
}
for(;;) pause();//0號進程要運行的代碼
上面的注釋中已經寫的很清楚了,1號進程的創建是通過調用fork函數創建的,然後運行相應的init()函數,init函數即為進程1的主體,fork函數的聲明位於include/unistd.h中
int fork(void);
可知fork函數是一個系統調用,其實現是通過相應的匯編代碼來實現的(linux 0.11/0.12)
_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
addl $20,%esp
1: ret
看到了吧,其實匯編代碼還是很好理解的,為了理解上述代碼,我們還必須要看的一個文件就是kernel/fork.c這個文件,這個文件主要涉及的是進程相關的創建,拷貝操作
//先找到一個目前沒有使用的PID,然後任務表中找到一個目前沒有使用的空間
//將該空間的索引號返回,如果沒有找到空間就返回-EAGAIN(-11)
//last_pid是一個全局變量被設置成0
int find_empty_process(void)
{
int i;
repeat:
if ((++last_pid)<0) last_pid=1;//只要在last_pid超過了數據的表示范圍,才會出現last_pid<0
//找到沒有使用的pid號,個人覺的寫的不好,浪費時間
for(i=0 ; i<NR_TASKS ; i++)
if (task[i] && task[i]->pid == last_pid) goto repeat;
//看看task_struct中有沒有空閒的PCB塊,如果有的話就返回其索引號
for(i=1 ; i<NR_TASKS ; i++)
if (!task[i])
return i;
return -EAGAIN;
}
下面是另一個比較關鍵的函數copy_process
//這個函數可以說是fork的核心,復制父進程的操作是由它完成的,按照執行順序可以將其分解成
//5個部份,進程常規變量的設定,TSS的設置 ,分配內存,文件設定及其它
//具有17個參數
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
long ebx,long ecx,long edx,
long fs,long es,long ds,
long eip,long cs,long eflags,long esp,long ss)
{
struct task_struct *p;
//其中p是任務數據結構指針,tss是任務狀態段結構,內核為新任務申請內存用作保存其
//task_struct結構數據,而tss結構段是task_struct中的一個段,該任務的內核棧段值也被設置成為
//0x10,即內核數據段選擇符,而tss.esp0則指向保存task_struct結構頁面的未端。
int i;
struct file *f;
//為新的進程分配一頁的內存空間
p = (struct task_struct *) get_free_page();
//如果分配失敗就返回錯誤碼
if (!p)
return -EAGAIN;
//將新進程空間的首地址賦給任務表
task[nr] = p;
//由於當前進程就是父進程,所以這裡就是對父進程的完整copy
*p = *current; /* NOTE! this doesn't copy the supervisor stack */
//新進程的狀態是不可中斷的等待狀態
p->state = TASK_UNINTERRUPTIBLE;
//設置子進程的ID
p->pid = last_pid;
//設置子進程的父進程ID
p->father = current->pid;
//設置子進程與父進程的優先級相同
p->counter = p->priority;
p->signal = 0;
p->alarm = 0;
p->leader = 0; /* process leadership doesn't inherit */
p->utime = p->stime = 0;
p->cutime = p->cstime = 0;
//設置進程的開始時間為當前的滴答數
p->start_time = jiffies;
//設置新進程的TSS內容
//設置新進程的TSS的內容
p->tss.back_link = 0;
p->tss.esp0 = PAGE_SIZE + (long) p;//棧底指針
//段選擇符,表示的是段基地址
p->tss.ss0 = 0x10;
p->tss.eip = eip;
p->tss.eflags = eflags;
p->tss.eax = 0;
p->tss.ecx = ecx;
p->tss.edx = edx;
p->tss.ebx = ebx;
p->tss.esp = esp;
p->tss.ebp = ebp;
p->tss.esi = esi;
p->tss.edi = edi;
p->tss.es = es & 0xffff;
p->tss.cs = cs & 0xffff;
p->tss.ss = ss & 0xffff;
p->tss.ds = ds & 0xffff;
p->tss.fs = fs & 0xffff;
p->tss.gs = gs & 0xffff;
p->tss.ldt = _LDT(nr);
p->tss.trace_bitmap = 0x80000000;
//如果當前任務使用了協處理器,就保存其上下文
if (last_task_used_math == current)
__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
if (copy_mem(nr,p)) {
task[nr] = NULL;
free_page((long) p);
return -EAGAIN;
}
//如果父進程中有文件 是打開的,則將對應的文件打開次數增1
for (i=0; i<NR_OPEN;i++)
if (f=p->filp[i])
f->f_count++;
//當前進程的pwd,root,和executable引用次數均增1
if (current->pwd)
current->pwd->i_count++;
if (current->root)
current->root->i_count++;
if (current->executable)
current->executable->i_count++;
//在內存的TSS描述符表和LDT描述符表中建立新進程的描述符
//新進程的描述符對應的TSS與LDT是從進程中取得的
//目前的LDT與父進程的LDT是相同的
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;
}
從這裡應該可以在往回看,就應該明白了sys_fork是如何創建進程1的啦。
相關閱讀:Linux中0號進程的創建 http://www.linuxidc.com/Linux/2013-07/87011.htm