Linux進(jìn)和創(chuàng)建——寫時拷貝機(jī)制
所有的進(jìn)程都是PID為1的init進(jìn)程的后代。內(nèi)核在系統(tǒng)啟動的最后階段啟動init進(jìn)程。該進(jìn)程會讀取系統(tǒng)的初始化腳本(initscript)并執(zhí)行其他相關(guān)的程序,最終完成整個系統(tǒng)的啟動過程。
內(nèi)核把進(jìn)程(在linux中進(jìn)程又稱任務(wù))存放在任務(wù)隊列中。任務(wù)隊列是雙向循環(huán)鏈表。鏈表中的每一項數(shù)據(jù)的類型都是task_struct,task_struct就是所謂的進(jìn)程描述符的結(jié)構(gòu)。進(jìn)程描述符中包含一個進(jìn)程的所有信息。進(jìn)程描述符所包含的數(shù)據(jù)能完整地描述一個正在執(zhí)行的程序(程序本身并不是進(jìn)程,進(jìn)程是處于執(zhí)行期的程序以及相關(guān)的資源的總稱)如它打開的文件,進(jìn)程的地址空間,掛起的信號,進(jìn)程的狀態(tài)等等。
可執(zhí)行程序代碼是進(jìn)程的重要組成部分。這些代碼從一個可執(zhí)行文件載入到進(jìn)程的地址空間執(zhí)行。一般程序在用戶空間執(zhí)行。當(dāng)一個程序執(zhí)行了系統(tǒng)調(diào)用或者觸發(fā)了某個異常,它就會陷入內(nèi)核空間,此時,內(nèi)核“代表進(jìn)程執(zhí)行”并處于進(jìn)程上下文中。在此期間除非有更高優(yōu)先級的進(jìn)程需要執(zhí)行并由調(diào)度器做出了相應(yīng)調(diào)整,否則在內(nèi)核退出時,程序恢復(fù),在用戶空間會繼續(xù)執(zhí)行。系統(tǒng)調(diào)用和異常處理程序是對內(nèi)核明確定義的接口,進(jìn)程只有通過這些接口才能陷入內(nèi)核執(zhí)行。對內(nèi)核的所有訪問都必須通過這些接口。
系統(tǒng)中的每個進(jìn)程必有一個父進(jìn)程,相應(yīng)的,每個進(jìn)程也可以擁有零個或多個子進(jìn)程。擁有同一個父進(jìn)程的所有進(jìn)程稱為兄弟。進(jìn)程間的關(guān)系存放在進(jìn)程描述符中。每個task_struct(進(jìn)程描述符結(jié)構(gòu))都包含一個指向其父進(jìn)程task_struct的名為parent的指針,還包含一個稱為children的子進(jìn)程鏈表。
init進(jìn)程的進(jìn)程描述符是作為init_task靜態(tài)分配的。
許多操作系統(tǒng)在創(chuàng)建新進(jìn)程時,都會首先在新的地址空間里創(chuàng)建進(jìn)程,讀入可執(zhí)行文件,最后開始執(zhí)行。而linux則不是,它將這兩個步驟分解到兩個單獨(dú)的函數(shù)中去執(zhí)行:fork()和exec(),首先fork()通過拷貝當(dāng)前進(jìn)程創(chuàng)建一個子進(jìn)程,子進(jìn)程與父進(jìn)程的區(qū)別僅僅在于PID(每個進(jìn)程唯一),PPID(父進(jìn)程的進(jìn)程號,子進(jìn)程將其設(shè)置為被拷貝進(jìn)程的PID),和某些資源和統(tǒng)計量。exec()函數(shù)負(fù)責(zé)讀取可執(zhí)行文件并將其載入地址空間開始運(yùn)行。
創(chuàng)建子進(jìn)程是通過fork()方法完成的,而fork()是使用寫時拷貝(copy-on-write)機(jī)制。這是一種可以推遲甚至免除拷貝數(shù)據(jù)的技術(shù)。當(dāng)執(zhí)行fork()方法創(chuàng)建進(jìn)程時,內(nèi)核并不復(fù)制整個進(jìn)程地址空間,而是讓父進(jìn)程和子進(jìn)程共享同一個拷貝。
只有在需要寫入的時候,數(shù)據(jù)才會被復(fù)制,從而使各個進(jìn)程擁有各自的拷貝。即是說,資源的復(fù)制只有在需要寫入時才進(jìn)行,在此之前,都是以只讀方式共享。
如果fork()之后立即調(diào)用exec()那么頁就根據(jù)無須復(fù)制了,這種情況下,頁根本不會被寫入。
fork()的實際開銷就是復(fù)制父進(jìn)程的頁表和給子進(jìn)程創(chuàng)建唯一的進(jìn)程描述符。
Linux通過clone()系統(tǒng)調(diào)用實現(xiàn)fork()。clone系統(tǒng)調(diào)用會通過一系列的參數(shù)標(biāo)志來指明父、子進(jìn)程需要共享的資源。clone()會去調(diào)用do_fork()。
do_fork會完成創(chuàng)建中的大部分工作,它會調(diào)用copy_process()函數(shù),然后讓進(jìn)程開始運(yùn)行。copy_process()函數(shù)完成以下工作:
(1)首先調(diào)用dup_task_struct為新進(jìn)程創(chuàng)建一個內(nèi)核棧、thread_info結(jié)構(gòu)和task_struct(進(jìn)程描述符的結(jié)構(gòu)),這些值與當(dāng)前進(jìn)程的值相同。此時的子進(jìn)程與父進(jìn)程的描述符是完全相同的。
(2)檢查并確保新創(chuàng)建這個子進(jìn)程后,當(dāng)前用戶所擁有的進(jìn)程數(shù)目沒有超出給它分配的資源的限制。
(3)子進(jìn)程開始著手使自己與父進(jìn)程區(qū)別開來。子進(jìn)程描述符內(nèi)的許多成員都被清0或設(shè)為初始值。那些不是繼承而來的描述符成員,主要是統(tǒng)計信息。此時task_struct中仍然有很多數(shù)據(jù)都依然未被修改。
(4)將子進(jìn)程的狀態(tài)設(shè)置為TASK_UNINTERRUPTIBLE以保證它不會投入運(yùn)行。
(5)copy_process()調(diào)用copy_flags()以更新task_struct的flags 成員,表明進(jìn)程是否有超級用戶權(quán)限的PF_SUPERPRIV標(biāo)志被清0,表明進(jìn)程沒有調(diào)用exec()函數(shù)的PF_FORKNOEXEC標(biāo)志被設(shè)置。
(6)調(diào)用alloc_pid為新進(jìn)程分配一個有效的PID。
(7)根據(jù)傳遞給clone()的參數(shù)標(biāo)志,copy_process()拷貝或共享打開的文件、文件系統(tǒng)信息、信號處理函數(shù)、進(jìn)程地址空間和命名空間等。在一般情況下,這些資源會被給定進(jìn)程的所有線程共享;否則這些資源對每個進(jìn)程是不同的,因此被拷貝到這里。
(8)最后,copy_process()做收尾工作并返回一個指向子進(jìn)程的指針。再回到do_fork()函數(shù),如果copy_process()函數(shù)成功返回,新創(chuàng)建的子進(jìn)程會被喚醒并讓其投入運(yùn)行。
一般子進(jìn)程都會馬上調(diào)用exec()函數(shù),這樣可以避免寫時拷貝的額外開銷。
謝謝閱讀。
Linux 任務(wù)調(diào)度
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。