鴻蒙核心原始碼中文註解 >> 精讀核心原始碼,中文註解分析,深挖地基工程,大腦永久記憶,四大原始碼倉每日同步更新
#include <stdio.h>#include <math.h>struct reg{//引數遠超暫存器數量 int Rn[100]; int pc;};int framePoint(reg cpu){ return cpu.Rn[0] * cpu.pc;}int main(){ reg cpu; cpu.Rn[0] = 1; cpu.pc = 2; return framePoint(cpu);}
//編譯器: armv7-a gcc (9.2.1)framePoint(reg): sub sp, sp, #16 @申請棧空間 str fp, [sp, #-4]! @保護main函式棧幀,等同於push {fp} add fp, sp, #0 @fp變成framePoint棧幀,同時也指向了棧頂 add ip, fp, #4 @定位到入棧口,讓4個引數依次入棧 stm ip, {r0, r1, r2, r3}@r0-r3依次入棧儲存 ldr r3, [fp, #4] @取值cpu.pc = 2 ldr r2, [fp, #404] @取值cpu.Rn[0] = 1 mul r3, r2, r3 @cpu.Rn[0] * cpu.pc mov r0, r3 @返回值由r0儲存 add sp, fp, #0 @重置sp,和add fp, sp, #0配套出現 ldr fp, [sp], #4 @恢復main函式棧幀 add sp, sp, #16 @歸還棧空間,sp回落到main函式棧頂位置 bx lr @跳回main函式main: push {fp, lr} @入棧儲存呼叫函式現場 add fp, sp, #4 @fp指向sp+4,即main棧幀的底部 sub sp, sp, #800 @分配800個線性地址,即main棧幀的頂部 mov r3, #1 @r3 = 1 str r3, [fp, #-408] @將1放置 fp-408處,即:cpu.Rn[0]處 mov r3, #2 @r3 = 2 str r3, [fp, #-8] @將2放置 fp-8處,即:cpu.pc mov r0, sp @r0 = sp sub r3, fp, #392 @r3 = fp - 392 mov r2, #388 @只複製388,剩下4個由暫存器傳參 mov r1, r3 @儲存由r1儲存r3,用於memcpy bl memcpy @複製結構體部分內容,將r1的內容複製r2的數量到r0 sub r3, fp, #408 @定位到結構體剩餘未複製處 ldm r3, {r0, r1, r2, r3} @將剩餘結構體內容透過暫存器傳參 bl framePoint(reg) @跳轉到framePoint執行 mov r3, r0 @framePoint的返回值先給r3 nop @用於程式指令的對齊 mov r0, r3 @再將返回值給r0 sub sp, fp, #4 @恢復SP值 pop {fp, lr} @出棧恢復呼叫函式現場 bx lr @跳回呼叫函式
兩個函式對應兩段彙編,乾淨利落,去除中間各項干擾,只有一個結構體reg,以下詳細講解如何傳遞它,以及它在棧中的資料變化是怎樣的?
入參方式結構體總共101個棧空間(一個棧空間單位四個位元組),對應就是404個線性地址.main上來就申請了 sub sp, sp, #800 @申請800個線性地址給main,即 200個棧空間
int main(){ reg cpu; cpu.Rn[0] = 1; cpu.pc = 2; return framePoint(cpu);}
但main函式只有一個變數,只需101個棧空間,其他都算上也用不了200個.為什麼要這麼做呢?而且注意下里面的數字 388, 408, 392 這些都是什麼意思?看完main彙編能得到一個結論是 200個棧空間中除了存放了main函式本身的變數外 ,還存放了要傳遞給framePoint函式的部分引數值,存放了多少個?答案是 388/4 = 97個. 注意變數沒有共用,而是複製了一部分出來.如何複製的?繼續看
memcpy彙編呼叫 mov r0, sp @r0 = sp sub r3, fp, #392 @r3 = fp - 392 mov r2, #388 @只複製388,剩下4個由暫存器傳參 mov r1, r3 @儲存由r1儲存r3,用於memcpy bl memcpy @複製結構體部分內容,將r1的內容複製r2的數量到r0 sub r3, fp, #408 @定位到結構體剩餘未複製處 ldm r3, {r0, r1, r2, r3} @將剩餘結構體內容透過暫存器傳參
看這段彙編複製,意思是從r1開始位置複製r2數量的資料到r0的位置,注意只複製了 388個,也就是 388/4 = 97個棧空間.剩餘的4個透過暫存器傳的引數.ldm代表從fp-408的位置將記憶體地址的值連續的給r0 - r3暫存器,即位置(fp-396,fp-400,fp-404,fp-408)的值.執行下來的結果就是
r3 = fp-408, r2 = fp-404 ,r1 = fp-400 ,r0 = fp-396 得到虛擬地址的值,這些值整好是memcpy沒有複製到變數剩餘的值
逐句分析 framePoint
framePoint(reg): sub sp, sp, #16 @申請棧空間 str fp, [sp, #-4]! @保護main函式棧幀,等同於push {fp} add fp, sp, #0 @fp變成framePoint棧幀,同時也指向了棧頂 add ip, fp, #4 @定位到入棧口,讓4個引數依次入棧 stm ip, {r0, r1, r2, r3}@r0-r3入棧儲存 ldr r3, [fp, #4] @取值cpu.pc = 2 ldr r2, [fp, #404] @取值cpu.Rn[0] = 1 mul r3, r2, r3 @cpu.Rn[0] * cpu.pc mov r0, r3 @返回值由r0儲存 add sp, fp, #0 @重置sp,和add fp, sp, #0配套出現 ldr fp, [sp], #4 @恢復main函式棧幀 add sp, sp, #16 @歸還棧空間,sp回落到main函式棧頂位置 bx lr @跳回main函式
framePoint申請了4個棧空間目的是用來存放四個暫存器值的,以上彙編程式碼逐句分析.第一句: sub sp, sp, #16 @申請棧空間,用來存放r0-r3四個引數第二句: str fp, [sp, #-4]! @保護main的fp,等同於push {fp},為什麼這裡要把main函式的fp放到 [sp, #-4]! 位置,注意 !號,表示SP的位置要變動,因為這裡必須要保證引數的連續性.第三句: add fp, sp, #0 @指定framePoint的棧幀位置,同時指向了棧頂 SP第四句: add ip, fp, #4 @很關鍵,用了ip暫存器,因為此時 fp sp 都已經確定了,但別忘了 r0 - r3 還沒有呢.從哪個位置入棧呢, fp+4位置,因為 main函式的棧幀已經入棧了,在已經fp的位置.中間隔了四個空位,就是給 r0-r3留的.第五句: stm ip, {r0, r1, r2, r3}@r0-r3入棧,填滿了剩下的四個空位.第六句: ldr r3, [fp, #4] @取的就是cpu.pc = 2的值,因為上一句就是從這裡依次入棧的,最後一個當然就是cpu.pc了.第七句: ldr r2, [fp, #404] @取值cpu.Rn[0] = 1,其實這一句已經是跳到了main函式的棧幀取值了,所以看明白了沒有,並不是在傳統意義上理解的在framePoint的棧幀中取值.第八句: mul r3, r2, r3 @cpu.Rn[0] * cpu.pc 做乘法運算第九句: mov r0, r3 @返回值r0儲存運算結構, 目的是return第十句: add sp, fp, #0 @重置sp,其實這一句可以最佳化掉,因為此時sp = fp第十一句: ldr fp, [sp], #4 @恢復fp,等同於pop {fp},因為函式執行完了,需要回到main函數了,所以要拿到main的棧幀第十二句: add sp, sp, #16 @歸還棧空間,等於把四個入參抹掉了.最後一句: bx lr @跳回main函式,如此 fp 和 lr 暫存器中儲存的都是 main函式的資訊,就可以安全著陸了.
總結因為暫存器數量有限,所以只能透過這種方式來傳遞大的引數,想想也只能在main函式棧中儲存大部分引數,同時又必須確保資料的連續性,好像也只能用這種辦法了,一部分透過暫存器傳,一部分透過複製的方式倒是挺有意思的.
鴻蒙核心原始碼中文註解 >> 精讀核心原始碼,中文註解分析,深挖地基工程,大腦永久記憶,四大原始碼倉每日同步更新
最新評論