在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

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

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

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

99%開發(fā)者從未聽說過的堆棧模型

痞子衡嵌入式 ? 來源:痞子衡嵌入式 ? 作者:痞子衡嵌入式 ? 2022-11-29 11:19 ? 次閱讀


朋友: 你知道如何設(shè)置棧最安全么?你知道如何不寫一行匯編代碼就能設(shè)置棧的大小么?你知道如何在鏈接腳本中使用宏和頭文件么?你知道如何在代碼中隨時(shí)隨地檢查棧的最大使用情況么? 本文從理論到實(shí)踐,從知其然到知其所以然,一杯奶茶的功夫就給你講得明明白白。

在中文嵌入式環(huán)境中,時(shí)不時(shí)的總能看到不少朋友”堆”“棧“傻傻分不清楚,我很早之前在文章《漫談C變量——夏蟲不可語(yǔ)冰》介紹過二者的區(qū)別,這里就不再深入展開,總之:
棧(Stack)“是我們用來分配局部變量、實(shí)現(xiàn)函數(shù)調(diào)用和在異常響應(yīng)時(shí)保存被打斷代碼上下文的地方——具體細(xì)節(jié)不重要,在本文的討論中,我們只需要記住以下信息
  • Cortex-M系統(tǒng)棧的生長(zhǎng)方向是自上而下的,也就是隨著更多內(nèi)容被壓入(PUSH)棧中,棧頂指針的地址值是越來越小的——也就是從地址值較大的位置向地址值較小的位置移動(dòng)。
  • Cortex-M的棧頂指針指向的是“棧頂部的空位”
  • 從最大兼容性角度考慮,Cortex-M架構(gòu)下棧存儲(chǔ)空間必須對(duì)齊到8字節(jié)。

f73033cc-6f92-11ed-8abf-dac502259ad0.png

“堆(Heap)”是我們使用 malloc 申請(qǐng)動(dòng)態(tài)存儲(chǔ)空間時(shí)所必須用到的一種數(shù)據(jù)結(jié)構(gòu)——通常由C語(yǔ)言的系統(tǒng)庫(kù)提供。
  • 堆本身只是一個(gè)內(nèi)存管理的算法,它所要管理的RAM空間需要用戶通過某種手段將指定大小的RAM空間交到Heap算法手里
  • 與棧不同,堆的生長(zhǎng)方向其實(shí)完全由具體的管理算法決定,而堆的算法數(shù)量雖然不能說是燦若星辰,至少一雙手肯定數(shù)不過來——但一般來說我們可以大體認(rèn)為堆的生長(zhǎng)方向是“自下而上的”——也就是從地址值較小的位置延伸到地址值較大的位置。
  • 堆的對(duì)齊要求一般是4字節(jié)起步,8字節(jié)更好,情況不明的直接就32個(gè)字節(jié)吧

f7401fda-6f92-11ed-8abf-dac502259ad0.png

【常見的堆棧模型】


從單純從我不負(fù)責(zé)任的經(jīng)驗(yàn)來看,由很多GCC領(lǐng)銜使用的“對(duì)向生長(zhǎng)”模型可能是嵌入式領(lǐng)域最常見的”大聰明模型“,沒有之一。如下圖所示:

f74f1620-6f92-11ed-8abf-dac502259ad0.png

先說優(yōu)點(diǎn)吧:
  • 該模型棧和堆共用同一塊連續(xù)的地址區(qū)間
  • 配置時(shí)不需要操心具體棧有多大、堆有多大
  • 配置方法簡(jiǎn)單:只需要指定這一整塊”堆棧“區(qū)域的起始地址,以及這一整塊堆棧區(qū)域的大小
  • 堆和棧的最大可用大小是此消彼長(zhǎng)的,理論上可以在某種最優(yōu)的情況下達(dá)到動(dòng)態(tài)的”此消彼長(zhǎng)“,可以獲得理想狀下最大的空間復(fù)用效率。
缺點(diǎn)也很明顯:
  • 堆和棧的最大可用大小是此消彼長(zhǎng)的,在真實(shí)場(chǎng)景中,由于”你長(zhǎng)我也長(zhǎng)誰怕誰”的情況居多,發(fā)生隨機(jī)性的“雙向奔赴”從而進(jìn)行“負(fù)距離”的互動(dòng)可能性從理論上就不可避免,因而是系統(tǒng)穩(wěn)定性的“一生之?dāng)场?/span>
  • 實(shí)驗(yàn)室里7x24小時(shí)完美通過,一去客戶那里就隨機(jī)性宕機(jī)的“挖坑之王”

為了提高系統(tǒng)穩(wěn)定性,人們簡(jiǎn)單地將“堆”和“棧”拆開來單獨(dú)配置,就獲得了常見的“兩段式堆棧模型”:

f778ca9c-6f92-11ed-8abf-dac502259ad0.png

可以看到,相較之前的模型,雖然仍然是“對(duì)向生長(zhǎng)”,但由于棧和堆有了自己固定空間,因此可以方便地根據(jù)實(shí)際用量調(diào)整它們的大小(比如留下足夠的余量),從而降低彼此入侵帶來的穩(wěn)定性風(fēng)險(xiǎn)。 更有甚者,在二者的邊界上引入一個(gè)特殊值(比如0xDEADBEEF)所充當(dāng)?shù)囊绯?a target="_blank">檢測(cè)”金絲雀(Canary)”——一旦發(fā)現(xiàn)這個(gè)值與預(yù)設(shè)的不同,基本就可以斷定發(fā)生了溢出。

【最安全的“兩面包夾芝士”模型】


