深入剖析 fork 內核實現(Linux kernel 2.6.11)

本文基於 Linux Kernel 2.6.11 分析進程複製系統調用 do_fork函數的實現過程。

詳細分析了其中的 copy_process()、dup_task_struct()、copy_file()、copy_mm()、copy_thread()等一系列輔助函數的實現過程。

並詳細講解了進程描述符task_struct、小型進程描述符/內核棧 thread_info、內存描述符mm_struct中的主要字段含義。

講解了線性區的概念和線性區對象 vm_area_struct、以及線性區數據結構的相關內容。並簡單描述了寫時拷貝機制的底層實現原理。



進程複製概述

在Linux系統中,進程的產生通常是調用fork系統調用的結果,該系統調用通過複製一個現有的進程來創建一個全新的進程,在調用結束時,在返回點父進程恢復執行,子進程開始執行(執行點就是父進程調用fork()系統調用之後)。

fork函數返回了兩次:一次是在父進程,一次是在子進程。如果在子進程中返回,返回值爲0;在父進程中返回,返回值是子進程的PID

執行進程複製在Linux中實現了三種相關的函數:

  • fork():fork()所創建的子進程複製了父進程的資源(寫時拷貝),包括內存的內容、task_struct內容(2個進程的pid不同)。這裏是資源的複製不是指針的複製。
  • vfork():vfork()系統調用所創建的進程能共享其父進程的內存地址空間。其實vfork創建出來的不是真正意義上的進程,而是一個線程,爲了防止父進程重寫子進程需要的數據,阻塞父進程的執行,直到子進程exit退出或者exec執行了一個新的程序爲止。
  • clone():clone()是Linux爲創建線程設計的,它不僅可以創建進程或者線程,還可以指定創建新的命名空間(namespace)、有選擇的繼承父進程的內存、甚至可以將創建出來的進程變成父進程的兄弟進程等等。

do_fork執行過程綜述

上述三個函數分別對應系統調用 sys_fork()、sys_vfork()、sys_clone(),它們被定義在process.c文件中,在Linux Kernel 2.6.11版本中都是調用do_fork()函數實現的,唯一的不同就是clone_flags參數的不同

asmlinkage int sys_fork(struct pt_regs regs)
{
	return do_fork(SIGCHLD, regs.esp, &regs, 0, NULL, NULL);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
	unsigned long clone_flags;
	unsigned long newsp;
	int __user *parent_tidptr, *child_tidptr;

	clone_flags = regs.ebx;
	newsp = regs.ecx;
	parent_tidptr = (int __user *)regs.edx;
	child_tidptr = (int __user *)regs.edi;
	if (!newsp)
		newsp = regs.esp;
	return do_fork(clone_flags, newsp, &regs, 0, parent_tidptr, child_tidptr);
}

asmlinkage int sys_vfork(struct pt_regs regs)
{
	return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0, NULL, NULL);
}

我們主要分析一下 sys_fork函數,首先來簡單的概述一下do_fork函數的執行過程

  • 創建子進程task_struct結構體
  • 調用alloc_pidmap()獲取空閒的pid分配給子進程
  • 檢查父進程的ptrace字段,其值表明了父進程是否被調試跟蹤,根據條件爲子進程設置相應標誌
  • 調用copy_process()函數來創建進程描述符(PCB)以及子進程執行所需要的所有其他內核數據結構。該函數返回創建好的task_struct描述符的地址。

接下來簡單分析一下copy_process()函數的執行:

  • 在copy_process()函數中具體是調用dup_task_struct爲子進程獲取進程描述符
  • dup_task_struct函數定義了task_struct 結構體指針tsk 和 thread_info結構體指針ti,它們分別通過alloc_task_struct和alloc_thread_info被創建,再將current進程描述符的內容複製到tsk所指向的task_struct結構中,然後把tsk_thread_info置爲ti,將current進程的thread_info內容複製給ti指向的結構中,並將ti_task置爲tsk。最後函數將創建好的task_struct類型的結構體指針tsk返回到copy_process()函數中
  • 接下來便是對剛創建好的進程描述符PCB的一系列初始化工作,需要被清零的字段被清零,需要設置標誌的字段設置相應的標誌。初始化工作結束後,接下來複制一系列資源,例如文件描述符copy_file、信號量copy_signal、進程的虛擬地址空間copy_mm等工作。
  • 最後一步便是通過copy_thread給子進程賦予父進程的現場信息,該函數拷貝父進程內核堆棧寫入子進程內核堆棧,由於do_fork函數中有參數爲pt_regs結構體類型指針 regs,其中保存了父進程執行的現場信息,進程複製結束後,父進程要恢復現場繼續執行,而子進程也是通過父進程的該現場信息開始執行。該函數還要做的工作就是將0存入子進程的eax寄存器中,而do_fork()在從內核態返回後,用戶態會從eax中讀取返回值,因此fork複製進程後子進程的返回值就是0

