0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

從 Linux 內(nèi)核的角度談線程棧和進(jìn)程棧

454398 ? 來源: Chinaunix ? 作者:lvyilong316 ? 2020-09-25 15:23 ? 次閱讀

1.進(jìn)程棧

進(jìn)程棧是屬于用戶態(tài)棧,和進(jìn)程虛擬地址空間(Virtual Address Space)密切相關(guān)。那我們先了解下什么是虛擬地址空間:在32位機(jī)器下,虛擬地址空間大小為4G。這些虛擬地址通過頁表(Page Table)映射到物理內(nèi)存,頁表由操作系統(tǒng)維護(hù),并被處理器的內(nèi)存管理單元(MMU)硬件引用。每個進(jìn)程都擁有一套屬于它自己的頁表,因此對于每個進(jìn)程而言都好像獨(dú)享了整個虛擬地址空間。

Linux內(nèi)核將這4G字節(jié)的空間分為兩部分,將最高的1G字節(jié)(0xC0000000-0xFFFFFFFF)供內(nèi)核使用,稱為內(nèi)核空間。而將較低的3G字節(jié)(0x00000000-0xBFFFFFFF)供各個進(jìn)程使用,稱為用戶空間。每個進(jìn)程可以通過系統(tǒng)調(diào)用陷入內(nèi)核態(tài),因此內(nèi)核空間是由所有進(jìn)程共享的。雖然說內(nèi)核和用戶態(tài)進(jìn)程占用了這么大地址空間,但是并不意味它們使用了這么多物理內(nèi)存,僅表示它可以支配這么大的地址空間。它們是根據(jù)需要,將物理內(nèi)存映射到虛擬地址空間中使用。

Linux對進(jìn)程地址空間有個標(biāo)準(zhǔn)布局,地址空間中由各個不同的內(nèi)存段組成(Memory Segment),主要的內(nèi)存段如下:
-程序段(Text Segment):可執(zhí)行文件代碼的內(nèi)存映射
-數(shù)據(jù)段(Data Segment):可執(zhí)行文件的已初始化全局變量的內(nèi)存映射
- BSS段(BSS Segment):未初始化的全局變量或者靜態(tài)變量(用零頁初始化)
-堆區(qū)(Heap) :存儲動態(tài)內(nèi)存分配,匿名的內(nèi)存映射
-棧區(qū)(Stack) :進(jìn)程用戶空間棧,由編譯器自動分配釋放,存放函數(shù)的參數(shù)值、局部變量的值等
-映射段(Memory Mapping Segment):任何內(nèi)存映射文件

而上面進(jìn)程虛擬地址空間中的棧區(qū),正指的是我們所說的進(jìn)程棧。進(jìn)程棧的初始化大小是由編譯器和鏈接器計算出來的,但是棧的實(shí)時大小并不是固定的,Linux內(nèi)核會根據(jù)入棧情況對棧區(qū)進(jìn)行動態(tài)增長(其實(shí)也就是添加新的頁表)。但是并不是說棧區(qū)可以無限增長,它也有最大限制RLIMIT_STACK (一般為8M),我們可以通過ulimit來查看或更改RLIMIT_STACK的值。

【擴(kuò)展閱讀】:進(jìn)程棧的動態(tài)增長實(shí)現(xiàn)

進(jìn)程在運(yùn)行的過程中,通過不斷向棧區(qū)壓入數(shù)據(jù),當(dāng)超出棧區(qū)容量時,就會耗盡棧所對應(yīng)的內(nèi)存區(qū)域,這將觸發(fā)一個缺頁異常(page fault)。通過異常陷入內(nèi)核態(tài)后,異常會被內(nèi)核的expand_stack()函數(shù)處理,進(jìn)而調(diào)用acct_stack_growth()來檢查是否還有合適的地方用于棧的增長。

