最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

從源碼角度看Linux線程是怎么創(chuàng)建出來(lái)的

2023-06-07 15:36 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿

這篇文章來(lái)學(xué)習(xí)一下線程的創(chuàng)建過(guò)程。

線程不是一個(gè)完全由內(nèi)核實(shí)現(xiàn)的機(jī)制,它是由內(nèi)核態(tài)和用戶態(tài)合作完成的。

用戶態(tài)創(chuàng)建線程

pthread_create 不是一個(gè)系統(tǒng)調(diào)用,是 glibc 庫(kù)的一個(gè)函數(shù),位于 nptl/pthread_create.c 中:

那這個(gè)函數(shù)到底做了什么呢?

  • 首先是線程的屬性參數(shù),如果沒(méi)有傳入線程屬性,就取默認(rèn)值:

接下來(lái),就像在內(nèi)核里一樣,每一個(gè)進(jìn)程或者線程都有一個(gè) task_struct 結(jié)構(gòu),在用戶態(tài)也有一個(gè)用于維護(hù)線程的結(jié)構(gòu),就是這個(gè) pthread 結(jié)構(gòu):

凡是涉及到函數(shù)的調(diào)用,都要使用自己的棧。每個(gè)線程都有自己的棧。因此需要?jiǎng)?chuàng)建線程棧:

  • ALLOCATE_STACK 是一個(gè)宏,我們找到它的定義之后,發(fā)現(xiàn)它其實(shí)是一個(gè)函數(shù):

    • 如果你在線程屬性里面設(shè)置過(guò)棧的大小,需要你把設(shè)置的值拿出來(lái)

    • 為了防止棧的訪問(wèn)越界,在棧的末尾會(huì)有一塊空間 guardsize,一旦訪問(wèn)到這里就錯(cuò)誤了

    • 其實(shí)線程棧是在進(jìn)程的堆里面創(chuàng)建的。如果一個(gè)進(jìn)程不斷的創(chuàng)建和刪除線程,我們不可能不斷地去申請(qǐng)和清除線程棧使用的內(nèi)存塊,這樣就需要有一個(gè)緩存。get_cached_stack 就是根據(jù)計(jì)算出來(lái)的 size 的大小,看看已經(jīng)有的緩存中,有沒(méi)有已經(jīng)能夠滿足條件的

    • 如果緩存里面沒(méi)有,就需要調(diào)用??__mmap 創(chuàng)建一塊新的(如果要在堆里面 malloc 一塊內(nèi)存,比較大的話,就用 __mmap)

    • 線程棧也是自頂而下生長(zhǎng)的,在棧底的位置,其實(shí)是地址的最高位

    • 每個(gè)線程都要有一個(gè) pthread 結(jié)構(gòu),這個(gè)結(jié)構(gòu)也是放在棧的空間里面的

    • 計(jì)算出 guard 內(nèi)存的位置,調(diào)用 setup_stack_prot 設(shè)置這塊內(nèi)存的是受保護(hù)的

    • 接下來(lái),開(kāi)始填充 pthread 這個(gè)結(jié)構(gòu)里面的成員變量 tackblock、stackblock_size、guardsize、specific。這里的 specific 是用于存放 Thread Specific Data 的,也即屬于線程的全局變量

    • 將這個(gè)線程棧放到 stack_used 鏈表中。

    • 其實(shí)管理線程??偣灿袃蓚€(gè)鏈表,一個(gè)是 stack_used,也就是這個(gè)棧正在被使用;另一個(gè)是 stack_cache,一旦線程結(jié)束,先緩存起來(lái),不釋放,等有其他的線程創(chuàng)建的時(shí)候,給其他的線程用

搞定了用戶態(tài)棧的問(wèn)題,其實(shí)用戶態(tài)的事情基本搞定了一半。


【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書(shū)、實(shí)戰(zhàn)項(xiàng)目及代碼)? ??



內(nèi)核態(tài)創(chuàng)建任務(wù)

接下來(lái),我們接著 pthread_create ?看。其實(shí)有了用戶態(tài)的棧,接著需要解決的就是用戶態(tài)的程序從哪里開(kāi)始運(yùn)行的問(wèn)題

start_routine 就是我們給線程的函數(shù),start_routine 、start_routine 的參數(shù) arg,以及調(diào)度策略都要賦值給 pthread。

接下來(lái) __nptl_nthreads 加 1,說(shuō)明又多了一個(gè)線程。

真正創(chuàng)建線程的是調(diào)用 create_thread 函數(shù),這個(gè)函數(shù)定義如下:

這里面有很長(zhǎng)的 clone_flags 需要特別關(guān)注一下。