這樣,do_fork函數執行完畢,完整的子進程就產生了,這時子進程還不在進程可
執行隊列中,不能接受操作系統的調度,但是隨後就會通過wake_up_process§將其加入可執行隊列接受調度,具體的調度策略是父進程分割它的一半時間片給新創建的子進程,這樣做的目的是防止惡意的fork獲取系統更多時間片,造成操作系統進程調度不公平現象的產生,最後父進程也就是當前進程繼續執行,do_fork()將子進程的pid作爲返回值返回。這樣子進程被調度時的返回值和父進程從do_fork()返回時就不一樣了。


以上是對do_fork()函數執行過程的完整概述,我們忽略掉了一些函數具體的實現以及重要結構體的定義,接下來我們對其進行着重分析。


do_fork函數執行過程的詳細分析

函數原型

fork()、vfork()和clone()系統調用的入口點分別是sys_fork()、sys_vfork()和sys_clone()函數,其定義依賴於具體的體系結構,因爲在用戶空間和內核空間之間傳遞參數的方法因體系結構而異。

上述函數的任務是從處理器寄存器中提取由用戶空間提供的信息,調用體系結構無關的do_fork函數,後者負責進程複製

我們接下來主要分析do_fork函數的執行過程首先我們來看一下do_fork函數的原型:

long do_fork(unsigned long clone_flags,
	      unsigned long stack_start,
	      struct pt_regs *regs,
	      unsigned long stack_size,
	      int __user *parent_tidptr,
	      int __user *child_tidptr){}

各參數的含義如下:

clone_flags是一個標誌集合,用來指定控制複製過程的一些屬性最低字節指定了在子進程終止時發送給父進程的信號代碼,通常選擇SIGCHLD信號,其餘的高位字節(三個字節)是各種clone標誌的組合,列舉幾個比較常用的:

  • CLONE_VM 共享進程地址空間
  • CLONE_FS 共享文件系統
  • CLONE_FILES 共享打開的文件
  • CLONE_PTRACE 繼續調試子進程
  • CLONE_VFORK 調用vfork(),所以父進程準備睡眠等待子進程將其喚醒
  • CLONE_SIGHAND 共享信號處理表,阻塞信號表和掛起信號表,選此必須選CLONE_VM
  • CLONE_THREAD 父子進程放入相同的線程組.選此必選CLONE_SIGHAND

start_stack是用戶態棧的起始地址,函數中要把用戶態堆棧指針賦予給子進程的ESP寄存器,調用進程(父進程)總該爲子進程分配新的堆棧

regs指向pt_regs結構體,當系統調用發生,從用戶態切換到內核態時,用來保存寄存器值,並被存放於內核態的棧中

stack_size是用戶態棧的大小。該參數通常是不必要的,設置爲0。

parent_tidptr是父進程在用戶態下pid的地址,僅在CLONE_PARENT_SETTID時有意義。

child_tidptr是子進程在用戶態下pid的地址,僅在CLONE_CHILD_SETTID時有意義。

下面是 do_fork() 的定義:

asmlinkage int sys_fork(struct pt_regs regs)
{
	return do_fork(SIGCHLD, regs.esp, &regs, 0, NULL, NULL);
}

唯一使用的標誌是SIGCHLD這意味着在子進程終止後發送SIGCHLD信號通知父進程。最初,父子進程的棧地址相同(起始地址保存在系統的esp寄存器中)。但如果操作棧地址並寫入數據,則寫時拷貝機制則會爲每個進程創建一個棧副本,便於寫入新數據且父子進程互不影響

如果do_fork成功,則新建進程的PID作爲系統調用的結果返回,否則返回錯誤碼(負值)。


執行過程

一、首先定義了一個task_struct類型的結構體指針