將“棧(Stack)”和"堆(Heap)"獨(dú)立配置的“兩段式”模型配合邊界金絲雀,為預(yù)防和檢測(cè)堆棧溢出提供了可能。但對(duì)金絲雀的檢測(cè)總歸有種“事后諸葛亮”的感覺,而且很多時(shí)候,我們是想不起來去檢查金絲雀的,比如:棧曾經(jīng)一度跨越雷池入侵到了堆空間,但由于此時(shí)堆恰巧分配出去的RAM不多,沒有與棧發(fā)生實(shí)質(zhì)性的重疊,因而整個(gè)系統(tǒng)“安然無恙”——這只能說是運(yùn)氣好,而風(fēng)險(xiǎn)肯定是存在的——正由于系統(tǒng)“安然無恙”,因此我們?cè)谙到y(tǒng)開發(fā)階段可能不會(huì)想起來去檢查一下金絲雀(有自動(dòng)檢查機(jī)制的RTOS除外),那么這類溢出就有可能被隱藏。

基于上述原因,有沒有一種方法可以:

  • 徹底避免棧/堆入侵對(duì)系統(tǒng)的破壞
  • 在棧/堆入侵的瞬間就立即表現(xiàn)出來——方便我們?cè)谡{(diào)試階段立即發(fā)現(xiàn)
答案是肯定的,這就是“兩面包夾芝士”模型(此前又叫“三明治”模型):

f79f234a-6f92-11ed-8abf-dac502259ad0.png

從上圖很容易看出:
  • 該模型屬于“兩段式模型”的變種
  • 與過去堆和棧的“相向生長(zhǎng)”不同,該模型采用了“背向生長(zhǎng)”的方式——避免了棧與堆的相互傷害
  • 棧被放在了SRAM的起始位置(Cortex-M從架構(gòu)上鼓勵(lì)將SRAM放置在從0x2000-0000開始的地址上),這樣一旦發(fā)生棧溢出,指針就會(huì)指向SRAM存儲(chǔ)器以外的無效位置——這在大部分芯片會(huì)觸發(fā)“Bus Fault”,從而產(chǎn)生故障異常——這就實(shí)現(xiàn)了對(duì)棧溢出的當(dāng)場(chǎng)捕獲,并且不依賴MPU或者“棧底地址限制檢測(cè)(Stack Limit Checking)”之類的架構(gòu)特性。

當(dāng)然有些芯片設(shè)計(jì)者可能會(huì)選擇“隱藏這類錯(cuò)誤”,不僅不會(huì)觸發(fā)異常,而且會(huì)當(dāng)做無事發(fā)生,具體表現(xiàn)為:對(duì)無效地址的寫入操作將被無視,對(duì)無效地址的讀取操作將會(huì)返回0值。具體可以參考芯片手冊(cè),或者干脆做個(gè)實(shí)驗(yàn)。
  • 堆被放置在了RAM的最后,中間夾著存放靜態(tài)/全局變量的“RW/ZI區(qū)域”,這也是“兩面包夾芝士”模型(或者“三明治”模型)名稱的由來。這樣的安排也徹底杜絕了棧和堆對(duì)“RW/ZI區(qū)域”發(fā)生入侵的可能。當(dāng)堆溢出時(shí),與棧類似,對(duì)大部分芯片來說都會(huì)觸發(fā)故障異常,從而在開發(fā)調(diào)試階段第一時(shí)間被我們所捕獲。

  • 通過鏈接腳本(比如Arm Compiler的Scatter Script或者gcc、clang的ld)的一些運(yùn)算功能,我們甚至可以做到“將剩下的空間全留給HEAP”,從而簡(jiǎn)化系統(tǒng)的配置。

【Arm官方低調(diào)推薦的”新“方法】


其實(shí),Arm Compiler 在很久之前就逐步淘汰了“大聰明的單段對(duì)向生長(zhǎng)模型”,而“兩段模型”早已成為主流。比如,我們?cè)趨R編啟動(dòng)文件中經(jīng)常可以見到這樣的代碼片段:

f872ee50-6f92-11ed-8abf-dac502259ad0.png

這就是“兩段式”模型的證據(jù)。實(shí)際上,在啟動(dòng)代碼的尾部,匯編程序通過:

IMPORT __use_two_region_memory

選擇了對(duì)兩段式模型提供支持的libc庫(kù):

f8823e96-6f92-11ed-8abf-dac502259ad0.png

看過我前面一期文章《【嵌入式秘術(shù)】Cortex-M靜態(tài)鏈接庫(kù)——從入坑到入土》的小伙伴一定會(huì)眼前一亮——“原來是這樣啊,我們其實(shí)是手動(dòng)選擇了對(duì)應(yīng)兩段式堆棧模型的庫(kù)版本呢”。 問題是,我們要如何在Arm Compiler環(huán)境下實(shí)現(xiàn)“兩面包夾芝士”模型呢?我們需要寫匯編代碼么? 不用擔(dān)心,即便你的啟動(dòng)文件是匯編的,具體操作方法也非常簡(jiǎn)單。步驟如下: 步驟一:準(zhǔn)備階段

注意:此步驟只針對(duì)使用匯編啟動(dòng)文件的情況。如果你的啟動(dòng)文件是C,則可跳過該步驟。

在工程管理器中找到你的匯編啟動(dòng)文件,它通常以

startup_<芯片型號(hào)>.s
的形式命名:

f8a553b8-6f92-11ed-8abf-dac502259ad0.png

找到配置棧和堆大小的部分(紅框標(biāo)注的部分):

f8bf15f0-6f92-11ed-8abf-dac502259ad0.png

將其整體刪除(或者注釋掉)。注意:請(qǐng)保留這里的 PRESERVE8和THUMB部分。

繼續(xù)移動(dòng)到匯編文件的尾部,找到如下的代碼:

f8cfc5da-6f92-11ed-8abf-dac502259ad0.png

同理,將其刪除(或者注釋掉)。注意:這里要保留 END 。

移動(dòng)到中斷向量表的定義處:

f8eee49c-6f92-11ed-8abf-dac502259ad0.png