如果棧的大小低于RLIMIT_STACK(通常為8MB),那么一般情況下棧會被加長,程序繼續(xù)執(zhí)行,感覺不到發(fā)生了什么事情,這是一種將棧擴(kuò)展到所需大小的常規(guī)機(jī)制。然而,如果達(dá)到了最大棧空間的大小,就會發(fā)生 棧溢出(stack overflow),進(jìn)程將會收到內(nèi)核發(fā)出的 段錯誤(segmentation fault) 信號

動態(tài)棧增長是唯一一種訪問未映射內(nèi)存區(qū)域而被允許的情形,其他任何對未映射內(nèi)存區(qū)域的訪問都會觸發(fā)頁錯誤,從而導(dǎo)致段錯誤。一些被映射的區(qū)域是只讀的,因此企圖寫這些區(qū)域也會導(dǎo)致段錯誤。

2.線程棧

從Linux內(nèi)核的角度來說,其實(shí)它并沒有線程的概念。Linux把所有線程都當(dāng)做進(jìn)程來實(shí)現(xiàn),它將線程和進(jìn)程不加區(qū)分的統(tǒng)一到了task_struct中。線程僅僅被視為一個與其他進(jìn)程共享某些資源的進(jìn)程,而是否共享地址空間幾乎是進(jìn)程和Linux中所謂線程的唯一區(qū)別。線程創(chuàng)建的時候,加上了CLONE_VM標(biāo)記,這樣線程的內(nèi)存描述符將直接指向父進(jìn)程的內(nèi)存描述符。

點(diǎn)擊(此處)折疊或打開

if(clone_flags&CLONE_VM){

/*

*current 是父進(jìn)程而 tsk 在 fork()執(zhí)行期間是共享子進(jìn)程

*/

atomic_inc(¤t->mm->mm_users);

tsk->mm=current->mm;

}

雖然線程的地址空間和進(jìn)程一樣,但是對待其地址空間的stack還是有些區(qū)別的。對于Linux進(jìn)程或者說主線程,其stack是在fork的時候生成的,實(shí)際上就是復(fù)制了父親的stack空間地址,然后寫時拷貝(cow)以及動態(tài)增長。然而對于主線程生成的子線程而言,其stack將不再是這樣的了,而是事先固定下來的,使用mmap系統(tǒng)調(diào)用(實(shí)際上是進(jìn)程的堆的一部分),它不帶有VM_STACK_FLAGS標(biāo)記。這個可以從glibc的nptl/allocatestack.c中的allocate_stack()函數(shù)中看到:

點(diǎn)擊(此處)折疊或打開

mem=mmap(NULL,size,prot,MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK,-1,0);

由于線程的mm->start_stack棧地址和所屬進(jìn)程相同,所以線程棧的起始地址并沒有存放在task_struct中,應(yīng)該是使用pthread_attr_t中的stackaddr來初始化task_struct->thread->sp(sp指向struct pt_regs對象,該結(jié)構(gòu)體用于保存用戶進(jìn)程或者線程的寄存器現(xiàn)場)。這些都不重要,重要的是,線程棧不能動態(tài)增長,一旦用盡就沒了,這是和生成進(jìn)程的fork不同的地方。由于線程棧是從進(jìn)程的地址空間中map出來的一塊內(nèi)存區(qū)域,原則上是線程私有的。但是同一個進(jìn)程的所有線程生成的時候淺拷貝生成者的task_struct的很多字段,其中包括所有的vma,如果愿意,其它線程也還是可以訪問到的,于是一定要注意。

3.進(jìn)程棧和線程棧大小的調(diào)整

進(jìn)程和線程的棧分別是多大呢?首先從我們熟悉的ulimit -s說起,熟悉linux的人都應(yīng)該知道通過ulimit -s可以修改棧的大小,除此之外還有g(shù)etrlimit/setrlimit兩個函數(shù):

點(diǎn)擊(此處)折疊或打開

intgetrlimit(intresource,struct rlimit*rlim);

intsetrlimit(intresource,conststruct rlimit*rlim);