task_struct是Linux內核的一種數據結構,它會被裝載到RAM中並且包含着進程的信息。每個進程都把它的信息放在這個數據結構體中,定義在usr/include/linux/sched.h中,簡單來說,它即爲進程描述符——PCB。 其中存放了進程的主要信息,介紹主要的如下:

  • 進程狀態state:-1爲不可運行,0可以運行,大於0表示停止。
  • 進程標誌flags:PF_FORKNOEXEC表示進程剛創建,沒有執行;PF_SUPERPRIV表示超級用戶特權;PF_DUMPCORE表示異常情況的抓取;PF_SIGNALED表示進程被信號終結;PF_EXITING表示進程開始關閉。
  • ptrace系統調用提供了父進程可以觀察和控制子進程執行的能力,並允許父進程檢查和替換子進程的內核鏡像(包括寄存器)的值。
  • 進程間的親屬關係 :real_parent表示進程的“親生父親”、parent表示進程的父進程、children表示鏈表頭,鏈表中的所有元素都是它的子進程、sibling表示將當前進程插入到兄弟鏈表中、group_leader指向其所在進程組的進程組長(第一個進程)
  • 內存描述符mm_struct (後面介紹):mm _struct用來描述每個進程的虛擬地址空間
  • 進程標識符 pid與線程的組號 tpid、退出碼exit_code
  • vfork_done成員,用於在do_fork()函數中內核調用complete喚醒睡眠進程。
  • thread_info結構體指針(下面分析)

上面是task_struct結構所定義的部分信息,只介紹了一部分,還包括很多,例如進程的調度信息、進程的優先級、信號處理信息等與進程相關的各類信息。


接下來再着重介紹一下thread_info結構

thread_info結構稱之爲小型的進程描述符,是因爲在這個結構中並沒有直接包含與進程相關的字段,而是通過task字段指向具體某個進程描述符。通常這塊內存區域的大小是8KB,也就是兩個頁的大小(有時候也使用一個頁來存儲,即4KB)。

struct thread_info {
struct task_struct *task;  /* 當前進程task_struct指針 */
struct exec_domain *exec_domain;  /* 執行區間 */
unsigned long flags;  /* 底層標誌 */
unsigned long status;  /* 線程同步標誌 */
__u32 cpu;  /* 當前CPU */
int preempt_count;  /* 0 => 可搶佔, <0 => BUG */
mm_segment_t addr_limit; /* 線程地址空間 */
struct restart_block restart_block;
}
  • task是指向task_struct結構的指針。
  • exec_domain用於實現執行區間(execution domain)
  • flags可以保存各種特定於進程的標誌
  • preempt_count實現內核搶佔所需的一個計數器
  • addr_limit指定了進程可以使用的虛擬地址的上限。該限制適用於普通進程,但內核線程可以訪問整個虛擬地址空間,包括只有內核能訪問的部分。
  • restart_block用於實現信號機制。

二、執行alloc_pidmap函數,爲子進程分配空閒PID

分配pid的linux內核實現了位圖算法,其中pid_max被定義爲0x8000即32767,數據結構爲pidmap-array,內核使用頁框來存放位圖,一個頁框包含32768個位,所以32位體系結構中pidmap-array位圖存放在一個單獨的頁中。

默認分配的pid爲上個進程pid+1,若其值大於32767,那麼則從RESERVED_PIDS重新開始,其值爲300,前300個PID是爲系統進程(包括內核線程)保留的,不可以分配。

offset爲pid在頁框中的偏移量,因爲pid可能大於一個頁框所能容納的最大位數,即BITS_PER_PAGE,所以pid要對BITS_PER_PAGE取模。pid/BITS_PER_PAGE求得pid所在頁面在pidmap_array中的下標。

之後通過掃描算法得出最終的pid並返回。


三、檢查父進程的ptrace字段,設置相應標誌位

如果父進程的ptrace字段值不等於0,說明有另外一個進程正在跟蹤父進程,因而,do_fork()函數檢查debugger程序是否自己想跟蹤子進程。在這種情況下,如果子進程不是內核線程(CLONE_UNTRACED標誌被清0),則do_fork()函數設置CLONE_PTRACE標誌。

接下來便是do_fork函數的核心:copy_process函數


四、調用copy_process()函數複製進程描述符,返回新創建的task_struct描述符的地址

