linux下建立程序的方式有三種,通過fork vfork clone系統呼叫實現程序的建立
1. fork
fork函式用於建立乙個新的程序,其建立的程序和當前程序為父關係,子程序建立自己的task_struct 之後初始化子程序的互斥變數、cpu定時器、自旋鎖、掛起訊號、程序資料結構等並且設定程序狀態, 然後子程序複製父程序的各項資訊(包括檔案系統、訊號處理函式、訊號、記憶體管理等)但是拷貝父程序的mm_struct時採用寫時拷貝(後面詳解)。之後會為子程序設定核心棧(thread_info) 和程序的pid 以及與呼叫程序的關係設定,呼叫 wake_up_new_task 將子程序加入排程器,為之分配 cpu;
使用fork()建立的子程序和和父程序通過寫時拷貝共享同一虛擬位址空間,子程序和父程序各自擁有不同的核心棧,子程序共享父程序的時間片。
寫時複製(copy _on_write)
子程序與父程序共享同一頁幀,不是複製,並且將子程序和父程序對該頁幀的許可權均設定成唯讀(頁幀被保護),當有任意一方需要進行寫入時會觸發出錯異常(page_fault int14)中斷,此時系統會為其重新分配乙個新的頁幀並且複製之前頁幀內容標記可寫, 然後返回繼續執行寫操作,此時原來的頁幀仍然被寫保護當,再有程序對其程序寫操作時,核心檢查寫程序是否是這個頁幀的唯一屬主,如果是,就把這個頁幀標記為對這個程序是可寫的。
另外linux下c++標準庫中string類也使用了寫時拷貝
2. vfork
vfork也是用於建立乙個新的程序,建立過程和fork相似,不過在vfork進行拷貝父程序mm_struct時進行的是指標值拷貝也就是說父子程序指向同一程序位址空間,不過vfork建立的子程序同樣會為其建立自己的核心棧,另外vfork 需要(設定clone_vfork和ptrace標誌)初始化完成處理資訊,還有父程序會阻塞等待。vfork()保證子程序先排程執行。因為父程序和子程序指向同一程序位址空間則使用vfork函式後會立即使用exec函式進行替換,當程序呼叫一種exec函式時,該程序完全由新程式代換,而新程式則從其main函式開始執行,因為呼叫exec並不建立新程序,所以前後的程序id 並未改變,exec只是用另乙個新程式替換了當前程序的正文,資料,堆和棧段。
如果在程序不去替換子程序,子程序繼續執行將可能破壞父程序的資料結構或棧而出錯。
3. fork和vfork呼叫流程
fork, vfork和clone的系統呼叫的入口位址分別是sys_fork, sys_vfork和sys_clone
以下**均來自linux-4.5
推薦乙個可以看各版本核心的**並且支援檢索sys_fork()實現
#ifdef __arch_want_sys_fork
syscall_define0
(fork)
#endif
sys_vfork()實現
#ifdef __arch_want_sys_vfork
syscall_define0
(vfork)
#endif
fork和vfork實現最後都呼叫了_do_fork函式區別在於引數不同,其中vfork函式clone_flags引數多了兩個屬性,clone_vfork 和clone_vm 接著看_do_fork實現
long
_do_fork
(unsigned
long clone_flags,
unsigned
long stack_start,
unsigned
long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
unsigned
long tls)
// 複製程序描述符
p =copy_process
(clone_flags, stack_start, stack_size,
child_tidptr,
null
, trace, tls)
;trace_sched_process_fork
(current, p)
;//得到新建立的程序的pid資訊
pid =
get_task_pid
(p, pidtype_pid)
; nr =
pid_vnr
(pid);if
(clone_flags & clone_parent_settid)
put_user
(nr, parent_tidptr)
;//如果呼叫的 vfork()方法,初始化 vfork 完成處理資訊
if(clone_flags & clone_vfork)
// 將子程序加入到排程器中,為其分配 cpu,準備執行
wake_up_new_task
(p);
/* forking complete and child started to run, tell ptracer */if(
unlikely
(trace)
)ptrace_event_pid
(trace, pid)
;// 如果是 vfork,將父程序加入至等待佇列,等待子程序完成
if(clone_flags & clone_vfork)
put_pid
(pid);}
else
return nr;
}
其主要完成
呼叫 copy_process 為子程序複製出乙份程序資訊
如果是 vfork(設定了clone_vfork和ptrace標誌)初始化完成處理資訊
呼叫 wake_up_new_task 將子程序加入排程器,為之分配 cpu
如果是 vfork,父程序加入等待佇列,待子程序完成 exec 替換自己的位址空間,或者子程序結束
另外檢視copy_process函式
#l1242
static
struct task_struct *
copy_process
(unsigned
long clone_flags,
unsigned
long stack_start,
unsigned
long stack_size,
int __user *child_tidptr,
struct pid *pid,
int trace,
unsigned
long tls)
static
intcopy_mm
(unsigned
long clone_flags,
struct task_struct *tsk)
retval =
-enomem;
//fork呼叫dup_mm->dup_mmap->copy_page_range->copy_pud_range->
//copy_pmd_range->copy_pte_range->copy_one_pte實現寫時複製
mm =
dup_mm
(tsk);if
(!mm)
goto fail_nomem;
good_mm:
tsk->mm = mm;
tsk->active_mm = mm;
return0;
fail_nomem:
return retval;
}
最後fork() vfork() 函式執行完之後在以後的程序切換中,排程程式繼續完善子程序:把子程序描述符thread(即核心棧中的值,因為父子程序核心棧不同,故可以保留不同的值以供esp暫存器讀取因此父子程序返回不同的值)欄位的值(tss值)裝入幾個cpu暫存器。特別是把thread.esp裝入esp暫存器,把函式ret_from_fork()的位址裝入eip暫存器。這個組合語言函式呼叫schedule_tail()函式,用存放在棧中的值再裝入所有暫存器,並強迫cpu返回到使用者態。這樣,eax暫存器就裝過兩個值,乙個是子程序的值0,乙個是父程序的值——子程序的pid。
1 #include
2 #include
3 #include
4 #include
5int
main()
613else
if(pid ==0)
//子程序返回0
1418
else
if(pid >0)
//父程序返回子程序的pid
1922
sleep(1
);23return0;
24}
執行結果
參考:linux中fork,vfork和clone詳解(區別與聯絡)
linux下程序的建立過程分析(_do_fork/do_fork詳解)–linux程序的管理與排程(八
程序的建立 —— do_fork()函式詳解
linux 程序控制
什麼是程序 每乙個程序在系統中都有唯一的id標示它,此id為程序標示符 程序標示符的型別 pit t,其實是乙個無符號整形 乙個程序標示符對應唯一的乙個程序,多個程序標示符可以對應同乙個程式 程序和程式的區別 程式 可執行的二進位制 檔案,這種檔案載入到記憶體中執行就得到了乙個程序 程序 同乙個程式...
Linux程序控制
linux程序控制 獲取pid include include pid t getpdi void 獲取本程序id pdi t getppid vodi 獲取父程序id,及獲取建立子程序的程序id 程序建立 include pid t fork void 建立子程序 呼叫一次返回兩次值 1.父程序中...
linux程序控制
linux程序控制 編寫命令直譯器 一 背景 之前在 計算機作業系統 這本書中已經多次接觸了程序這一概念,而程序在作業系統中具體是做什麼的呢?卻沒有實際的與程序發生過互動,因此也就沒有實際的感受。對於我來說程序僅僅停留在概念的層面上 程序是程式執行時的記憶體空間和設定或者說程序就是程式的進行時。沒有...