執行 SAVE_ALL,保存全部寄存器到當前進程內核棧中;
node
函數返回,執行 RESTORE_ALL,恢復保存的寄存器;執行 iret,cpu 切換至用戶態;
數據結構
從 eax 中取出返回值,fork() 返回;函數
詳見:系統調用的工做機制atom
當咱們調用 fork()、clone()、vfork() 時,實際上在內核中調用的都是同一個函數 —— do_fork()操作系統
這裏的三個系統調用的區別就在於調用 do_fork() 時傳入的參數不一樣線程
do_fork() 中第一個參數 clone_flags 是一個 32bit 的標誌,其中不一樣的 bit 置 1 表明不一樣的選項,表示新的子進程與父進程之間共享哪些資源3d
其中 sys_fork() 調用 do_fork() 只設置了 SIGCHLD 選項,sys_vfork() 設置了 CLONE_VM | CLONE_VFORK | SIGCHLD 選項,而 sys_clone() 的參數來自上層,經過 ebx 傳入;指針
下面簡述下 do_fork() 的執行過程code
struct task_struct *tsk; struct thread_info *ti;
tsk = alloc_task_struct(); if (!tsk) return NULL;
struct task_struct { struct thread_info * thread_info; // 指向 thread_info 的指針 struct mm_struct * mm; // 進程地址空間 pid_t pid; struct list_head children; // 子進程鏈表 ... } struct thread_info { struct task_struct task; // 指向 task_struct 的指針 _u32 cpu; // 當前所在的cpu mm_segment_t addr_limit; // 線程地址空間 // user_thread 0-0xBFFFFFFF // kernel_thread 0-0xFFFFFFFF ... } // thread_info 和 stack 共享一塊內存 union thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE/sizeof(long)]; };
*ti = *current->thread_info; ti->task = tsk;
新進程的進程描述符建立完成,返回至 copy_process()orm
atomic_inc(p->user->process);
檢查系統中的進程數量是否超過了 max_threads;
max_threads的數量由系統內存容量決定,全部的thread_info描述符和內核棧所佔用空間不能超過系統內存的1/8;
拷貝全部的進程信息:
struct mm_struct { struct vm_area_struct * mmap; // 指向線性區對象的鏈表頭 struct rb_root mm_rb; // 指向線性區對象的紅黑樹的根 pgd_t * pgd; // 指向頁全局目錄 atomic_t mm_users; // 次使用計數器,存放共享 mm_struct 數據結構輕量級進程的個數 atomic_t mm_count; // 主使用計數器,每當 mm_count 遞減,內核就要檢查它是否爲0,若是是就要解除這個內存描述符 }
oldmm = current->mm; //oldmm 初始化爲父進程的 mm_struct
atomic_inc(&oldmm->mm_users); // 父進程的地址空間引用計數加一 mm = oldmm; // 將父進程地址空間賦給子進程
mm = allocate_mm(); memcpy(mm, oldmm, sizeof(*mm));
struct vm_area_struct { struct mm_struct * vm_mm; // 指向線性區所在的內存描述符 unsigned long vm_start; // 當前線性區起始地址 unsigned long vm_end; // 線性區尾地址 struct vm_area_struct * vm_next; // 下一個線性區 pgprot_t vm_page_prot; // 線性區訪問權限 struct rb_node vm_rb; // 用於紅黑樹搜索的節點 }
新進程的線性區和頁表複製完成,返回至copy_process()
調用 copy_thread() 用父進程的內核棧初始化子進程的內核棧
將eax的值強制設爲0(fork / clone 系統調用的返回值)
childregs->eax = 0;
進程建立完成,返回至 do_fork()
返回至 do_fork()
fork() 和 vfork() 參數是寫死的,而 clone() 是可選的,它能夠選擇當前建立的進程哪些部分是共享的,哪些部分是獨立的;
vfork() 是歷史的產物,當調用 fork() 的時候,須要將父進程的線性區和頁表都拷貝一份,而調用 exec() 執行新程序後,又要把全部頁表刪除重置新的頁表,創建映射關係,效率很低;
因此要有 vfork(),vfork() 的 clone_flags 位置了 CLONE_VM ,表示共享父進程的地址空間,vfork() 中建立的進程沒有分配本身的地址空間,而是經過一個 mm_struct 指針指向父進程的地址空間,這個進程是爲了在以後調用 exec() 執行新的程序;
而在有了 Copy-on-write 技術後,fork() 出的子進程只建立了本身的地址空間,而後用父進程的地址空間初始化,每一個頁表的項置爲父進程的頁表項,共享父進程的物理頁面,並將全部 私有/可寫 頁面改成只讀;
當咱們改變父子進程的數據後,cpu 在運行過程當中會發生一個缺頁錯誤,cpu 轉交控制權給操做系統,操做系統查找 VMA 發現該頁權限爲只讀,但所在段又是可寫的,產生一個矛盾,這就是識別 Copy-on-write 的方法,接着 OS 給子進程分配一個新的物理頁,並將頁表該頁的地址修改爲新的物理頁地址;
這樣 fork() 後再調用 exec() 就不用那麼麻煩了,能夠直接將新的物理頁與子進程的虛擬空間創建映射
綜上,fork 在建立子進程時,主要作了這些工做