do_fork()函數的核心是copy_process()函數,該函數完成了進程創建的絕大部分工作。

我們首先畫出其執行流程:

copy_process()函數創建進程描述符以及子進程執行所需要的其他數據結構其參數與do_fork()相同,外加子進程PID

static task_t *copy_process(unsigned long clone_flags,
				 unsigned long stack_start,
				 struct pt_regs *regs,
				 unsigned long stack_size,
				 int __user *parent_tidptr,
				 int __user *child_tidptr,
				 int pid)

1、函數檢查參數clone_flags所傳遞標誌的一致性。尤其是在下列情況下,函數返回錯誤代號:
CLONE_NEWNS和CLONE_FS標誌都被設置。
CLONE_THREAD標誌被設置,但CLONE_SIGHAND標誌被清0(同一線程組中的輕量級進程必須共享信號)。
CLONE_SIGHAND被設置,但是CLONE_VM沒有設置。(共享信號處理程序的輕量級進程也必須共享內存描述符)。

2、通過調用security_task_create(clone_flags)函數執行所有附加的安全檢查

3、接下來調用dup_task_struct()函數,該函數的主要作用是:爲子進程獲取進程描述符,該函數爲子進程創建一個新的內核棧,複製task_struct結構和thread_info結構,這裏只是對結構完整的複製,所以子進程的進程描述符跟父進程完全一樣

該函數的主要執行流程如下:

  • 定義tsk是指向結構task_struct的指針。
  • 定義ti是指向結構thread_info的指針。
  • 調用prepare_to_copy爲正式的拷貝做一些準備工作。主要是將此一些必要的寄存器的值保存到父進程的thread_info結構中,這些值稍後會被複制到子進程的thread_info結構中。
  • 執行alloc_task_struct宏爲新進程獲取進程描述符,並將描述符保存到局部變量tsk中。alloc_task_struct宏採用kmem_cache_alloc,kmem_cache_alloc調用__cache_alloc函數,用於從專用高速緩存中申請內存
  • 執行alloc_thread_info宏獲取一塊空閒內存區,用來存放新進程的thread_info結構和內核棧。 並將這塊內存區字段的地址存放在局部變量ti中,這塊內存區字段的大小是8KB或者4KB(兩頁或一頁大小)。alloc_thread_info宏採用kmalloc分配內存,獲得普通高速緩存中的對象,由於thread_indo中存儲進程的task_struct描述符和內核棧,大小爲一頁或兩頁,因此kmalloc在內核申請內存需要指定大小,改大小即爲8KB或者4KB。
  • 將current進程描述符的內容複製到tsk所指向的task_struct結構中,然後把tsk->thread_info置爲ti
  • 將current進程的thread_info內容複製給ti指向的結構中,並將ti->task置爲tsk。
  • 最終dup_task_struct()函數返回新創建好的進程描述符指針tsk。

4、返回到copy_process()函數中後,是進程描述符的一系列檢查和初始化的工作。(下面只介紹部分初始化工作)

5、檢查存放在p->signal->rlim[RLIMIT_NPROC].rlim_cur變量中的值是否小於或等於用戶所擁有的進程數。如果是,則返回錯誤碼,除非進程沒有root權限。該函數從每個用戶數據結構user_struct中獲取用戶擁有的進程數。通過進程描述符user字段指針可以找到這個數據結構。

6、user_struct結構中使用計數器(tsk->user->__count)遞增、**用戶所擁有的進程計數器(tsk->user->processes)也加一。

7、檢查系統中的進程數量是否超過max_threads變量的值,所有thread_info描述符和內核棧所佔用的空間不能超過物理內存大小的1/8。

8、把新進程的PID存入新進程描述tsk->pid字段。

9、初始化子進程描述符中的list_head數據結構和自旋鎖,併爲與掛起信號、定時器及時間統計表相關的若干字段賦初值。

10、調用copy系列函數來創建新的數據結構,並把父進程的相應數據結構的值複製到新數據結構中。到底是拷貝還是共享根據clone_flag標誌位決定

我們主要關注兩個函數:

  • copy_file() 複製文件描述符信息
  • copy_mm() 複製進程虛擬地址空間(寫時拷貝機制的實現)

Copy_file()函數:

函數copy_file()有條件地複製文件描述符當CLONE_FILES標誌位爲0時才真正複製,否則就是共享父進程已打開的文件。因爲是在當前進程創建子進程,是從當前進程複製到子進程,因此把當前進程的task_struct結構中的file_struct結構指針作爲oldf。

若參數clone_flags中CLONE_FILES標誌位爲1,就只是通過atomic_inc()將當前進程的file_struct結構中的共享計數加一便結束了,父子進程共享該文件描述符,程序返回。

若參數clone_flags中CLONE_FILES標誌位爲0,那麼就要進行復制了,首先通過kmem_cache_alloc()爲子進程分配一個files_struct數據結構作爲newf,然後從oldf把內容複製到newf


copy_mm()函數:

函數copy_mm()用來建立子進程的所有頁表和有條件的複製當前進程的虛擬地址空間給子進程

我們先來講解一下內存描述符的概念。

與進程地址空間有關的全部信息都包含在一個叫做內存描述符的數據結構中,這個結構的類型爲mm_struct進程描述符task_struct中的mm字段就指向這個結構。

用戶進程所獲得的空間是虛擬內存,是被稱爲線性區的空間,是一堆線性地址空間的使用權。而描述這一框架的數據結構就是內存描述符,線性區對象vm_area_struct。內存描述符在slab分配器中,內存描述符彼此形成一個鏈表

  • mmap就是指向線性區的開始
  • mmap_cache指向最後一個引用的線性區。
  • pgd字段表示全局頁目錄,也就是頁表。

線性區也彼此形成一個鏈表。mmap指向的就是當前內存描述符之下的線性區鏈表頭,每個線性區描述符表示一個線性地址空間

  • vm_start字段包含區間的第一個線性地址
  • vm_end字段包含線性區之後的第一個線性地址
  • vm_next表示進程鏈表中的下一個線性區
  • vm_mm字段指向擁有這個區間進程的mm_struct內存描述符。

線性區的數據結構

進程所擁有的所有線性區是通過一個簡單的鏈表鏈接在一起的出現在鏈表中的線性區是按照內存地址的升序排列的;每兩個線性區可以由未用的內存地址區隔開,每個vm_area_struct的vm_next字段指向鏈表的下一個元素。內核通過進程的內存描述的mmap字段來查找線性區,其中mmap字段指向鏈表中的第一個線性區描述符。

我們採用下圖來表徵它們之間的關係:

接下來我們繼續分析copy_mm()函數:


對mm_struct的複製也只是在clone_flags中CLONE_VM標誌爲0時才真正進行,否則就只是通過已經複製的指針共享父進程的用戶空間

對mm_struct的真正複製不只是侷限於這個數據結構本身了,也包括了更深層次的複製,最重要的就是vm_area_struct數據結構和頁面映射表,這是由dup_mmap()複製的

拷貝頁表的執行過程do_fork->copy_process->copy_mm->dup_mm->dup_mmap->copy_page_range。

dup_mmap既複製了父進程的線性區和它的頁表,也設置了mm的一些屬性

其中的copy_page_range是關鍵所在,這個函數逐層處理頁面目錄和頁面表項,負責頁表的拷貝,寫時拷貝機制就在這裏實現

接下來主要分析一下寫時拷貝機制的實現原理:

若copy_page_range函數中會循環檢查父進程一個頁表中的每一個表項,若發現表項的內容是需要從父進程複製的可寫頁面,那麼本來,應該分配一個空閒的內存頁面,再從父進程的頁面把內容複製過來,併爲之建立映射。

但是這樣操作的代價比較大,並且複製下來的頁面子進程不一定會有寫操作。如果子進程只是讀訪問,則只要父進程從此不再寫這個頁面,就完全可以通過複製指針來共享這個頁面

所以,Linux內核採用了寫時拷貝(copy on write)的技術,先通過複製頁表暫時共享這個頁面,到子進程(或父進程)真的要寫這個頁面時再次分配頁面和複製。

dup_mmap函數,沿着下述路徑將頁表拷貝了一份

copy_page_range->copy_pud_range->
copy_pmd_range->copy_pte_range

由於子進程並沒有拷貝整個內存,所以,當子進程修改內存某地址對應的值的時候,會產生缺頁中斷,page fault ,缺頁異常最終會調用do_page_fault,do_page_fault調用handle_mm_fault,handle_mm_fault是公共代碼,一般所有的缺頁異常均會調用handle_mm_fault。handle_mm_fault最終會調用handle_pte_fault。

