共計 2382 個字符,預(yù)計需要花費 6 分鐘才能閱讀完成。
本篇文章為大家展示了如何分析 Linux 內(nèi)核源碼 do_fork,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。
我們都知道進(jìn)程是 Linux 內(nèi)核中最為重要的一個抽象概念,那么我們平時在 fork 一個進(jìn)程時,該進(jìn)程究竟是怎么產(chǎn)生的呢?
推送會淺談一下在進(jìn)程創(chuàng)建過程中扮演著重要角色的 do_fork 函數(shù)。
內(nèi)核如何來抽象一個進(jìn)程
內(nèi)核通過一個叫做 task_struct 的結(jié)構(gòu)體來抽象一個進(jìn)程,該結(jié)構(gòu)體的定義 (以內(nèi)核 2.6 為例) 在 include/linux.sched.h 中。
截取部分 task_struct 如下:
上述 task_struct 屬性是我節(jié)選出的部分其結(jié)構(gòu)體中的屬性,我們從中可以大致了解到標(biāo)識一個進(jìn)程的屬性大致會有該用以表示該進(jìn)程所處的狀態(tài), 進(jìn)程的標(biāo)志,以及進(jìn)程是否被其他進(jìn)程跟蹤,進(jìn)程鎖的深度,進(jìn)程的優(yōu)先級,進(jìn)程的 pid,進(jìn)程的父母,進(jìn)程的孩子鏈表,進(jìn)程所打開的文件描述符表,進(jìn)程所處的文件系統(tǒng),進(jìn)程的信號。。。。等等一堆我們平時可能遇到的和進(jìn)程相關(guān)的東西。
do_fork 簡單分析
接觸 linuxC 編程的人都知道,創(chuàng)建一個進(jìn)程我們需要調(diào)用 fork 函數(shù),fork 其實又是調(diào)用了 clone 函數(shù)來實現(xiàn)的,而 clone 函數(shù)中最關(guān)鍵的函數(shù)就是 do_fork 函數(shù)。
在分析 do_fork 前我們腦海中可以大致想象一下,進(jìn)程究竟是如何被創(chuàng)建出來的,假如讓你來創(chuàng)建一個進(jìn)程你會咋么做?
我們可以這樣去分析,既然原來的進(jìn)程被抽象成一個 task_struct,那么新進(jìn)程也是一個 task_struct 只不過它里面的一些屬性會不同與原來的 task_struct,那么創(chuàng)建一個新進(jìn)程所要做的工作就是賦值一個與原來進(jìn)程一樣都的 task_struct 結(jié)構(gòu),然后然后將新進(jìn)程的 task_struct 不同于原來 task_struct 的屬性進(jìn)行修改即可。
do_fork 定義在 kernel/fork.c 文件中。
在分析該函數(shù)之前我們先來分析一下它的函數(shù)的各個參數(shù)。
參數(shù)如下:
1.clone_flags: 該參數(shù)是此函數(shù)中最重要的一個參數(shù),該值中的每個位都代表對子進(jìn)程 task_struct 中的每種屬性的設(shè)置;
2.stack_start: 子進(jìn)程用戶態(tài)堆棧的開始地址;
3.regs: 當(dāng)系統(tǒng)發(fā)生系統(tǒng)調(diào)用時,需從用戶態(tài)切換到內(nèi)核態(tài),此結(jié)構(gòu)體用來保存此時用戶態(tài)進(jìn)程中的通用寄存器中的值,并被存放在內(nèi)核態(tài)堆棧中;
4.stack_size: 目前未被使用,通常設(shè)為 0;
5.parent_tidptr: 父進(jìn)程在用戶態(tài)下 pid 的地址;
6.child_tidptr: 子進(jìn)程在用戶態(tài)下 pid 的地址;
其中 clone_flags 的標(biāo)志位宏定義如下:
舉個簡單的例子當(dāng)我們的參數(shù)中設(shè)置了 CLONE_VM 這個宏,那么就以為這我們新創(chuàng)建的進(jìn)程和其父進(jìn)程要共享 VM,當(dāng)我們設(shè)置了 CLONE_FILES 時意味這父子進(jìn)程之間共享打開的文件描述符。
do_fork 開始執(zhí)行后首先做的就是為子進(jìn)程定義一個新的 task_struct 指針:
struct task_struct *p;
在下來會檢查一些 clone_flags 所不允許的位組合,例如:
if (clone_flags CLONE_NEWUSER) { if (clone_flags CLONE_THREAD) return -EINVAL; }
上述中不允許同時既設(shè)置了 CLONE_NEWUSER 標(biāo)志,還設(shè)置 CLONE_THREAD 標(biāo)志,這樣就會產(chǎn)生錯誤。
類似上面當(dāng)一系列的安全檢查完畢之后,copy_process 函數(shù)就登場了,copy_process 函數(shù)工作流程具體如下:
1)調(diào)用 dup_task_struct 函數(shù)為新的進(jìn)程創(chuàng)建一個內(nèi)核棧,thread_info 結(jié)構(gòu)和 task_struct 等,當(dāng)然此時的值都是和父進(jìn)程完全一樣的
dup_task_struct 函數(shù)定義如下:
2)檢查并確保新創(chuàng)建該子進(jìn)程后,當(dāng)前用戶所擁有的進(jìn)程數(shù)沒有超出給它分配的資源限制,代碼如下:
3)子進(jìn)程著手使自己與父進(jìn)程區(qū)別開來,從父進(jìn)程那繼承過來的許多屬性都要被清 0 或設(shè)置一個初始值,但 task_struct 中的大多數(shù)數(shù)據(jù)還是未被修改,部分代碼如下:
4)給子進(jìn)程分配一個 CPU,代碼如下:
sched_fork(p, clone_flags);
5) 接著就是子進(jìn)程拷貝父進(jìn)程的一些資源,具體如下,調(diào)用 copy_files 函數(shù)拷貝父進(jìn)程打開的文件描述符:
調(diào)用 copy_fs 繼承父進(jìn)程所屬的文件系統(tǒng)。
調(diào)用 copy_signal 函數(shù)拷貝并設(shè)置新的 signal_struct,signal_struct 包含了大量的進(jìn)程運(yùn)行的信息,調(diào)用 copy_mm 函數(shù)處理與新進(jìn)程的內(nèi)存問題。
調(diào)用 copy_io 函數(shù)拷貝父進(jìn)程的 I / O 情況:
還有調(diào)用 copy_namespaces 和 copy_thread 等,這里就不在贅述。
6)調(diào)用 alloc_pid 為新進(jìn)程分配一個 pid。
pid = alloc_pid(p- nsproxy- pid_ns);
7)copy_process 做一些收尾工作,并返回新進(jìn)程的 task_struct 指針,此時再次回到了 do_fork,新創(chuàng)建的子進(jìn)程被喚醒,并讓其先投入運(yùn)行。
總結(jié)
關(guān)于進(jìn)程創(chuàng)建的源碼理解,我感覺主要抓住倆點即可。*** 進(jìn)程被內(nèi)核抽象成了啥? 它的數(shù)據(jù)結(jié)構(gòu)是咋樣的 (task_struct) 這點我們必須有所認(rèn)識,第二創(chuàng)建進(jìn)程最主要的其實就是拷貝父進(jìn)程的 task_struct 里的屬性,但是關(guān)鍵點是拷貝哪些,哪些又是子進(jìn)程和父進(jìn)程所不同的,很簡單我們只需要把握住進(jìn)程創(chuàng)建函數(shù)里的 clone_flags 參數(shù)就可以知道怎么拷貝了。
上述內(nèi)容就是如何分析 Linux 內(nèi)核源碼 do_fork,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注丸趣 TV 行業(yè)資訊頻道。