然后就是 ARCH_CLONE ,其實(shí)就是調(diào)用 __clone(如果對(duì)于匯編不太熟悉也沒(méi)關(guān)系,重點(diǎn)看注釋?zhuān)?/p>

我們能看到最后調(diào)用了 syscall,這一點(diǎn) clone 和其他系統(tǒng)調(diào)用幾乎一模一樣,但是也有一些不一樣的地方:

  • 如果在進(jìn)程的主線程里面調(diào)用了其他系統(tǒng)調(diào)用,當(dāng)前用戶態(tài)的棧是指向整個(gè)進(jìn)程的棧,棧頂指針也是指向進(jìn)程的棧,指令指針也是指向進(jìn)程的主線程的代碼。此時(shí)此刻執(zhí)行到這里,調(diào)用 clone 的時(shí)候,用戶態(tài)的棧、棧頂指針、指令指針和其他系統(tǒng)調(diào)用一樣,都是指向主線程的。

  • 但是對(duì)于線程來(lái)說(shuō),這些都要變。因?yàn)槲覀兿M?dāng) clone 這個(gè)系統(tǒng)調(diào)用成功的時(shí)候,除了內(nèi)核里面有這個(gè)線程對(duì)應(yīng)的task_struct,當(dāng)系統(tǒng)調(diào)用1返回到用戶態(tài)的時(shí)候,用戶態(tài)的棧應(yīng)該是線程的棧,棧頂指針應(yīng)該執(zhí)行線程的棧,指令指針應(yīng)該指向線程將要執(zhí)行的那個(gè)函數(shù)

  • 所以這些都要我們自己做,將線程要執(zhí)行的函數(shù)的參數(shù)和指令的位置都?jí)旱綏@锩?,?dāng)從內(nèi)核返回,從棧里面彈出來(lái)的時(shí)候,就從這個(gè)函數(shù)開(kāi)始,帶著這些參數(shù)執(zhí)行下去。

接下來(lái)我們就要進(jìn)入內(nèi)核了。內(nèi)核里面對(duì)于 clone 系統(tǒng)調(diào)用的定義是這樣的:

可以看到,調(diào)用了 _do_fork,先前我們已經(jīng)看過(guò)了一遍它的主要邏輯,現(xiàn)在我們重點(diǎn)關(guān)注幾個(gè)區(qū)別。

第一個(gè)是上面復(fù)雜的標(biāo)識(shí)位設(shè)定,我們來(lái)看都影響了什么。

  • 對(duì)于 copy_files,原來(lái)是調(diào)用 dup_fd 復(fù)制一個(gè) files_struct 的,現(xiàn)在因?yàn)?CLONE_FILES 標(biāo)識(shí)位變成將原來(lái)的files_struct 引用計(jì)數(shù)加 1

對(duì)于 copy_fs,原來(lái)是調(diào)用 copy_fs_struct 復(fù)制一個(gè) fs_struct,現(xiàn)在因?yàn)?CLONE_FS 標(biāo)識(shí)位,變成將原來(lái)的 fs_struct 的用戶數(shù)加 1

對(duì)于 copy_sighand,原來(lái)是創(chuàng)建一個(gè)新的 sighand_struct,現(xiàn)在因?yàn)?CLONE_SIGHAND 標(biāo)識(shí)位變成將原來(lái)的sighand_struct 引用計(jì)數(shù)加 1

對(duì)于 copy_signal,原來(lái)是創(chuàng)建一個(gè)新的 signal_struct,現(xiàn)在因?yàn)?CLONE_THREAD 直接返回了

對(duì)于 copy_mm,原來(lái)是調(diào)用 dup_mm 復(fù)制一個(gè) mm_struct,現(xiàn)在因?yàn)?CLONE_VM 標(biāo)識(shí)位而直接指向了原來(lái)的mm_struct

第二個(gè)是對(duì)于親緣關(guān)系的影響,畢竟我們要識(shí)別多個(gè)線程是不是屬于一個(gè)進(jìn)程。

從上面可以看出,使用了 CLONE_THREAD 標(biāo)識(shí)位之后,使得親緣關(guān)系有了一定的變化。

  • 如果是新進(jìn)程,那這個(gè)進(jìn)程的 group_leader 就是它自己,tgid 就是它自己的 pid,也就是說(shuō),這就完全重打鑼鼓另開(kāi)張了,自己是線程組的頭;如果是新線程,group_leader 是當(dāng)前進(jìn)程的 group_leader,tgid 是當(dāng)前進(jìn)程的 tgid,也就是當(dāng)前進(jìn)程的 pid,這個(gè)時(shí)候還是拜原來(lái)進(jìn)程為老大。

  • 如果是新進(jìn)程,新進(jìn)程的 real_parent 就是當(dāng)前的進(jìn)程,也就是新進(jìn)程是子輩;如果是新線程,線程的 real_parent 是當(dāng)前的進(jìn)程的 real_parent,其實(shí)是平輩的。