上述我們簡單分析了寫時複製的實現,接下來我們回到copy_process()函數中:將copy系列函數執行完畢後, 接下來將調用copy_thread(),用父進程的內核棧中的寄存器保存的現場信息來初始化子進程的內核棧copy_thread把子進程eax寄存器對應字段的值強行置爲0,子進程描述符的thread.esp字段初始化爲子進程內核棧的基地址。這就是fork執行完畢後子進程的返回值是0的原因。


我們接下來看一下copy_thread函數中重要的部分:

childregs = ((struct pt_regs *) 
(THREAD_SIZE + (unsigned long) p->thread_info)) - 1;

上述語句的作用便是使用父進程的內核棧初始化子進程的內核棧,父進程的內核棧中保存着調用fork時的現場信息,因此fork執行完畢,父進程恢復執行,子進程在父進程的執行點開始執行。

THREAD_SIZE大小爲4096或8192(1頁或者兩頁),其值加上p->thread_info剛好到達thread_info結構體的尾端(高地址),再減一,剛好減的是pt_regs的大小,由於內核棧是由高地址向低地址增長,因此剛好將父進程內核棧中保存的現場信息複製給子進程

最後函數會執行最後的收尾工作,初始化子線程的cpu字段、設置進程關係、將進程加入到進程鏈表等工作,最終函數返回創建的task_struct描述符的地址到do_fork函數中。


五、do_fork在copy_process之後的函數的執行過程

copy_process執行生成新進程的實際工作,並根據指定的標誌重用父進程的數據。在子進程生成之後,內核必須執行下列收尾操作:

  • 在 task_struct結構體中,counter字段的值就是進程的運行時間配額這裏將父進程的時間片分成兩半,讓父子進程各持有原值的一半,接下來便是參與操作系統的調度了。
  • 子進程使用wake_up_new_task喚醒。換言之,即將其task_struct添加到操作系統的調度隊列。調度程序也有機會對新啓動的進程給予特別處理,這使得可以實現一種策略以便新進程有較高的機率儘快開始運行,另外也可以防止一再地調用fork浪費CPU時間。
  • 如果子進程在父進程之前開始運行,則可以大大地減少複製內存頁的工作量,尤其是子進程在fork之後發出exec調用的情況下。將進程排到調度隊列中並不意味着該子進程可以立即開始執行,而是操作系統此時可以選擇調度它得以運行。
  • 如果使用vfork機制(內核通過設置的CLONE_VFORK標誌識別),必須啓用子進程的完成機制。子進程的task_struct的vfork_done成員即用於該目的。藉助於wait_for_completion函數,父進程在該變量上進入睡眠狀態,直至子進程退出。在進程終止或用exec啓動新程序時,內核自動調用complete(vfork_done)。這會喚醒所有因該變量睡眠的進程。

通過採用這種方法,內核可以確保使用vfork生成的子進程的父進程會一直處於不活動狀態,直至子進程退出或執行一個新的程序。父進程的臨時睡眠狀態,也確保了兩個進程不會彼此干擾或操作對方的地址空間。

至此,新進程的創建已經完成了,並且已經掛入了可運行進程的隊列接受調度。子進程與父進程在用戶空間中具有相同的返回地址,然後纔會因用戶空間中程序的安排而分開。

現在,我們有了處於可運行狀態的完整的子進程。但是,它還沒有實際運行,調度程序要決定何時把CPU交給這個子進程。在以後的進程切換中,調度程序繼續完善子進程:把子進程描述符thread字段的值裝入幾個CPU寄存器。特別是把thread.esp (即把子進程內核態堆棧的地址)裝入esp寄存器,把函數ret_from_fork ()的地址裝入eip寄存器。這個彙編語言函數調用schedule_tail ()函數,用存放在棧中的值再裝載所有的寄存器,並強迫CPU返回到用戶態。然後,在fork(),vfork()或clone()系統調用結束時,新進程將開始執行。系統調用的返回值放在eax寄存器中:返回給子進程的值是0,返回給父進程的值是子進程的PID


參考:

《Linux內核設計與實現 第三版》
《深入理解Linux內核 第三版》
《Linux內核編程》
《深入Linux內核架構》
《Linux內核源代碼情景分析 上冊》

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