將紅框中所標(biāo)注的代碼選中:
__VectorsDCD__initial_sp
替換為如下內(nèi)容:
                IMPORT   |Image$$ARM_LIB_STACK$$ZI$$Limit|


__VectorsDCD|Image$$ARM_LIB_STACK$$ZI$$Limit|
即:

f9063ad4-6f92-11ed-8abf-dac502259ad0.png

保存啟動(dòng)文件。

此時(shí),如果你著急編譯,當(dāng)你當(dāng)你開啟了microLib時(shí),很可能會(huì)看到如下的鏈接錯(cuò)誤:

f90fa718-6f92-11ed-8abf-dac502259ad0.png

即:
Error: L6218E: Undefined symbol __initial_sp (referred from entry2.o).
或者你沒有開啟 microLib,則會(huì)看到一個(gè)不同的錯(cuò)誤:

f926fd28-6f92-11ed-8abf-dac502259ad0.png

即:
Error: L6915E: Library reports error: The semihosting __user_initial_stackheap cannot reliably set up a usable heap region if scatter loading is in use
這都是正常的,不必驚慌。這類錯(cuò)誤會(huì)在完成后面的步驟后自然消失。
步驟二:獲取鏈接腳本(Scatter Script)

打開工程配置窗口“Options for Target”,切換到“Linker”選項(xiàng)卡:

f93274fa-6f92-11ed-8abf-dac502259ad0.png

首先,一定要確保你勾選了圖中的“Use Memory Layout from Target Dialog”選項(xiàng)。在這一前提下,再次取消對(duì)它的勾選:

f93f09cc-6f92-11ed-8abf-dac502259ad0.png

我們會(huì)看到,MDK基于當(dāng)前的Memory Layout,為我們?cè)?span style="color:rgb(61,170,214);">Out目錄下生成了一個(gè)與工程同名的鏈接腳本(比如圖中的工程名叫example,因此生成的鏈接腳本為 example.sct)。

單擊 Edit 按鈕,可以看到腳本的內(nèi)容:

f95980e0-6f92-11ed-8abf-dac502259ad0.png

先別著急半路開香檳——該文件是系統(tǒng)自動(dòng)生成的,如果我們不移動(dòng)它的位置,那么只要哪次手抖勾選了“Use Memory Layout from Target Dialog”,它的內(nèi)容就會(huì)立即被覆蓋掉——意味著我們?cè)诤罄m(xù)步驟中所做的改就會(huì)付諸東流。

為了避免該問題,應(yīng)該將它從 Out 目錄中移動(dòng)到工程目錄下。具體步驟為,右鍵單擊腳本文件名:

f9790492-6f92-11ed-8abf-dac502259ad0.png

選擇“Open Container Folder”來打開文件所在目錄:

f991f6a0-6f92-11ed-8abf-dac502259ad0.png

找到Scatter Script腳本文件后,將其拷貝到上一級(jí)目錄下(也就是工程目錄):

f9a82704-6f92-11ed-8abf-dac502259ad0.png

重新打開工程配置窗口:

f9c4323c-6f92-11ed-8abf-dac502259ad0.png

確保我們“沒有”選中“Use Memory Layout from Target Dialog”選項(xiàng),并在Scatter File文本框中直接填寫我們剛剛拷貝出來的腳本文件名(由于我們直接放在工程目錄下,因此這里直接用相對(duì)路徑"./example.scat"或者"example.scat"就行)。單擊OK保存配置。 步驟三:在鏈接腳本中部署堆和棧

在編輯器中打開我們的腳本文件:

f9de471c-6f92-11ed-8abf-dac502259ad0.png

圖中選中的部分實(shí)際上包含了RAM中的所有內(nèi)容,包括靜態(tài)變量、全局變量、棧和堆:

f9f632c8-6f92-11ed-8abf-dac502259ad0.png

是的,你的猜測(cè)沒錯(cuò):當(dāng)我們沒有特別說明時(shí),Stack和Heap都以ZI的形式存在于上述空間內(nèi),其位置任由Linker擺布——這當(dāng)然也帶來了很多不確定性

接下來我們要做的就是按照我們的設(shè)計(jì)——“兩面包夾芝士”來明確的指定棧和隊(duì)列的大小和位置:

f79f234a-6f92-11ed-8abf-dac502259ad0.png

我們要做的是首先將一個(gè)名為ARM_LIB_STACK的execution region放置到RAM的起始位置:
  ARM_LIB_STACK 0x20000000 ALIGN 8 EMPTY 0x800 {}
這里:
  • 起始地址是 0x20000000
  • STACK的大小是 0x800
  • ALIGN 8 指定對(duì)齊是8個(gè)字節(jié)
  • EMPTY是必須要保留的,它用來說明 ARM_LIB_STACK 是一個(gè)大數(shù)組,里面默認(rèn)填充了0。
  • 如果你想修改填充的內(nèi)容還可以通過關(guān)鍵字 FILL <填充值> 來指定填充的32bit數(shù)值,比如:
 ARM_LIB_STACK 0x20000000 ALIGN 8 FILL 0xDEADBEEF EMPTY 0x800 {}
它實(shí)現(xiàn)了往0x20000000開始的0x800(2KB)大小的棧空間中填充0xDEADBEEF的功能:

fa3acb36-6f92-11ed-8abf-dac502259ad0.png

熟悉“水印法”測(cè)量棧用量的小伙伴一定大喜。

為了讓ZI/RW緊隨其后——放在STACK的后面,我們需要對(duì) RW_IRAM1 的描述進(jìn)行修改,即從:

RW_IRAM1 0x20000000 0x00020000  {
修改為:
RW_IRAM1+0{
即:

fa58cdca-6f92-11ed-8abf-dac502259ad0.png

這里,我們?cè)谠痉胖玫刂?strong>0x20000000的位置用"+0"表示“緊隨其后”,并刪除了原本的大小0x00020000——這樣做就是告訴編譯器“RW_IRAM1”不限制大小。

接下來,我們要用類似的方法緊隨 RW_IRAM1 之后放置名為 ARM_LIB_HEAP 的execution region——用來指定堆的位置和大小:

ARM_LIB_HEAP +0 ALIGN 8 EMPTY 0x200 {}
可以看到,這里與棧的設(shè)置方式幾乎一樣,而“+0”則同樣告訴linker:請(qǐng)將ARM_LIB_HEAP緊鄰前面的 RW_IRAM1 放置。最終的效果如下:
LR_IROM1 0x00000000 0x00040000  {    
  ER_IROM1 0x00000000 0x00040000  {  
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }


  ARM_LIB_STACK 0x20000000 ALIGN 8 EMPTY 0x800 {}


  ;RW_IRAM1 0x20000000 0x00020000  {  ; RW data
  RW_IRAM1 +0  {  ; RW data
   .ANY (+RW +ZI)
  }


ARM_LIB_HEAP+0ALIGN8EMPTY0x200{}
}

fa6bee5a-6f92-11ed-8abf-dac502259ad0.png

還記得我們前面刪除了原本對(duì)RW_IRAM1的尺寸限制(也就是0x0002000)么?這意味著,現(xiàn)階段的腳本文件對(duì)我們實(shí)際使用的RAM空間是沒有任何限制的——換句話說,如果超出了芯片實(shí)際的SRAM大小,編譯器也是不會(huì)報(bào)告錯(cuò)誤的。為了重新加入這一限制,我們可以在 ARM_LIB_HEAP的后面加入下面的語(yǔ)句:

ScatterAssert(ImageLimit(ARM_LIB_HEAP) <= 0x20000000 + 0x20000)
這里:
  • ScatterAssert() 是讓linker對(duì)括號(hào)中的內(nèi)容進(jìn)行檢查
  • ImageLimit() 是在編譯時(shí)刻獲得括號(hào)內(nèi)指定execution region的終止地址
  • 0x20000000+0x20000是例子中整個(gè)RAM的終止地址(這里假設(shè)RAM從0x20000000開始,大小是0x20000
  • 綜合來說,上述代碼的作用是在linker的鏈接階段計(jì)算HEAP的終止地址,確認(rèn)它是否落在了RAM的有效范圍內(nèi)。
如果超出了范圍,我們就會(huì)看到如下的編譯錯(cuò)誤:
Error:L6388E:ScatterAssertexpression(ImageLimit(ARM_LIB_HEAP)<=?0x20000000?+?0x20000)?failed?on?line?22?:?(0x20001220?<=?0x20020000)
最終效果如下:

fa772d10-6f92-11ed-8abf-dac502259ad0.png

對(duì)應(yīng)的“兩面包夾芝士”圖示如下:

fa927c96-6f92-11ed-8abf-dac502259ad0.png

編譯工程:

fabb7588-6f92-11ed-8abf-dac502259ad0.png

【“雖遲但到”的宏和頭文件】


是的,你猜沒錯(cuò),我們可以在鏈接腳本中使用編譯預(yù)處理,這意味著:
  • 我們可以使用宏
  • 我們可以include頭文件
  • 我們可以進(jìn)行條件編譯

具體方法并不難,只需要在鏈接腳本的“第一行”,注意一定要是第一行(Number One)——前面不能有任何內(nèi)容,空行或者注釋都不行——放置如下的內(nèi)容:

#! armclang --target=arm-arm-none-eabi -mcpu=cortex-m0 -E -xc

faf17d22-6f92-11ed-8abf-dac502259ad0.png

然后我們就可以在腳本文件中愉快地使用宏和include了。看到腳本中這么多的常數(shù)了么?地址啊、大小啊,這下都可以用宏替代了。比如:
#define RAM1_SIZE    0x00020000
#define RAM1_BASE    0x20000000
#define RAM1_LIMIT   (RAM1_BASE + RAM1_SIZE)


#define STACK_SIZE   0x800
#define HEAP_SIZE    0x200

fb145658-6f92-11ed-8abf-dac502259ad0.png

其實(shí)我們還可以把宏的定義部分放置到專門的配置頭文件中——通過#include來包含——從而真正做到一個(gè)配置頭文件定天下。 至于宏可以有哪些騷操作,感興趣的小伙伴可以關(guān)注【裸機(jī)思維】公眾號(hào)后,發(fā)送關(guān)鍵字“”來獲取相關(guān)文章,這里就不再贅述。
需要注意的是:
  • 在較新版本的MDK中,上述方法“應(yīng)該”同時(shí)支持Arm Compiler 5(armcc)Arm Compiler 6(armclang)。你可以關(guān)注【裸機(jī)思維】公眾號(hào)后,發(fā)送關(guān)鍵字“MDK”來獲取最新的MDK。
對(duì)于某些較老的MDK來說,如果你使用的是 Arm Compiler 5,則需要把添加在 scatter script 第一行的命令行修改為:
#! armcc --cpu Cortex-M0 -E
...

以解決可能出現(xiàn)的編譯錯(cuò)誤。

  • 如果你的頭文件并沒有“直接”放置在工程目錄下,而是存在一個(gè)相對(duì)路徑,則可以通過在上述命令行中追加 -I <路徑> 的形式來告知編譯器去哪里搜索我們的頭文件。比如:
#!armclang--target=arm-arm-none-eabi-mcpu=cortex-m0-E-xc-I../../cfg

或者

#!armcc--cpuCortex-M0-E-I../../cfg

則是告訴編譯器從相對(duì)路徑 "../../cfg" 下去搜索頭文件。

  • 當(dāng)你通過修改頭文件的方式來更新scatter script的內(nèi)容后,第一次編譯,請(qǐng)務(wù)必一定要以“Rebuild All”的形式進(jìn)行,否則你的修改不會(huì)生效。

別說我沒提醒過你哦!


【如何把剩余的空間都留給堆】


很多時(shí)候,把剩余空間都留給堆是一個(gè)不錯(cuò)的想法,這樣“兩面包夾芝士”模型就獲得了和“單段相向生長(zhǎng)”模型一樣的優(yōu)勢(shì)——配置簡(jiǎn)單。由于我們已經(jīng)有了宏的幫助,借助 ImageLimit() 我們可以將 HEAP_SIZE 的宏定義修改為:
#define HEAP_SIZE    (RAM1_LIMIT - ImageLimit(RW_IRAM1))
它的意思是:用RAM1的終止地址減去 RW_IRAM1的終止地址,獲得中間的差額,其圖示如下:

fb5ca96c-6f92-11ed-8abf-dac502259ad0.png

看似完美,有的小伙伴一編譯就會(huì)報(bào)告如下的錯(cuò)誤:

fb6a5cd8-6f92-11ed-8abf-dac502259ad0.png

即:

Error: L6388E: ScatterAssert expression (ImageLimit(ARM_LIB_HEAP) <= (0x20000000 + 0x20000)) failed on line 29 : (0x20020004 <= 0x20020000)

奇怪,我們的計(jì)算公式應(yīng)該沒錯(cuò)啊——Heap的尺寸應(yīng)該就是使用整個(gè) RAM的終止地址減去 RW_IRAM1 的終止地址啊,為什么提示差4個(gè)字節(jié)呢?

聰明的小伙伴一定已經(jīng)注意到了,我們?cè)?ARM_LIB_HEAP 的定義中,指定了其首地址的對(duì)齊為8字節(jié):

ARM_LIB_HEAP +0 ALIGN 8 EMPTY HEAP_SIZE {}

RW_IRAM1 的尺寸不一定是8的整倍數(shù),當(dāng)它只是“4的整倍數(shù)”而不滿足“8的整倍數(shù)”這一條件時(shí),ImageLimit(RW_IRAM1)的后面與 ARM_LIB_HEAP的起始地址之間就會(huì)產(chǎn)生一個(gè)4字節(jié)的氣泡

fb75b5c4-6f92-11ed-8abf-dac502259ad0.png

要解決這一問題也很簡(jiǎn)單,我們可以使用 scatter script 腳本為我們提供的一個(gè)專門來進(jìn)行地址對(duì)齊的函數(shù):

AlignExpr(<地址數(shù)值>,<對(duì)齊要求>)

比如:

AlignExpr(ImageLimit(RW_IRAM1), 8)

就表示對(duì) RW_IRAM1 的終止地址進(jìn)行 8 字節(jié)對(duì)齊。借助它的幫助,我們可以修改腳本如下:

#defineHEAP_SIZE
    (RAM1_LIMIT-AlignExpr(ImageLimit(RW_IRAM1),8))

即:

fb8d26e6-6f92-11ed-8abf-dac502259ad0.png

再編譯時(shí),已然沒有問題。

【如何隨時(shí)隨地的了解棧的最大使用情況】


水印法是實(shí)現(xiàn)“最大棧用量統(tǒng)計(jì)”的最有效方式。其原理也不復(fù)雜:

  1. 先用指定的水印常數(shù)(比如 0xDEADBEEF)將整個(gè)棧填滿;
  2. 從棧空間的最初頂部(棧存儲(chǔ)空間的終止地址)向下開始搜索之前填充的水印常數(shù)——一旦碰到水印,就將當(dāng)前已經(jīng)經(jīng)歷過的RAM總量作為棧的最大深度(最大用量);

fbf32b30-6f92-11ed-8abf-dac502259ad0.png

對(duì)于步驟1來說,可以通過前面介紹的 FILL 關(guān)鍵字來完成對(duì)棧空間的填充:
ARM_LIB_STACK0x20000000ALIGN8FILL0xDEADBEEFEMPTYSTACK_SIZE
{
}

然后借助下面的代碼完成統(tǒng)計(jì)工作:

#if defined(__clang__)
#   pragma clang diagnostic push
#   pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
#   pragma clang diagnostic ignored "-Wdouble-promotion"
#endif
uint32_t calculate_stack_usage_topdown(void)
{
    extern uint32_t Image$$ARM_LIB_STACK$$Limit[];
    extern uint32_t Image$$ARM_LIB_STACK$$Length;


    uint32_t *pwStack = Image$$ARM_LIB_STACK$$Limit;
    uint32_t wStackSize = (uintptr_t)&Image$$ARM_LIB_STACK$$Length / 4;
    uint32_t wStackUsed = 0;




    do {
        if (*--pwStack == 0xDEADBEEF) {
            break;
        }
        wStackUsed++;
    } while(--wStackSize);
    
    
    printf("
Stack Usage: [%d/%d] %2.2f%%
", 
            wStackUsed * 4, 
            (uintptr_t)&Image$$ARM_LIB_STACK$$Length,
            (   (float)wStackUsed * 400.0f 
            /   (float)(uintptr_t)&Image$$ARM_LIB_STACK$$Length));


    return wStackUsed * 4;
}
#if defined(__clang__)
#   pragma clang diagnostic pop
#endif

這里有幾點(diǎn)需要說明一下:

  • armlink 為我們提供了通用的語(yǔ)法來獲取 execution region 的起始地址、大小和終止地址:

extern uint32_t Image$$$$Base[];
externuint32_tImage$$$$Length;
extern uint32_t Image$$$$Limit[];

這里,BaseLimit被定義成了不定長(zhǎng)數(shù)組的形式,因此我們可以直接把它們當(dāng)做常量指針來使用——獲取所需的地址。Length被定義成了一個(gè)普通的uint32_t型的變量,按照官方文檔的要求,雖然很反直覺,但如果要獲取它的值——也就是對(duì)應(yīng)execution region的大小,必須要對(duì)其進(jìn)行&操作,并隨后強(qiáng)制轉(zhuǎn)化為整形數(shù)值。這么說也許有點(diǎn)抽象,不妨對(duì)照前面的代碼來看:

#include 
...
extern uint32_t Image$$ARM_LIB_STACK$$Limit[];
extern uint32_t Image$$ARM_LIB_STACK$$Length;


uint32_t *pwStack = Image$$ARM_LIB_STACK$$Limit;
uint32_t wStackSize = (uintptr_t)&Image$$ARM_LIB_STACK$$Length / 4;

這里,我們通過 Image$ARM_LIB_STACK$$Limit[] 將棧的終止地址賦值給了(uint32_t *)型的指針 pwStack。以表達(dá)式 (uintptr_t)&Image$$ARM_LIB_STACK$$Length 獲取了 ARM_LIB_STACK 的實(shí)際大小。

  • 普通情況下,在變量名中使用 “$” 會(huì)在Arm Compiler 6引發(fā)警告:

warning:'$'inidentifier[-Wdollar-in-identifier-extension]

為了讓編譯器閉嘴,我們臨時(shí)對(duì)函數(shù) calculate_stack_usage_topdown() 在編譯時(shí)刻做了屏蔽warning的操作:

#if defined(__clang__)
#   pragma clang diagnostic push
#   pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
#   pragma clang diagnostic ignored "-Wdouble-promotion"
#endif
uint32_t calculate_stack_usage_topdown(void)
{
    ...
}
#if defined(__clang__)
#pragmaclangdiagnosticpop
#endif

-Wdouble-promotion 則是由printf中的百分比運(yùn)算引起的,一并屏蔽即可。

在任意時(shí)刻,當(dāng)我們想要知道當(dāng)前系統(tǒng)的最大棧用量時(shí),可以直接調(diào)用函數(shù) calculate_stack_usage_topdown(),比如:
int main(void)
{
    ...
calculate_stack_usage_topdown();
...
}

一個(gè)可能的執(zhí)行結(jié)果如下:

fc1dcf66-6f92-11ed-8abf-dac502259ad0.png

自上而下統(tǒng)計(jì)棧用量的方法優(yōu)點(diǎn)是:當(dāng)棧空間很大而實(shí)際棧用量較小時(shí),可以較快的完成統(tǒng)計(jì);缺點(diǎn)是:如果恰好棧里因?yàn)槿魏卧颍ū热缬脩舳x了一個(gè)局部變量,然后恰好給他賦予了我們的水印常數(shù)),就會(huì)造成統(tǒng)計(jì)錯(cuò)誤——沒能實(shí)際獲得最大深度。

