<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Linux 中的各種棧:進(jìn)程棧 線程棧 內(nèi)核棧 中斷棧

          共 9423字,需瀏覽 19分鐘

           ·

          2021-10-16 08:26

          點(diǎn)擊上方“程序員大白”,選擇“星標(biāo)”公眾號(hào)

          重磅干貨,第一時(shí)間送達(dá)

          轉(zhuǎn)自:https://blog.csdn.net/yangkuanqaz85988/article/details/52403726

          棧是什么?棧有什么作用?

          首先,棧 (stack) 是一種串列形式的 數(shù)據(jù)結(jié)構(gòu)。這種數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)是 后入先出 (LIFO, Last In First Out),數(shù)據(jù)只能在串列的一端 (稱(chēng)為:棧頂 top) 進(jìn)行 推入 (push) 和 彈出 (pop) 操作。根據(jù)棧的特點(diǎn),很容易的想到可以利用數(shù)組,來(lái)實(shí)現(xiàn)這種數(shù)據(jù)結(jié)構(gòu)。但是本文要討論的并不是軟件層面的棧,而是硬件層面的棧。

          大多數(shù)的處理器架構(gòu),都有實(shí)現(xiàn)硬件棧。有專(zhuān)門(mén)的棧指針寄存器,以及特定的硬件指令來(lái)完成 入棧/出棧 的操作。例如在 ARM 架構(gòu)上,R13 (SP) 指針是堆棧指針寄存器,而 PUSH 是用于壓棧的匯編指令,POP 則是出棧的匯編指令。

          【擴(kuò)展閱讀】ARM 寄存器簡(jiǎn)介

          ARM 處理器擁有 37 個(gè)寄存器。這些寄存器按部分重疊組方式加以排列。每個(gè)處理器模式都有一個(gè)不同的寄存器組。編組的寄存器為處理處理器異常和特權(quán)操作提供了快速的上下文切換。

          提供了下列寄存器:

          • 三十個(gè) 32 位通用寄存器:
          • 存在十五個(gè)通用寄存器,它們分別是 r0-r12、sp、lr
          • sp (r13) 是堆棧指針。C/C++ 編譯器始終將 sp 用作堆棧指針
          • lr (r14) 用于存儲(chǔ)調(diào)用子例程時(shí)的返回地址。如果返回地址存儲(chǔ)在堆棧上,則可將 lr 用作通用寄存器
          • 程序計(jì)數(shù)器 (pc):指令寄存器
          • 應(yīng)用程序狀態(tài)寄存器 (APSR):存放算術(shù)邏輯單元 (ALU) 狀態(tài)標(biāo)記的副本
          • 當(dāng)前程序狀態(tài)寄存器 (CPSR):存放 APSR 標(biāo)記,當(dāng)前處理器模式,中斷禁用標(biāo)記等
          • 保存的程序狀態(tài)寄存器 (SPSR):當(dāng)發(fā)生異常時(shí),使用 SPSR 來(lái)存儲(chǔ) CPSR

          上面是棧的原理和實(shí)現(xiàn),下面我們來(lái)看看棧有什么作用。棧作用可以從兩個(gè)方面體現(xiàn):函數(shù)調(diào)用 和 多任務(wù)支持 。

          一、函數(shù)調(diào)用

          我們知道一個(gè)函數(shù)調(diào)用有以下三個(gè)基本過(guò)程:

          • 調(diào)用參數(shù)的傳入
          • 局部變量的空間管理
          • 函數(shù)返回

          函數(shù)的調(diào)用必須是高效的,而數(shù)據(jù)存放在 CPU通用寄存器 或者 RAM 內(nèi)存 中無(wú)疑是最好的選擇。以傳遞調(diào)用參數(shù)為例,我們可以選擇使用 CPU通用寄存器 來(lái)存放參數(shù)。但是通用寄存器的數(shù)目都是有限的,當(dāng)出現(xiàn)函數(shù)嵌套調(diào)用時(shí),子函數(shù)再次使用原有的通用寄存器必然會(huì)導(dǎo)致沖突。因此如果想用它來(lái)傳遞參數(shù),那在調(diào)用子函數(shù)前,就必須先 保存原有寄存器的值,然后當(dāng)子函數(shù)退出的時(shí)候再 恢復(fù)原有寄存器的值 。

          函數(shù)的調(diào)用參數(shù)數(shù)目一般都相對(duì)少,因此通用寄存器是可以滿(mǎn)足一定需求的。但是局部變量的數(shù)目和占用空間都是比較大的,再依賴(lài)有限的通用寄存器未免強(qiáng)人所難,因此我們可以采用某些 RAM 內(nèi)存區(qū)域來(lái)存儲(chǔ)局部變量。但是存儲(chǔ)在哪里合適?既不能讓函數(shù)嵌套調(diào)用的時(shí)候有沖突,又要注重效率。

          這種情況下,棧無(wú)疑提供很好的解決辦法。一、對(duì)于通用寄存器傳參的沖突,我們可以再調(diào)用子函數(shù)前,將通用寄存器臨時(shí)壓入棧中;在子函數(shù)調(diào)用完畢后,在將已保存的寄存器再?gòu)棾龌謴?fù)回來(lái)。二、而局部變量的空間申請(qǐng),也只需要向下移動(dòng)下棧頂指針;將棧頂指針向回移動(dòng),即可就可完成局部變量的空間釋放;三、對(duì)于函數(shù)的返回,也只需要在調(diào)用子函數(shù)前,將返回地址壓入棧中,待子函數(shù)調(diào)用結(jié)束后,將函數(shù)返回地址彈出給 PC 指針,即完成了函數(shù)調(diào)用的返回;

          于是上述函數(shù)調(diào)用的三個(gè)基本過(guò)程,就演變記錄一個(gè)棧指針的過(guò)程。每次函數(shù)調(diào)用的時(shí)候,都配套一個(gè)棧指針。即使循環(huán)嵌套調(diào)用函數(shù),只要對(duì)應(yīng)函數(shù)棧指針是不同的,也不會(huì)出現(xiàn)沖突。

          【擴(kuò)展閱讀】:函數(shù)棧幀 (Stack Frame)

          函數(shù)調(diào)用經(jīng)常是嵌套的,在同一時(shí)刻,棧中會(huì)有多個(gè)函數(shù)的信息。每個(gè)未完成運(yùn)行的函數(shù)占用一個(gè)獨(dú)立的連續(xù)區(qū)域,稱(chēng)作棧幀(Stack Frame)。棧幀存放著函數(shù)參數(shù),局部變量及恢復(fù)前一棧幀所需要的數(shù)據(jù)等,函數(shù)調(diào)用時(shí)入棧的順序?yàn)椋?/p>

          實(shí)參N~1 → 主調(diào)函數(shù)返回地址 → 主調(diào)函數(shù)幀基指針EBP → 被調(diào)函數(shù)局部變量1~N

          棧幀的邊界由 棧幀基地址指針 EBP 和 棧指針 ESP 界定,EBP 指向當(dāng)前棧幀底部(高地址),在當(dāng)前棧幀內(nèi)位置固定;ESP指向當(dāng)前棧幀頂部(低地址),當(dāng)程序執(zhí)行時(shí)ESP會(huì)隨著數(shù)據(jù)的入棧和出棧而移動(dòng)。因此函數(shù)中對(duì)大部分?jǐn)?shù)據(jù)的訪問(wèn)都基于EBP進(jìn)行。函數(shù)調(diào)用棧的典型內(nèi)存布局如下圖所示:

          二、多任務(wù)支持

          然而棧的意義還不只是函數(shù)調(diào)用,有了它的存在,才能構(gòu)建出操作系統(tǒng)的多任務(wù)模式。我們以 main 函數(shù)調(diào)用為例,main 函數(shù)包含一個(gè)無(wú)限循環(huán)體,循環(huán)體中先調(diào)用 A 函數(shù),再調(diào)用 B 函數(shù)。

          func?B():
          ??return;

          func?A():
          ??B();

          func?main():
          ??while?(1)
          ????A();

          試想在單處理器情況下,程序?qū)⒂肋h(yuǎn)停留在此 main 函數(shù)中。即使有另外一個(gè)任務(wù)在等待狀態(tài),程序是沒(méi)法從此 main 函數(shù)里面跳轉(zhuǎn)到另一個(gè)任務(wù)。因?yàn)槿绻呛瘮?shù)調(diào)用關(guān)系,本質(zhì)上還是屬于 main 函數(shù)的任務(wù)中,不能算多任務(wù)切換。此刻的 main 函數(shù)任務(wù)本身其實(shí)和它的棧綁定在了一起,無(wú)論如何嵌套調(diào)用函數(shù),棧指針都在本棧范圍內(nèi)移動(dòng)。

          由此可以看出一個(gè)任務(wù)可以利用以下信息來(lái)表征:

          1. main 函數(shù)體代碼
          2. main 函數(shù)棧指針
          3. 當(dāng)前 CPU 寄存器信息

          假如我們可以保存以上信息,則完全可以強(qiáng)制讓出 CPU 去處理其他任務(wù)。只要將來(lái)想繼續(xù)執(zhí)行此 main 任務(wù)的時(shí)候,把上面的信息恢復(fù)回去即可。有了這樣的先決條件,多任務(wù)就有了存在的基礎(chǔ),也可以看出棧存在的另一個(gè)意義。在多任務(wù)模式下,當(dāng)調(diào)度程序認(rèn)為有必要進(jìn)行任務(wù)切換的話,只需保存任務(wù)的信息(即上面說(shuō)的三個(gè)內(nèi)容)。恢復(fù)另一個(gè)任務(wù)的狀態(tài),然后跳轉(zhuǎn)到上次運(yùn)行的位置,就可以恢復(fù)運(yùn)行了。

          可見(jiàn)每個(gè)任務(wù)都有自己的棧空間,正是有了獨(dú)立的棧空間,為了代碼重用,不同的任務(wù)甚至可以混用任務(wù)的函數(shù)體本身,例如可以一個(gè)main函數(shù)有兩個(gè)任務(wù)實(shí)例。至此之后的操作系統(tǒng)的框架也形成了,譬如任務(wù)在調(diào)用 sleep() 等待的時(shí)候,可以主動(dòng)讓出 CPU 給別的任務(wù)使用,或者分時(shí)操作系統(tǒng)任務(wù)在時(shí)間片用完是也會(huì)被迫的讓出 CPU。不論是哪種方法,只要想辦法切換任務(wù)的上下文空間,切換棧即可。

          【擴(kuò)展閱讀】:任務(wù)、線程、進(jìn)程 三者關(guān)系

          任務(wù)是一個(gè)抽象的概念,即指軟件完成的一個(gè)活動(dòng);而線程則是完成任務(wù)所需的動(dòng)作;進(jìn)程則指的是完成此動(dòng)作所需資源的統(tǒng)稱(chēng);關(guān)于三者的關(guān)系,有一個(gè)形象的比喻:

          • 任務(wù) = 送貨
          • 線程 = 開(kāi)送貨車(chē)
          • 系統(tǒng)調(diào)度 = 決定合適開(kāi)哪部送貨車(chē)
          • 進(jìn)程 = 道路 + 加油站 + 送貨車(chē) + 修車(chē)廠

          Linux 中有幾種棧?各種棧的內(nèi)存位置?

          介紹完棧的工作原理和用途作用后,我們回歸到 Linux 內(nèi)核上來(lái)。內(nèi)核將棧分成四種:

          • 進(jìn)程棧
          • 線程棧
          • 內(nèi)核棧
          • 中斷棧

          一、進(jìn)程棧

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

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

          Linux 對(duì)進(jìn)程地址空間有個(gè)標(biāo)準(zhǔn)布局,地址空間中由各個(gè)不同的內(nèi)存段組成 (Memory Segment),主要的內(nèi)存段如下:

          • 程序段 (Text Segment):可執(zhí)行文件代碼的內(nèi)存映射
          • 數(shù)據(jù)段 (Data Segment):可執(zhí)行文件的已初始化全局變量的內(nèi)存映射
          • BSS段 (BSS Segment):未初始化的全局變量或者靜態(tài)變量(用零頁(yè)初始化)
          • 堆區(qū) (Heap) : 存儲(chǔ)動(dòng)態(tài)內(nèi)存分配,匿名的內(nèi)存映射
          • 棧區(qū) (Stack) : 進(jìn)程用戶(hù)空間棧,由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值、局部變量的值等
          • 映射段(Memory Mapping Segment):任何內(nèi)存映射文件



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

          【擴(kuò)展閱讀】:如何確認(rèn)進(jìn)程棧的大小

          我們要知道棧的大小,那必須得知道棧的起始地址和結(jié)束地址。棧起始地址 獲取很簡(jiǎn)單,只需要嵌入?yún)R編指令獲取棧指針 esp 地址即可。棧結(jié)束地址 的獲取有點(diǎn)麻煩,我們需要先利用遞歸函數(shù)把棧搞溢出了,然后再 GDB 中把棧溢出的時(shí)候把棧指針 esp 打印出來(lái)即可。代碼如下:

          /*?file?name:?stacksize.c?*/

          void?*orig_stack_pointer;

          void?blow_stack()?{
          ????blow_stack();
          }

          int?main()?{
          ????__asm__("movl?%esp,?orig_stack_pointer");

          ????blow_stack();
          ????return?0;
          }
          $ g++ -g stacksize.c -o ./stacksize
          $ gdb ./stacksize
          (gdb) r
          Starting program: /home/home/misc-code/setrlimit

          Program received signal SIGSEGV, Segmentation fault.
          blow_stack () at setrlimit.c:4
          4 blow_stack();
          (gdb) print (void *)$esp
          $1 = (void *) 0xffffffffff7ff000
          (gdb) print (void *)orig_stack_pointer
          $2 = (void *) 0xffffc800
          (gdb) print 0xffffc800-0xff7ff000
          $3 = 8378368 // Current Process Stack Size is 8M

          上面對(duì)進(jìn)程的地址空間有個(gè)比較全局的介紹,那我們看下 Linux 內(nèi)核中是怎么體現(xiàn)上面內(nèi)存布局的。內(nèi)核使用內(nèi)存描述符來(lái)表示進(jìn)程的地址空間,該描述符表示著進(jìn)程所有地址空間的信息。內(nèi)存描述符由 mm_struct 結(jié)構(gòu)體表示,下面給出內(nèi)存描述符結(jié)構(gòu)中各個(gè)域的描述,請(qǐng)大家結(jié)合前面的 進(jìn)程內(nèi)存段布局 圖一起看:

          struct?mm_struct?{
          ????struct?vm_area_struct?*mmap;???????????/*?內(nèi)存區(qū)域鏈表?*/
          ????struct?rb_root?mm_rb;??????????????????/*?VMA?形成的紅黑樹(shù)?*/
          ????...
          ????struct?list_head?mmlist;???????????????/*?所有?mm_struct?形成的鏈表?*/
          ????...
          ????unsigned?long?total_vm;????????????????/*?全部頁(yè)面數(shù)目?*/
          ????unsigned?long?locked_vm;???????????????/*?上鎖的頁(yè)面數(shù)據(jù)?*/
          ????unsigned?long?pinned_vm;???????????????/*?Refcount?permanently?increased?*/
          ????unsigned?long?shared_vm;???????????????/*?共享頁(yè)面數(shù)目?Shared?pages?(files)?*/
          ????unsigned?long?exec_vm;?????????????????/*?可執(zhí)行頁(yè)面數(shù)目?VM_EXEC?&?~VM_WRITE?*/
          ????unsigned?long?stack_vm;????????????????/*?棧區(qū)頁(yè)面數(shù)目?VM_GROWSUP/DOWN?*/
          ????unsigned?long?def_flags;
          ????unsigned?long?start_code,?end_code,?start_data,?end_data;????/*?代碼段、數(shù)據(jù)段?起始地址和結(jié)束地址?*/
          ????unsigned?long?start_brk,?brk,?start_stack;???????????????????/*?棧區(qū)?的起始地址,堆區(qū)?起始地址和結(jié)束地址?*/
          ????unsigned?long?arg_start,?arg_end,?env_start,?env_end;????????/*?命令行參數(shù)?和?環(huán)境變量的?起始地址和結(jié)束地址?*/
          ????...
          ????/*?Architecture-specific?MM?context?*/
          ????mm_context_t?context;??????????????????/*?體系結(jié)構(gòu)特殊數(shù)據(jù)?*/

          ????/*?Must?use?atomic?bitops?to?access?the?bits?*/
          ????unsigned?long?flags;???????????????????/*?狀態(tài)標(biāo)志位?*/
          ????...
          ????/*?Coredumping?and?NUMA?and?HugePage?相關(guān)結(jié)構(gòu)體?*/
          };

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

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

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

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

          二、線程棧

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

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

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

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

          三、進(jìn)程內(nèi)核棧

          在每一個(gè)進(jìn)程的生命周期中,必然會(huì)通過(guò)到系統(tǒng)調(diào)用陷入內(nèi)核。在執(zhí)行系統(tǒng)調(diào)用陷入內(nèi)核之后,這些內(nèi)核代碼所使用的棧并不是原先進(jìn)程用戶(hù)空間中的棧,而是一個(gè)單獨(dú)內(nèi)核空間的棧,這個(gè)稱(chēng)作進(jìn)程內(nèi)核棧。進(jìn)程內(nèi)核棧在進(jìn)程創(chuàng)建的時(shí)候,通過(guò) slab 分配器從?thread_info_cache?緩存池中分配出來(lái),其大小為?THREAD_SIZE,一般來(lái)說(shuō)是一個(gè)頁(yè)大小 4K;

          union?thread_union?{???????????????????????????????????
          ????????struct?thread_info?thread_info;????????????????
          ????????unsigned?long?stack[THREAD_SIZE/sizeof(long)];
          };

          thread_union?進(jìn)程內(nèi)核棧 和?task_struct?進(jìn)程描述符有著緊密的聯(lián)系。由于內(nèi)核經(jīng)常要訪問(wèn)?task_struct,高效獲取當(dāng)前進(jìn)程的描述符是一件非常重要的事情。因此內(nèi)核將進(jìn)程內(nèi)核棧的頭部一段空間,用于存放?thread_info?結(jié)構(gòu)體,而此結(jié)構(gòu)體中則記錄了對(duì)應(yīng)進(jìn)程的描述符,兩者關(guān)系如下圖(對(duì)應(yīng)內(nèi)核函數(shù)為?dup_task_struct()):

          有了上述關(guān)聯(lián)結(jié)構(gòu)后,內(nèi)核可以先獲取到棧頂指針 esp,然后通過(guò) esp 來(lái)獲取 thread_info。這里有一個(gè)小技巧,直接將 esp 的地址與上 ~(THREAD_SIZE - 1) 后即可直接獲得 thread_info 的地址。由于 thread_union 結(jié)構(gòu)體是從 thread_info_cache 的 Slab 緩存池中申請(qǐng)出來(lái)的,而 thread_info_cache 在 kmem_cache_create 創(chuàng)建的時(shí)候,保證了地址是 THREAD_SIZE 對(duì)齊的。因此只需要對(duì)棧指針進(jìn)行 THREAD_SIZE 對(duì)齊,即可獲得 thread_union 的地址,也就獲得了 thread_union 的地址。成功獲取到 thread_info 后,直接取出它的 task 成員就成功得到了 task_struct。其實(shí)上面這段描述,也就是 current 宏的實(shí)現(xiàn)方法:

          register?unsigned?long?current_stack_pointer?asm?("sp");

          static?inline?struct?thread_info?*current_thread_info(void)??
          {????????????????????????????????????????????????????????????
          ????????return?(struct?thread_info?*)????????????????????????
          ????????????????(current_stack_pointer?&?~(THREAD_SIZE?-?1));
          }????????????????????????????????????????????????????????????

          #define?get_current()?(current_thread_info()->task)

          #define?current?get_current()

          四、中斷棧

          進(jìn)程陷入內(nèi)核態(tài)的時(shí)候,需要內(nèi)核棧來(lái)支持內(nèi)核函數(shù)調(diào)用。中斷也是如此,當(dāng)系統(tǒng)收到中斷事件后,進(jìn)行中斷處理的時(shí)候,也需要中斷棧來(lái)支持函數(shù)調(diào)用。由于系統(tǒng)中斷的時(shí)候,系統(tǒng)當(dāng)然是處于內(nèi)核態(tài)的,所以中斷棧是可以和內(nèi)核棧共享的。但是具體是否共享,這和具體處理架構(gòu)密切相關(guān)。

          X86 上中斷棧就是獨(dú)立于內(nèi)核棧的;獨(dú)立的中斷棧所在內(nèi)存空間的分配發(fā)生在?arch/x86/kernel/irq_32.c?的?irq_ctx_init()?函數(shù)中 (如果是多處理器系統(tǒng),那么每個(gè)處理器都會(huì)有一個(gè)獨(dú)立的中斷棧),函數(shù)使用?__alloc_pages?在低端內(nèi)存區(qū)分配 2個(gè)物理頁(yè)面,也就是8KB大小的空間。有趣的是,這個(gè)函數(shù)還會(huì)為 softirq 分配一個(gè)同樣大小的獨(dú)立堆棧。如此說(shuō)來(lái),softirq 將不會(huì)在 hardirq 的中斷棧上執(zhí)行,而是在自己的上下文中執(zhí)行。

          而 ARM 上中斷棧和內(nèi)核棧則是共享的;中斷棧和內(nèi)核棧共享有一個(gè)負(fù)面因素,如果中斷發(fā)生嵌套,可能會(huì)造成棧溢出,從而可能會(huì)破壞到內(nèi)核棧的一些重要數(shù)據(jù),所以棧空間有時(shí)候難免會(huì)捉襟見(jiàn)肘。


          國(guó)產(chǎn)小眾瀏覽器因屏蔽視頻廣告,被索賠100萬(wàn)(后續(xù))

          年輕人“不講武德”:因看黃片上癮,把網(wǎng)站和786名女主播起訴了

          中國(guó)聯(lián)通官網(wǎng)被發(fā)現(xiàn)含木馬腳本,可向用戶(hù)推廣色情APP

          張一鳴:每個(gè)逆襲的年輕人,都具備的底層能力


          關(guān)


          學(xué)西學(xué)學(xué)運(yùn)營(yíng)護(hù)號(hào)樂(lè)質(zhì)結(jié)識(shí)關(guān)[]學(xué)習(xí)進(jìn)


          瀏覽 61
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  激情在线视频韩国青青 | 大香蕉在线伊人 | 人人操人人色人人 | 天天插天天射 | 欧美成人精品欧美一级乱黄 |