在线不卡日本ⅴ一区v二区_精品一区二区中文字幕_天堂v在线视频_亚洲五月天婷婷中文网站

  • <menu id="lky3g"></menu>
  • <style id="lky3g"></style>
    <pre id="lky3g"><tt id="lky3g"></tt></pre>

    一個(gè)新進(jìn)程的內(nèi)核之旅

    一個(gè)新進(jìn)程的內(nèi)核之旅

    一、背景

    我們常在Linux平臺(tái)bash環(huán)境下執(zhí)行一條cmd,如看下當(dāng)前文件有哪些”ls -l”。這條cmd會(huì)fork一個(gè)新的進(jìn)程,然后完成ls可執(zhí)行程序的加載和執(zhí)行。對(duì)于用戶(hù)而言,看上去仿佛就應(yīng)該這樣,簡(jiǎn)單快速。而對(duì)于這個(gè)新進(jìn)程,在內(nèi)核中經(jīng)歷了什么,內(nèi)核表示自己默默扛下所有。本文嘗試探索內(nèi)核所做的工作,開(kāi)始之前提出幾個(gè)問(wèn)題:1 fork新進(jìn)程的返回值為何是0?2 fork父進(jìn)程和新進(jìn)程如何執(zhí)行同一份代碼?3 fork新進(jìn)程何時(shí)被加入到調(diào)度器隊(duì)列?接下來(lái),將開(kāi)始整個(gè)旅行啦!以下研究基于開(kāi)源代碼linux kernel-5.10/Android kernel-5.10,主要以x86架構(gòu)為目標(biāo)。源碼可從以下鏈接獲取:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/?h=linux-5.10.yhttps://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/stable-review/https://android.googlesource.com

    二、第1站:用戶(hù)態(tài)觸發(fā)

    在用戶(hù)態(tài)調(diào)用fork,生成一個(gè)新進(jìn)程(子進(jìn)程),如圖1。成功后,子進(jìn)程的pid = 0,子進(jìn)程就可以為所欲為了,當(dāng)然對(duì)于bash而言,主要還是執(zhí)行cmd,如ls -l。

    圖1.fork程序

    下圖就比較清晰的說(shuō)明fork前父進(jìn)程和子感覺(jué)像是運(yùn)行一樣的進(jìn)程,判斷pid之后,父子開(kāi)始分道揚(yáng)鑣,至于為何pid不同,需要從內(nèi)核層面找到原因。

    從用戶(hù)態(tài)的匯編代碼可以知道:父、子進(jìn)程在fork后會(huì)判斷寄存器eax (x86架構(gòu))的返回值,即pid。

    圖2.fork子進(jìn)程

    三、第2站:內(nèi)核fork

    從用戶(hù)空間調(diào)用的fork通過(guò)中斷調(diào)用到內(nèi)核sys_fork,繼續(xù)調(diào)用到kernel_clone,該函數(shù)注意看返回的是pid_t,即進(jìn)程pid。

    圖3.內(nèi)核fork調(diào)用

    截取kernel_clone關(guān)鍵操作,copy_process,wake_up_new_task,那就進(jìn)入下一站分析。

    圖4.內(nèi)核kernel_clone調(diào)用

    更多l(xiāng)inux內(nèi)核視頻教程文檔資料免費(fèi)領(lǐng)取后臺(tái)私信【內(nèi)核】自行獲取.

    Linux內(nèi)核源碼/內(nèi)存調(diào)優(yōu)/文件系統(tǒng)/進(jìn)程管理/設(shè)備驅(qū)動(dòng)/網(wǎng)絡(luò)協(xié)議棧-學(xué)習(xí)視頻教程-騰訊課堂

    四、第3站:copy_process進(jìn)程信息拷貝操作

    這一站就是fork的核心操作了,一步步探索其中的奧秘。從圖5中的注釋看到,copy_process函數(shù)僅做進(jìn)程所擁有的各種資源復(fù)制,復(fù)制完成后,新進(jìn)程并沒(méi)有啟動(dòng)。

    圖5.內(nèi)核copy_process函數(shù)

    首先開(kāi)始一系列的flag檢查,其中可以關(guān)注下這個(gè)flag:SIGNAL_UNKILLABLE,在Android中init進(jìn)程設(shè)置該flag,即新進(jìn)程不可以作為init進(jìn)程的兄弟進(jìn)程,只能作為init進(jìn)程的子進(jìn)程,你想到了什么呢?

    圖6.SIGNAL_UNKILLABLE標(biāo)志

    1. 復(fù)制task_struct關(guān)鍵信息

    copy_process開(kāi)始復(fù)制第一個(gè)重要的資源:task_struct。

    圖7.內(nèi)核dup_task_struct

    這個(gè)結(jié)構(gòu)體表示什么?還得從操作系統(tǒng)說(shuō)起。

    每個(gè)進(jìn)程在內(nèi)核中都有一個(gè)進(jìn)程控制塊(PCB)來(lái)維護(hù)進(jìn)程相關(guān)的信息,Linux內(nèi)核的進(jìn)程控制塊是task_struct結(jié)構(gòu)體。

    這個(gè)結(jié)構(gòu)體主要是用于內(nèi)核管理任務(wù),包括進(jìn)程狀態(tài)、進(jìn)程標(biāo)識(shí)符、進(jìn)程親屬關(guān)系、ptrace系統(tǒng)調(diào)用、進(jìn)程高度、進(jìn)程地址空間、進(jìn)程返回碼判斷標(biāo)志、時(shí)間統(tǒng)計(jì)、信號(hào)處理、內(nèi)存、文件描述符等。

    以下列舉幾個(gè)說(shuō)明:

    • thread_info: 內(nèi)核線(xiàn)程信息
    • pid:進(jìn)程標(biāo)識(shí)符
    • tgid:進(jìn)程標(biāo)識(shí)符

    【注】在Linux系統(tǒng)中,一個(gè)線(xiàn)程組(thread group)中的所有線(xiàn)程使用相同的PID,主線(xiàn)程PID存放在tgid成員中。getpid系統(tǒng)調(diào)用返回的是當(dāng)前進(jìn)程的tgid值而不是pid值。

    • policy:進(jìn)程調(diào)度策略
    • state: 任務(wù)狀態(tài)
    • 。。。

    繼續(xù)回到dup_task_struct,主要工作有:

    1分配task_struct內(nèi)存2分配thread_info內(nèi)存3復(fù)制父進(jìn)程的task_struct信息4設(shè)置線(xiàn)程堆棧關(guān)于thread_info,每當(dāng)進(jìn)程從用戶(hù)態(tài)進(jìn)入內(nèi)核態(tài)后都要使用棧,這個(gè)棧叫做進(jìn)程的內(nèi)核棧。當(dāng)進(jìn)程已進(jìn)入內(nèi)核棧,CPU就自動(dòng)設(shè)置該進(jìn)程的內(nèi)核棧,這個(gè)棧位于內(nèi)核的數(shù)據(jù)段上。為了節(jié)省空間,linux把內(nèi)核棧和一個(gè)緊挨近PCB的小數(shù)據(jù)結(jié)構(gòu)thread_info放在一起,占用8KB的內(nèi)存空間。

    在dup_task_struct中會(huì)調(diào)用alloc_thread_info分配兩個(gè)頁(yè)(8KB)的空閑內(nèi)存(其內(nèi)部調(diào)用的是一個(gè)__get_free_psages函數(shù))。

    有了task_struct結(jié)構(gòu)體,后邊copy_process繼續(xù)復(fù)制其他資源到該結(jié)構(gòu)體中。

    2. 調(diào)度相關(guān)初始化

    task_struct結(jié)構(gòu)初始化之后,開(kāi)始進(jìn)行調(diào)度相關(guān)的初始化,這里僅設(shè)置任務(wù)狀態(tài)和優(yōu)先級(jí),如圖8。

    圖8.內(nèi)核sched_fork

    state = TASK_NEW這點(diǎn)表示,在調(diào)度器與進(jìn)程創(chuàng)建的第二個(gè)邏輯交互時(shí)機(jī),內(nèi)核會(huì)調(diào)用調(diào)度器類(lèi)的task_new函數(shù),將新進(jìn)程加入到相應(yīng)的類(lèi)的就緒隊(duì)列。該函數(shù)主要工作:1初始化跟調(diào)度相關(guān)的值,比如調(diào)度實(shí)體,運(yùn)行時(shí)間等2根據(jù)父進(jìn)程的運(yùn)行優(yōu)先級(jí)設(shè)置設(shè)置進(jìn)程的優(yōu)先級(jí)3設(shè)置調(diào)度類(lèi)rt_sched_class(實(shí)時(shí)進(jìn)程)或者fair_sched_class(普通進(jìn)程)

    3. 復(fù)制進(jìn)程主要數(shù)據(jù)

    接著繼續(xù)復(fù)制files、fs、signal、mm、io等數(shù)據(jù),需要關(guān)注的有:

    • copy_signal:如果創(chuàng)建的是進(jìn)程,則會(huì)將父進(jìn)程的信號(hào)屏蔽并安排復(fù)制到子進(jìn)程中。如果創(chuàng)建的是線(xiàn)程,直接返回0。

    copy_mm:如果是進(jìn)程,則將父進(jìn)程的mm_struct結(jié)構(gòu)復(fù)制到子進(jìn)程中,然后修改當(dāng)中屬于子進(jìn)程有別于父進(jìn)程的信息(如頁(yè)目錄)。如果是線(xiàn)程,則將子線(xiàn)程的mm指針和active_mm指針都指向父進(jìn)程的mm指針?biāo)附Y(jié)構(gòu)。

    圖9.內(nèi)核復(fù)制進(jìn)程數(shù)據(jù)

    4. 復(fù)制線(xiàn)程copy_thread

    copy_thread函數(shù)將子進(jìn)程的ip寄存器值設(shè)置為ret_from_fork()的地址,childregsax,即新進(jìn)程的返回值設(shè)置為0,即當(dāng)子進(jìn)程首次被調(diào)用就立即執(zhí)行系統(tǒng)調(diào)用clone返回0。

    當(dāng)子進(jìn)程被調(diào)度到時(shí),子進(jìn)程處于內(nèi)核態(tài)。經(jīng)過(guò)switch_to函數(shù)進(jìn)程切換,ip設(shè)置為thread.ip,sp設(shè)置為thread.sp。ip是ret_from_fork,sp是childregs。

    對(duì)于子進(jìn)程而言:好像是內(nèi)核態(tài)正要執(zhí)行到ret_from_fork時(shí),由于中斷等原因被搶占,所以在fork執(zhí)行完畢后,在隨后進(jìn)程調(diào)度中,新進(jìn)程可能被調(diào)度到。執(zhí)行完ret_from_fork中的一些代碼,返回到用戶(hù)態(tài)后,開(kāi)始執(zhí)行fork之后的代碼。

    圖10.內(nèi)核copy_thread函數(shù)

    這里的設(shè)置非常巧妙,對(duì)子進(jìn)程的運(yùn)行現(xiàn)場(chǎng)進(jìn)行偽裝,形成父進(jìn)程fork后,子進(jìn)程執(zhí)行的分叉點(diǎn)。

    ret_from_fork函數(shù)主要完成新進(jìn)程寄存器的恢復(fù),包括SS、CS段寄存器等,然后返回用戶(hù)模式。

    圖11.x86函數(shù)ret_from_fork函數(shù)

    5. 設(shè)置全局PID和收尾工作

    將子進(jìn)程的PID設(shè)置為在全局namespace中分配的PID,在不同namespace中進(jìn)程的PID不同,而p->pid保存的是全局的namespace中所分配的PID。

    接著進(jìn)行TGID、PGID、SID的設(shè)置,并加入到鏈表中。

    圖12.設(shè)置全局pid

    然后進(jìn)行proc文件系統(tǒng)、cgroup等的關(guān)聯(lián),最后返回task_struct結(jié)構(gòu)體指針。

    圖13.關(guān)聯(lián)proc文件系統(tǒng)

    五、第4站:新進(jìn)程調(diào)度運(yùn)行

    經(jīng)過(guò)漫長(zhǎng)的復(fù)制操作,最后回到第2站內(nèi)核的fork過(guò)程,在kernel_clone中,copy_process結(jié)束后,調(diào)用wake_up_new_task函數(shù)。

    這表明新進(jìn)程調(diào)度相關(guān)的初始化已經(jīng)完成,但是還沒(méi)有被調(diào)度器加入到隊(duì)列中,是不能被調(diào)度執(zhí)行的。因此,該函數(shù)主要是將新進(jìn)程加入到隊(duì)列中。

    圖14.內(nèi)核調(diào)用wake_up_new_task

    在wake_up_new_task函數(shù)中,將進(jìn)程加入到運(yùn)行隊(duì)列的函數(shù)為activate_task,而activate_task函數(shù)最后會(huì)調(diào)用到新進(jìn)程調(diào)度類(lèi)中的enqueue_task指針?biāo)负瘮?shù)。

    該函數(shù)會(huì)調(diào)用CFS調(diào)度類(lèi)的enqueue_task_fair(普通進(jìn)程),接著會(huì)調(diào)用enqueue_entity函數(shù),完成調(diào)度中運(yùn)行時(shí)間、權(quán)重等設(shè)置,最后加入CFS紅黑樹(shù)進(jìn)程運(yùn)行隊(duì)列中,等待被調(diào)度執(zhí)行。

    圖15.內(nèi)核wake_up_new_task函數(shù)

    六、結(jié)束語(yǔ)

    到此,fork一個(gè)新進(jìn)程內(nèi)核之旅第1部分結(jié)束了,其中分析了主要的流程,實(shí)際上還有大量的細(xì)節(jié)沒(méi)有分析,有興趣的讀者可以進(jìn)一步深入研究。

    再回顧下整個(gè)過(guò)程,如下圖part1部分,最后到新進(jìn)程開(kāi)始運(yùn)行。同時(shí)預(yù)告下part2部分內(nèi)容,將分析load程序?qū)嶓w的ELF和so,尤其是so的加載非常的有趣和精彩。Linux內(nèi)核博大精深,需要不斷去探索,可謂是“路曼曼其修遠(yuǎn),吾將上下而求索”。

    圖1.6.進(jìn)程之旅系列

    鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場(chǎng),版權(quán)歸原作者所有,如有侵權(quán)請(qǐng)聯(lián)系管理員(admin#wlmqw.com)刪除。
    上一篇 2022年7月11日 09:35
    下一篇 2022年7月11日 09:35

    相關(guān)推薦

    聯(lián)系我們

    聯(lián)系郵箱:admin#wlmqw.com
    工作時(shí)間:周一至周五,10:30-18:30,節(jié)假日休息