針對(duì)這一問題,我們可以修改搜索策略,從占空間的起始地址(也就是基地址)處向上搜索“非水印常數(shù)”——一旦碰到,就可以用已知的棧空間尺寸減去已經(jīng)經(jīng)歷過的RAM總量作為棧的最大深度(最大用量)。

fc3ff47e-6f92-11ed-8abf-dac502259ad0.png

該方法的優(yōu)點(diǎn)是:不容易發(fā)生誤判;缺點(diǎn)是:當(dāng)棧空間很大而實(shí)際棧用量較小時(shí)往往較為耗時(shí)。對(duì)應(yīng)的代碼如下:

#if defined(__clang__)
#   pragma clang diagnostic push
#   pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
#   pragma clang diagnostic ignored "-Wdouble-promotion"
#endif
uint32_t calculate_stack_usage_bottomup(void)
{
    extern uint32_t Image$$ARM_LIB_STACK$$Base[];
    extern uint32_t Image$$ARM_LIB_STACK$$Length;


    uint32_t *pwStack = Image$$ARM_LIB_STACK$$Base;
    uint32_t wStackSize = (uintptr_t)&Image$$ARM_LIB_STACK$$Length;
    uint32_t wStackUsed = wStackSize / 4;


    do {
        if (*pwStack++ != 0xDEADBEEF) {
            break;
        }
    } while(--wStackUsed);
    
    printf("
Stack Usage: [%d/%d] %2.2f%%
", 
            wStackUsed * 4, 
            wStackSize,
((float)wStackUsed*400.0f/(float)wStackSize));


    return wStackUsed * 4;
}
#if defined(__clang__)
#   pragma clang diagnostic pop
#endif