第三,對(duì)信號(hào)的處理,如何保證發(fā)給進(jìn)程的信號(hào)雖然可以被一個(gè)線程處理,但是影響范圍應(yīng)該是整個(gè)進(jìn)程的。比如,kill一個(gè)進(jìn)程,則所有線程都要被干掉。如果 pthread_kill 一個(gè)線程,那只有那個(gè)線程才能夠收到。

  • 在 copy_process 的主流程里面,無(wú)論是創(chuàng)建進(jìn)程還是線程,都會(huì)初始化 struct sigpending pending,也就是每個(gè)task_struct,都會(huì)有這樣一個(gè)成員變量。這就是一個(gè)信號(hào)列表。如果這個(gè) task_struct 是一個(gè)線程,這里面的線程就是發(fā)給這個(gè)線程的;如果 task_struct ?是一個(gè)進(jìn)程,那這里面的信號(hào)是發(fā)給主線程的。

另外,上面 copy_signal 的時(shí)候,我們可以看到,在創(chuàng)建進(jìn)程的過(guò)程中,會(huì)初始化 signal_struct 里面的 struct sigpending shared_pending。但是,在創(chuàng)建線程的過(guò)程中,連 signal_struct ?都共享了。也就是說(shuō),整個(gè)進(jìn)程里的所有線程共享一個(gè) shard_pending,這也是一個(gè)信號(hào)列表,是發(fā)給整個(gè)進(jìn)程的,哪個(gè)線程處理都一樣

至此,clone 在內(nèi)核的調(diào)用完畢,要返回系統(tǒng)調(diào)用,回到用戶態(tài)。

用戶態(tài)執(zhí)行線程

根據(jù) clone 的第一個(gè)參數(shù),回到用戶態(tài)也不是直接運(yùn)行我們指定的那個(gè)函數(shù),而是一個(gè)通用的 start_thread,這是所有線程在用戶態(tài)的統(tǒng)一入口

在 start_thread 入口函數(shù)中,才真正的調(diào)用用戶提供的函數(shù),在用戶的函數(shù)執(zhí)行完畢之后,會(huì)釋放這個(gè)線程相關(guān)的數(shù)據(jù)。比如,線程本地?cái)?shù)據(jù) thread_local ,線程數(shù)目也減少 1。如果這是最后一個(gè)線程了,就直接退出進(jìn)程,另外 __free_tcb ?用于釋放 pthread

線程棧的列表 stack_used 中拿下來(lái),放到緩存的線程棧列表 stack_cache 中。

至此,整個(gè)線程的生命周期結(jié)束。

總結(jié)

下表對(duì)比了創(chuàng)建進(jìn)程和創(chuàng)建線程在用戶態(tài)和內(nèi)核態(tài)的不同。

  • 創(chuàng)建進(jìn)程的話,調(diào)用的系統(tǒng)調(diào)用是 fork,在 copy_process 函數(shù)里面,會(huì)將五大結(jié)構(gòu) files_struct、fs_struct、sighand_struct、signal_struct、mm_struct 都復(fù)制一遍,從此父進(jìn)程和子進(jìn)程各用各的數(shù)據(jù)結(jié)構(gòu)。

  • 創(chuàng)建線程的話,調(diào)用的是系統(tǒng)調(diào)用 clone,在 copy_process 函數(shù)里面,五大結(jié)構(gòu)僅僅是引用計(jì)數(shù)加一,也就是線程共享進(jìn)程的數(shù)據(jù)結(jié)構(gòu)


從源碼角度看Linux線程是怎么創(chuàng)建出來(lái)的的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
双城市| 潼关县| 凤山县| 兴隆县| 张家港市| 弥渡县| 昂仁县| 邵武市| 天水市| 大同县| 文登市| 吴忠市| 壤塘县| 克拉玛依市| 荣昌县| 江孜县| 华安县| 渭南市| 巴青县| 拜泉县| 高碑店市| 孟连| 兴安盟| 于都县| 宁化县| 阳春市| 青阳县| 六枝特区| 含山县| 来安县| 苍溪县| 石狮市| 赤峰市| 游戏| 霍林郭勒市| 司法| 榆林市| 长岭县| 康平县| 肇庆市| 雷山县|