這兩個函數(shù)當(dāng)?shù)谝粋€參數(shù)傳入RLIMIT_STACK時,可以設(shè)置和獲取棧的大小,其作用和ulimit -s是一樣的,只是單位不同,ulimit -s的單位是kB,而這兩個函數(shù)的單位是B(字節(jié)),詳細(xì)使用方法請參考man手冊。

最后還有線程的pthread_attr_setstacksize/pthread_attr_getstacksize。

使用setrlimit和使用ulimit -s設(shè)置棧大小效果相同,這兩種方式都是針對進(jìn)程棧大小設(shè)置,只不過前者只真對當(dāng)前進(jìn)程,后者針對當(dāng)前shell;

而線程棧大小的關(guān)系就相對比較復(fù)雜點(diǎn),前文說過線程大小是靜態(tài)的,是在創(chuàng)建時就確定了的,當(dāng)然如果使用pthread_attr_setstacksize可以在創(chuàng)建線程時指定線程棧大小,但如果不指定線程棧的話其默認(rèn)大小是什么情況呢?想要了解線程棧的大小就要看glibc的線程創(chuàng)建函數(shù),具體就是pthread_create->__pthread_create_2_1->allocate_stack。具體代碼還是比較復(fù)雜的,這里簡化為一個偽代碼:

點(diǎn)擊(此處)折疊或打開

limit=getlimit(RLIMIT_STACK)

if(limit==RLIMIT_INFINITY)

thread.rlimit=ARCH_STACK_DEFAULT_SIZE//2M

elseifthread.rlimit

thread.rlimit=PTHREAD_STACK_MIN

可以看出,線程默認(rèn)棧大小和進(jìn)程棧大小的關(guān)系:

1)如果ulimit(setrlimit)設(shè)置大小大于16k,則線程棧默認(rèn)大小由ulimit(setrlimit)決定;

2)如果ulimit(setrlimit)設(shè)置大小小于16k,則線程棧默認(rèn)大小為16;

3)如果ulimit(setrlimit)設(shè)置大小為無限制,則線程棧默認(rèn)大小為2M;

所以我們?nèi)绻褂胾limit設(shè)置進(jìn)程棧大小是無限大其實(shí)棧大小反而相對比較小,這是為什么呢?前面我們已經(jīng)講過線程棧和進(jìn)程棧的位置不同,線程棧其實(shí)是在進(jìn)程的堆上分配的,并且不會動態(tài)增加,所以不可能設(shè)置一個無限大小的線程棧。

最后,我們再對進(jìn)程棧和線程棧做一下總結(jié)和說明:

(1)ulimit -s決定進(jìn)程棧的大小,但不是嚴(yán)格相等(實(shí)際測試稍大于ulimit -s設(shè)置;

(2)創(chuàng)建線程時如果通過pthread_attr_setstacksize設(shè)置了線程棧大小,則使用該屬性創(chuàng)建的線程棧大小就為其設(shè)置的值,但不影響線程默認(rèn)屬性的棧大小值,也不影響ulimit -s的值。

(3)線程一旦創(chuàng)建就無法在修改其棧大小了,即使使用setrlimit。

(4)pthread_attr_setstacksize/pthread_attr_getstacksize的作用是獲取和設(shè)置線程屬性中的棧大小的,而不獲取設(shè)置線程棧大小的??梢栽賱?chuàng)建前設(shè)置好線程屬性,這樣使用該屬性創(chuàng)建線程就能影響線程的棧大小了。但通過pthread_attr_init,pthread_attr_getstacksize是無法獲取當(dāng)前線程棧大小的,只能獲取默認(rèn)屬性的線程棧大小,其值未必就是當(dāng)前線程棧大小。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11310

    瀏覽量

    209620
  • 線程
    +關(guān)注

    關(guān)注

    0

    文章

    505

    瀏覽量

    19695
  • 內(nèi)存映射
    +關(guān)注

    關(guān)注

    0

    文章

    14

    瀏覽量

    7416
  • 進(jìn)程
    +關(guān)注

    關(guān)注

    0

    文章

    203

    瀏覽量

    13962
