本文分析基於Linux 0.11核心,轉載請表明出處http://blog.csdn.net/yming0221/archive/2011/06/05/6527337.aspx
Linux在move_to_user_mode()之後,進程0通過fork()產生子進程實際就是進程1(init進程)。
其中fork()是通過內嵌彙編的形式給出
#define _syscall0(type,name) /<br />type name(void) /<br />{ /<br />long __res; /<br />__asm__ volatile ( "int $0x80" /// 調用系統中斷0x80。<br />:"=a" (__res) /// 傳回值??eax(__res)。<br />:"" (__NR_##name)); /// 輸入為系統中斷調用號__NR_name。<br /> if (__res >= 0) /// 如果傳回值>=0,則直接返回該值。<br /> return (type) __res; errno = -__res; /// 否則置出錯號,並返回-1。<br /> return -1;}<br />
這樣使用int 0x80中斷,調用sys_fork系統調用來建立進程。詳細過程如下:
系統在sched.c中sched_init()函數最後設定系統調用中斷門
set_system_gate (0x80, &system_call);
設定系統調用的中斷號。
通過int 0x80調用sys_fork()
其使用彙編實現
系統將堆棧的內容入棧,然後執行call _sys_call_table(,%eax,4)
調用地址 = _sys_call_table + %eax * 4
然後真正調用sys_fork()
_sys_fork:<br />call _find_empty_process # 調用find_empty_process()(kernel/fork.c,135)。<br />testl %eax,%eax<br />js 1f<br />push %gs<br />pushl %esi<br />pushl %edi<br />pushl %ebp<br />pushl %eax<br />call _copy_process # 調用C 函數copy_process()(kernel/fork.c,68)。<br />addl $20,%esp # 丟棄這裡所有壓棧內容。<br />1: ret
然後調用find_empty_process()
int find_empty_process (void)<br />{<br /> int i;<br />repeat:<br /> if ((++last_pid) < 0)<br /> last_pid = 1;<br /> for (i = 0; i < NR_TASKS; i++)<br /> if (task[i] && task[i]->pid == last_pid)<br /> goto repeat;<br /> for (i = 1; i < NR_TASKS; i++)// 任務0 排除在外。<br /> if (!task[i])<br /> return i;<br /> return -EAGAIN;<br />}
該函數設定last_pid為最後可用不重複的pid號,然後返回task[]數組中閒置項的index,存放在EAX中。
再將相應的寄存器 入棧,作為C函數的參數,調用copy_process()
int<br />copy_process (int nr, long ebp, long edi, long esi, long gs, long none,<br /> long ebx, long ecx, long edx,<br /> long fs, long es, long ds,<br /> long eip, long cs, long eflags, long esp, long ss)<br />{<br /> struct task_struct *p;<br /> int i;<br /> struct file *f;<br /> p = (struct task_struct *) get_free_page ();// 為新任務資料結構分配記憶體。<br /> if (!p)// 如果記憶體配置出錯,則返回出錯碼並退出。<br /> return -EAGAIN;<br /> task[nr] = p;// 將新任務結構指標放入任務數組中。<br />// 其中nr 為任務號,由前面find_empty_process()返回。<br /> *p = *current;/* NOTE! this doesn't copy the supervisor stack */<br />/* 注意!這樣做不會複製超級使用者的堆棧 */ (只複製當前進程內容)。<br /> p->state = TASK_UNINTERRUPTIBLE;// 將新進程的狀態先置為不可中斷等待狀態。<br /> p->pid = last_pid;// 新進程號。由前面調用find_empty_process()得到。<br /> p->father = current->pid;// 設定父進程號。<br /> p->counter = p->priority;<br /> p->signal = 0;// 訊號位元影像置0。<br /> p->alarm = 0;<br /> p->leader = 0;/* process leadership doesn't inherit */<br />/* 進程的領導權是不能繼承的 */<br /> p->utime = p->stime = 0;// 初始化使用者態時間和核心態時間。<br /> p->cutime = p->cstime = 0;// 初始化子進程使用者態和核心態時間。<br /> p->start_time = jiffies;// 當前滴答數時間。<br />// 以下設定任務狀態段TSS 所需的資料(參見列表後說明)。<br /> p->tss.back_link = 0;<br /> p->tss.esp0 = PAGE_SIZE + (long) p;// 堆棧指標(由於是給任務結構p 分配了1 頁<br />// 新記憶體,所以此時esp0 正好指向該頁頂端)。<br /> p->tss.ss0 = 0x10;// 堆棧段選擇符(核心資料區段)[??]。<br /> p->tss.eip = eip;// 指令代碼指標。<br /> p->tss.eflags = eflags;// 標誌寄存器。<br /> p->tss.eax = 0;<br /> p->tss.ecx = ecx;<br /> p->tss.edx = edx;<br /> p->tss.ebx = ebx;<br /> p->tss.esp = esp;<br /> p->tss.ebp = ebp;<br /> p->tss.esi = esi;<br /> p->tss.edi = edi;<br /> p->tss.es = es & 0xffff;// 段寄存器僅16 位有效。<br /> p->tss.cs = cs & 0xffff;<br /> p->tss.ss = ss & 0xffff;<br /> p->tss.ds = ds & 0xffff;<br /> p->tss.fs = fs & 0xffff;<br /> p->tss.gs = gs & 0xffff;<br /> p->tss.ldt = _LDT (nr);// 該新任務nr 的局部描述符表選擇符(LDT 的描述符在GDT 中)。<br /> p->tss.trace_bitmap = 0x80000000;<br />// 如果當前任務使用了副處理器,就儲存其上下文。<br /> if (last_task_used_math == current)<br /> __asm__ ("clts ; fnsave %0"::"m" (p->tss.i387));<br />// 設定新任務的代碼和資料區段基址、限長並複製頁表。如果出錯(傳回值不是0),則複位任務數組中<br />// 相應項並釋放為該新任務分配的記憶體頁。<br /> if (copy_mem (nr, p))<br /> {// 返回不為0 表示出錯。<br /> task[nr] = NULL;<br /> free_page ((long) p);<br /> return -EAGAIN;<br /> }<br />// 如果父進程中有檔案是開啟的,則將對應檔案的開啟次數增1。<br /> for (i = 0; i < NR_OPEN; i++)<br /> if (f = p->filp[i])<br /> f->f_count++;<br />// 將當前進程(父進程)的pwd, root 和executable 引用次數均增1。<br /> if (current->pwd)<br /> current->pwd->i_count++;<br /> if (current->root)<br /> current->root->i_count++;<br /> if (current->executable)<br /> current->executable->i_count++;<br />// 在GDT 中設定新任務的TSS 和LDT 描述符項,資料從task 結構中取。<br />// 在任務切換時,任務寄存器tr 由CPU 自動載入。<br /> set_tss_desc (gdt + (nr << 1) + FIRST_TSS_ENTRY, &(p->tss));<br /> set_ldt_desc (gdt + (nr << 1) + FIRST_LDT_ENTRY, &(p->ldt));<br /> p->state = TASK_RUNNING;/* do this last, just in case */<br />/* 最後再將新任務設定成可運行狀態,以防萬一 */<br /> return last_pid;// 返回新進程號(與任務號是不同的)。<br />}
這段代碼的執行內容是:首先為進程分配記憶體,然後將新任務的指標放入上步查到的空閑task[]數組項中,然後複製父進程的內容後修改當前
進程的一部分屬性和tss(任務狀態段),最後設定新進程的程式碼片段和資料區段,限長,在GDT 中設定新任務的TSS 和LDT 描述符項,資料從task 結構中取。在任務切換時,任務寄存器tr 由CPU 自動載入。
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;
這樣,新進程就建立完畢了。
其中複製頁表函數copy_mem()待續.......