【后記】


在這篇文章中,我們介紹了棧和堆在存儲(chǔ)器中的常見排布模型,比較了它們的優(yōu)劣,并提出了一種被稱為“兩面包夾芝士”的兩段式模型。該模型:
  • 可以有效避免堆棧溢出破壞常規(guī)變量
  • 溢出發(fā)生時(shí)可以在大部分芯片中第一時(shí)間觸發(fā)異常——被我們捕捉到

后面,我們以MDK為例介紹了如何在Arm Compiler環(huán)境下應(yīng)用這一模型,并引入了使用宏對(duì)其進(jìn)行進(jìn)一步拓展的方法。

值得說明的是,這一方法對(duì)Arm Compiler 5(armcc)Arm Compiler 6(armclang)同樣適用。支持MicroLib和非MicroLib的情況。無論啟動(dòng)文件是否為匯編,都可以正常工作。

實(shí)際上,使用鏈接腳本而非匯編啟動(dòng)文件來對(duì)兩段式堆棧模型進(jìn)行配置是Arm公司一直以來所提倡的。隨著Arm Compiler 6的逐步普及,更多的芯片公司正在追隨Arm的腳步將原本的匯編啟動(dòng)文件替換為 CMSIS 目錄下所提倡的純C語(yǔ)言啟動(dòng)文件。

作為【反復(fù)橫跳】系列的一部分,我希望通過這篇文章能幫助大家掃清從Arm Compiler 5Arm Compiler 6過渡圖中與棧相關(guān)的障礙。希望對(duì)你有所幫助。