收藏 人收藏

    評論

    相關(guān)推薦

    Linux網(wǎng)絡(luò)原理與實(shí)現(xiàn)

    本文嘗試技術(shù)研發(fā)與工程實(shí)踐(而非純理論學(xué)習(xí))角度,在原理與實(shí)現(xiàn)、監(jiān)控告警、 配置調(diào)優(yōu)三方面介紹內(nèi)核5.10 網(wǎng)絡(luò)。由于內(nèi)容非常多,因此分為了幾篇系列文章。
    發(fā)表于 08-10 08:58 ?3760次閱讀

    進(jìn)程線程的區(qū)別

    應(yīng)用程序提供多個線程執(zhí)行控制。 邏輯角度來看,多線程的意義在于一個應(yīng)用程序中,有多個執(zhí)行部分可以同時執(zhí)行。但操作系統(tǒng)并沒有將多個線程看做多
    發(fā)表于 12-12 09:28

    請問uCOS-II中的任務(wù)是進(jìn)程還是線程?

    個提問,然后上面的定義是百度的。按照定義任務(wù)應(yīng)該是進(jìn)程。有沒大佬rtos的角度剖析一下進(jìn)程線程的區(qū)別。
    發(fā)表于 06-03 05:07

    有關(guān)Linux系統(tǒng)的PBC (進(jìn)程控制塊)基礎(chǔ)知識介紹

    ,比如打開的文件,掛起的信號,處理器狀態(tài),內(nèi)核數(shù)據(jù)結(jié)構(gòu),內(nèi)存映射地址空間等。在操作系統(tǒng)中,內(nèi)核的調(diào)度對象時線程,而不是進(jìn)程。線程
    發(fā)表于 06-23 16:27

    用一個實(shí)例展示一下Linux內(nèi)核幀的入和退過程

    1、Linux內(nèi)核調(diào)試方法總結(jié)之幀  幀  幀和指針可以說是C語言的精髓。幀是一種特殊的
    發(fā)表于 11-04 15:47

    基于STM32的虛擬多線程(TI_BLE協(xié)議_ZStack協(xié)議)

    基于STM32的虛擬多線程,可以很好的用于裸機(jī)程序中,用于模擬小型操作系統(tǒng)的多線程概念。本實(shí)例參考了參考TI_BLE協(xié)議_ZStack協(xié)議。
    發(fā)表于 06-14 10:42 ?6937次閱讀
    基于STM32的虛擬多<b class='flag-5'>線程</b>(TI_BLE協(xié)議<b class='flag-5'>棧</b>_ZStack協(xié)議<b class='flag-5'>棧</b>)

    一文詳解Linux內(nèi)核回溯與妙用

    網(wǎng)上或多或少都能找到回溯的一些文章,但是講的都并不完整,沒有將內(nèi)核回溯的功能用于實(shí)際的內(nèi)核、應(yīng)用程序調(diào)試,這是本篇文章的核心:盡可能引導(dǎo)讀者將
    的頭像 發(fā)表于 10-05 10:02 ?5404次閱讀
    一文詳解<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>的<b class='flag-5'>棧</b>回溯與妙用

    Linux進(jìn)程內(nèi)核的認(rèn)識

    在每一個進(jìn)程的生命周期中,必然會通過到系統(tǒng)調(diào)用陷入內(nèi)核。在執(zhí)行系統(tǒng)調(diào)用陷入內(nèi)核之后,這些內(nèi)核代碼所使用的并不是原先用戶空間中的
    發(fā)表于 05-12 08:53 ?627次閱讀
    對<b class='flag-5'>Linux</b>的<b class='flag-5'>進(jìn)程</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>棧</b>的認(rèn)識

    淺談鴻蒙內(nèi)核源碼的

    上面的代碼和鴻蒙內(nèi)核方式一樣,都采用了遞減滿的方式, 什么是遞減滿?
    的頭像 發(fā)表于 04-24 11:21 ?1450次閱讀
    淺談鴻蒙<b class='flag-5'>內(nèi)核</b>源碼的<b class='flag-5'>棧</b>

    Linux線程進(jìn)程的區(qū)別

    不同的任務(wù)。在Unix System V及SunOS中也被稱為輕量進(jìn)程(lightweight processes),但輕量進(jìn)程更多指內(nèi)核線程(kernel thread),而把用戶
    的頭像 發(fā)表于 08-24 15:37 ?1867次閱讀
    <b class='flag-5'>Linux</b>下<b class='flag-5'>線程</b>與<b class='flag-5'>進(jìn)程</b>的區(qū)別

    Linux中的進(jìn)程、線程內(nèi)核以及中斷

    首先, (stack) 是一種串列形式的 數(shù)據(jù)結(jié)構(gòu)。這種數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)是 后入先出 (LIFO, Last In First Out),數(shù)據(jù)只能在串列的一端 (稱為:頂 top) 進(jìn)行 推入
    的頭像 發(fā)表于 05-14 09:30 ?704次閱讀
    <b class='flag-5'>Linux</b>中的<b class='flag-5'>進(jìn)程</b><b class='flag-5'>棧</b>、<b class='flag-5'>線程</b><b class='flag-5'>棧</b>、<b class='flag-5'>內(nèi)核</b><b class='flag-5'>棧</b>以及中斷<b class='flag-5'>棧</b>

    系統(tǒng)調(diào)用:用戶內(nèi)核的切換(上)

    當(dāng)發(fā)生系統(tǒng)調(diào)用、產(chǎn)生異常,外設(shè)發(fā)生中斷等事件時,會發(fā)生用戶內(nèi)核之間的切換, 本文系統(tǒng)調(diào)用角度分析用戶
    的頭像 發(fā)表于 07-31 11:27 ?878次閱讀
    系統(tǒng)調(diào)用:用戶<b class='flag-5'>棧</b>與<b class='flag-5'>內(nèi)核</b><b class='flag-5'>棧</b>的切換(上)

    linux中的進(jìn)程,線程,內(nèi)核的區(qū)別

    大多數(shù)的處理器架構(gòu),都有實(shí)現(xiàn)硬件。有專門的指針寄存器,以及特定的硬件指令來完成 入/出 的操作。例如在 ARM 架構(gòu)上,R13 (SP) 指針是堆棧指針寄存器,而 PUSH 是
    發(fā)表于 08-18 10:57 ?521次閱讀
    <b class='flag-5'>linux</b>中的<b class='flag-5'>進(jìn)程</b><b class='flag-5'>棧</b>,<b class='flag-5'>線程</b><b class='flag-5'>棧</b>,<b class='flag-5'>內(nèi)核</b><b class='flag-5'>棧</b>的區(qū)別

    ethernetif_input和tcpip協(xié)議線程的作用

    tcpip協(xié)議線程是lwIP協(xié)議的核心線程,負(fù)責(zé)處理TCP/IP協(xié)議的各種功能,包括TCP連接管理、IP數(shù)據(jù)報的路由和轉(zhuǎn)發(fā)、以及UDP
    的頭像 發(fā)表于 03-20 10:01 ?1363次閱讀

    Linux網(wǎng)絡(luò)協(xié)議的實(shí)現(xiàn)

    網(wǎng)絡(luò)協(xié)議是操作系統(tǒng)核心的一個重要組成部分,負(fù)責(zé)管理網(wǎng)絡(luò)通信中的數(shù)據(jù)包處理。在 Linux 操作系統(tǒng)中,網(wǎng)絡(luò)協(xié)議(Network Stack)負(fù)責(zé)實(shí)現(xiàn) TCP/IP 協(xié)議簇,處理應(yīng)用程序發(fā)起的網(wǎng)絡(luò)
    的頭像 發(fā)表于 09-10 09:51 ?319次閱讀
    <b class='flag-5'>Linux</b>網(wǎng)絡(luò)協(xié)議<b class='flag-5'>棧</b>的實(shí)現(xiàn)