審核編輯 :李倩


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

    關(guān)注

    5096

    文章

    19199

    瀏覽量

    308165
  • 堆棧
    +關(guān)注

    關(guān)注

    0

    文章

    182

    瀏覽量

    19841
  • 變量
    +關(guān)注

    關(guān)注

    0

    文章

    613

    瀏覽量

    28481

原文標(biāo)題:99%開發(fā)者從未聽說過的堆棧模型

文章出處:【微信號(hào):pzh_mcu,微信公眾號(hào):痞子衡嵌入式】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    2024年AI開發(fā)者中間件工具生態(tài)全面總結(jié)

    最近,開源中國(guó) OSCHINA、Gitee 與 Gitee AI?聯(lián)合發(fā)布了《2024 中國(guó)開源開發(fā)者報(bào)告》。 報(bào)告聚焦 AI 大模型領(lǐng)域,對(duì)過去一年的技術(shù)演進(jìn)動(dòng)態(tài)、技術(shù)趨勢(shì)、以及開源開發(fā)者生態(tài)數(shù)據(jù)
    的頭像 發(fā)表于 02-14 09:45 ?65次閱讀

    2024 RT-Thread開發(fā)者大會(huì)精彩回顧

    近30位重量級(jí)嘉賓,圍繞AI、大模型、虛擬化、工業(yè)應(yīng)用、車控、PLC、智能設(shè)備開發(fā)等熱點(diǎn)主題進(jìn)行了分享,吸引了近千名來自全球的開發(fā)者和企業(yè)家。本次大會(huì)上還重磅發(fā)布
    的頭像 發(fā)表于 01-21 18:53 ?173次閱讀
    2024 RT-Thread<b class='flag-5'>開發(fā)者</b>大會(huì)精彩回顧

    開發(fā)者的開源鴻蒙故事

    近日,在以“一切為了開發(fā)者”為主題的“2024開放原子開發(fā)者大會(huì)暨首屆開源技術(shù)學(xué)術(shù)大會(huì)”上,開源鴻蒙5.0 Release版本正式發(fā)布,備受各方關(guān)注。該版本在系統(tǒng)完備度、分布式創(chuàng)新、開發(fā)者體驗(yàn)以及系統(tǒng)穩(wěn)定性等方面均實(shí)現(xiàn)了顯著提升
    的頭像 發(fā)表于 01-06 10:28 ?209次閱讀

    《HarmonyOS第一課》煥新升級(jí),賦能開發(fā)者快速掌握鴻蒙應(yīng)用開發(fā)

    開發(fā)輕量型APP或元服務(wù)。課程涵蓋Stage模型進(jìn)階、多線程編程、組件狀態(tài)管理等核心技能,讓開發(fā)者能獨(dú)立完成多個(gè)模塊業(yè)務(wù)及多個(gè)子系統(tǒng)等復(fù)雜模塊開發(fā)與配置工作。 高級(jí)課程旨在塑造系統(tǒng)
    發(fā)表于 01-02 14:24

    我國(guó)軟件開發(fā)者數(shù)量突破940萬

    。目前,開源已覆蓋軟件開發(fā)的所有場(chǎng)景,全球97%的軟件開發(fā)者99%的企業(yè)使用開源軟件。 工業(yè)和信息化部總工程師 謝少鋒:我國(guó)已經(jīng)成為全球開源參與數(shù)量排名第二,增長(zhǎng)速度最快的國(guó)家,開
    的頭像 發(fā)表于 12-24 13:52 ?205次閱讀

    NVIDIA Jetson Orin Nano開發(fā)者套件的新功能

    生成式 AI 領(lǐng)域正在迅速發(fā)展,每天都有新的大語(yǔ)言模型(LLM)、視覺語(yǔ)言模型(VLM)和視覺語(yǔ)言動(dòng)作模型(VLA)出現(xiàn)。為了在這一充滿變革的時(shí)代保持領(lǐng)先,開發(fā)者需要一個(gè)足夠強(qiáng)大的平臺(tái)
    的頭像 發(fā)表于 12-23 12:54 ?362次閱讀
    NVIDIA Jetson Orin Nano<b class='flag-5'>開發(fā)者</b>套件的新功能

    云端AI開發(fā)者工具怎么用

    云端AI開發(fā)者工具通常包括代碼編輯器、模型訓(xùn)練平臺(tái)、自動(dòng)化測(cè)試工具、代碼管理工具等。這些工具不僅降低了AI開發(fā)的門檻,還極大地提高了開發(fā)效率和模型
    的頭像 發(fā)表于 12-05 13:31 ?195次閱讀

    2024 VDC人工智能會(huì)場(chǎng):全新藍(lán)心大模型矩陣,助力開發(fā)者高效創(chuàng)新

    2024 vivo開發(fā)者大會(huì)于10月10日在廣東深圳正式召開,vivo發(fā)布自研大模型——全新藍(lán)心大模型矩陣,為用戶和開發(fā)者帶來諸多驚喜。在同日舉辦的人工智能會(huì)場(chǎng)上,vivo AI團(tuán)隊(duì)分
    發(fā)表于 10-12 14:03 ?220次閱讀
    2024 VDC人工智能會(huì)場(chǎng):全新藍(lán)心大<b class='flag-5'>模型</b>矩陣,助力<b class='flag-5'>開發(fā)者</b>高效創(chuàng)新

    KaihongOS 4.1.2開發(fā)者預(yù)覽版正式上線,誠(chéng)邀開發(fā)者免費(fèi)試用!

    深開鴻在2024開放原子開源生態(tài)大會(huì)上正式宣布KaihongOS4.1.2開發(fā)者預(yù)覽版全面上線,并向全球開發(fā)者開放免費(fèi)下載。作為KaihongOS不斷創(chuàng)新與發(fā)展的重要里程碑,此次預(yù)覽版為開發(fā)者提供了
    的頭像 發(fā)表于 09-28 08:07 ?420次閱讀
    KaihongOS 4.1.2<b class='flag-5'>開發(fā)者</b>預(yù)覽版正式上線,誠(chéng)邀<b class='flag-5'>開發(fā)者</b>免費(fèi)試用!

    KaihongOS 4.1.2開發(fā)者預(yù)覽版正式上線,誠(chéng)邀開發(fā)者免費(fèi)試用!

    今日,深開鴻在2024開放原子開源生態(tài)大會(huì)上正式宣布KaihongOS 4.1.2開發(fā)者預(yù)覽版全面上線,并向全球開發(fā)者開放免費(fèi)下載。作為KaihongOS不斷創(chuàng)新與發(fā)展的重要里程碑,此次預(yù)覽版為
    的頭像 發(fā)表于 09-26 15:59 ?524次閱讀

    NVIDIA NIM 革命性地改變模型部署,將全球數(shù)百萬開發(fā)者轉(zhuǎn)變?yōu)樯墒?AI 開發(fā)者

    和測(cè)試 ? COMPUTEX—2024 年 6 月 2 日— NVIDIA 于今日宣布,全球 2,800 萬開發(fā)者現(xiàn)可下載 NVIDIA NIM?——一種推理微服務(wù),通過經(jīng)優(yōu)化的容器的形式提供模型——以
    發(fā)表于 06-03 09:12 ?268次閱讀
    NVIDIA NIM 革命性地改變<b class='flag-5'>模型</b>部署,將全球數(shù)百萬<b class='flag-5'>開發(fā)者</b>轉(zhuǎn)變?yōu)樯墒?AI <b class='flag-5'>開發(fā)者</b>

    2024 TUYA全球開發(fā)者大會(huì)盛大啟幕,Cube AI大模型重磅首發(fā)!

    2024TUYA全球開發(fā)者大會(huì)上,重磅推出AI大模型、AI開發(fā)工具、AI小程序開發(fā)基座等重量級(jí)產(chǎn)品,讓AI價(jià)值真正落地。01重磅發(fā)布涂鴉的首個(gè)AI大
    的頭像 發(fā)表于 05-31 08:15 ?421次閱讀
    2024 TUYA全球<b class='flag-5'>開發(fā)者</b>大會(huì)盛大啟幕,Cube AI大<b class='flag-5'>模型</b>重磅首發(fā)!

    2024 TUYA全球開發(fā)者大會(huì)盛大啟幕,Cube AI大模型重磅首發(fā)!

    2024 TUYA全球開發(fā)者大會(huì)上,重磅推出AI大模型、AI開發(fā)工具、AI小程序開發(fā)基座等重量級(jí)產(chǎn)品,讓AI價(jià)值真正落地。 01 重磅發(fā)布涂鴉的首個(gè)AI大
    發(fā)表于 05-30 09:13 ?253次閱讀
    2024 TUYA全球<b class='flag-5'>開發(fā)者</b>大會(huì)盛大啟幕,Cube AI大<b class='flag-5'>模型</b>重磅首發(fā)!

    “云天天書”大模型亮相昇騰開發(fā)者大會(huì),加速大模型解決方案落地

    5月9日-11日,2024年鯤鵬昇騰開發(fā)者大會(huì)在北京成功舉辦。本
    的頭像 發(fā)表于 05-14 09:36 ?555次閱讀
    “云天天書”大<b class='flag-5'>模型</b>亮相昇騰<b class='flag-5'>開發(fā)者</b>大會(huì),加速大<b class='flag-5'>模型</b>解決方案落地

    比無縫漫游更高級(jí)的無感漫游,你聽說過嗎?

    關(guān)于無縫漫游無縫漫游,很多人都聽說過。無縫漫游是指設(shè)備在移動(dòng)過程中穿越不同網(wǎng)絡(luò)覆蓋區(qū)域時(shí),能夠從一個(gè)接入點(diǎn)(AP)自動(dòng)切換到另一個(gè)接入點(diǎn),而無需中斷網(wǎng)絡(luò)服務(wù)或手動(dòng)進(jìn)行任何操作,確保網(wǎng)絡(luò)連接在移動(dòng)
    的頭像 發(fā)表于 03-28 08:15 ?1140次閱讀
    比無縫漫游更高級(jí)的無感漫游,你<b class='flag-5'>聽說過</b>嗎?
    主站蜘蛛池模板: 女bbbbxxxx毛片视频0 | 黄色大片三级 | 91av在线免费观看 | 全午夜免费一级毛片 | 午夜亚洲福利 | 夜天干天干啦天干天天爽 | 国内精品视频免费观看 | 亚洲swag精品自拍一区 | 欲色影院 | 五月天丁香婷婷网 | 一级特黄视频 | 精品国产你懂的在线观看 | 四虎影免看黄 | 男人午夜天堂 | 色偷偷88欧美精品久久久 | 国产一区二区三区美女在线观看 | 国产三片理论电影在线 | 欧美成人免费观看bbb | 天天草夜夜 | 永久在线观看www免费视频 | 欧美在线观看一区二区三 | www.九九热| 丁香激情六月 | 天天影视网天天影网 | 在线观看免费视频国产 | 亚洲高清一区二区三区 | 神马午夜嘿嘿嘿 | 高清一本之道加勒比在线 | 97影院3 | 成人网中文字幕色 | 久久99精品国产麻豆宅宅 | 天天干夜夜笙歌 | 羞羞色男人的天堂伊人久久 | 操熟逼 | 国产精品久久久久久久午夜片 | 99午夜高清在线视频在观看 | 综合色久| 国产叼嘿免费视频网站 | 欧美a欧美 | 亚洲成a人片在线观看www | 欧美老汉色 |