ARM程序由于字節(jié)對齊引起的問題深入分析
用ADS的ARMC Complier下Optimization Level可能引起問題,其中的一個問題就是字節(jié)對齊的問題。下面講講問題的現(xiàn)象及實質(zhì)。
當時問題的現(xiàn)象是:程序使 用一公共變量Buf創(chuàng)建隊列,如果ADS編譯優(yōu)化選項采用Minium則軟件工作正常;源碼不變,如果采用ALL優(yōu)化,則不正常,數(shù)據(jù)紊亂且無法工作。為 了發(fā)現(xiàn)問題,我們分別用Minium和ALL編譯,在反匯編條件下單步跟蹤程序,觀察CPU寄存器和內(nèi)存變量的變化情況。發(fā)現(xiàn)在Minium模式下,編譯 器把隊列內(nèi)存塊Uart0TxBuf分配到的地址是0x400015cc,這個地址是一個4字節(jié)對齊的地址,而在ALL模式下,編譯器把Buf分配的地址 是0x400015c2,這個地址是一個非4字節(jié)對齊的地址。正是由于這個非4字節(jié)對齊的地址導致了問題的發(fā)生。
問題發(fā)生在QueueCreate(void *Buf, uint32 SizeOfBuf, uint8 (* ReadEmpty)(), uint8 (* WriteFull)())這個函數(shù)里,問題是如何發(fā)生的,
在了解問題發(fā)生的機理前,先了解QueueCreate這個函數(shù)的工作原理。QueueCreate工作原理是,首先把buf指向的內(nèi)存初始化為DataQueue格式的結(jié)構(gòu)體。DataQueue的結(jié)構(gòu)體格式如下:
typedef struct {
QUEUE_DATA_TYPE *Out; /* 指向數(shù)據(jù)輸出位置 */
QUEUE_DATA_TYPE *In; /* 指向數(shù)據(jù)輸入位置 */
QUEUE_DATA_TYPE *End; /* 指向Buf的結(jié)束位置 */
uint16 NData; /* 隊列中數(shù)據(jù)個數(shù) */
uint16 MaxData; /* 隊列中允許存儲的數(shù)據(jù)個數(shù) */
uint8 (* ReadEmpty)(); /* 讀空處理函數(shù) */
uint8 (* WriteFull)(); /* 寫滿處理函數(shù) */
QUEUE_DATA_TYPE *Buf; /* 存儲數(shù)據(jù)的空間 */
} DataQueue;
從結(jié)構(gòu)體可以看出,結(jié)構(gòu)體字節(jié)類型在內(nèi)存分配為: 4字節(jié)指針變量(*Out)、4字節(jié)指針變量(*In)、4字節(jié)指針變量(*End)、2字節(jié)變量NData、2字節(jié)變量MaxData、4字節(jié)函數(shù)指針 變量ReadEmpty()、4字節(jié)函數(shù)指針變量 WriteFull()。
觀察結(jié)構(gòu)體起始地址放在非對齊時會出現(xiàn)什么情況。
起始地址為0x400015c2時的由編譯器分配得到的地址 實際操作地址
*Out 0x400015c2~0x400015c5 0x40015c0~0x400015c3
*In 0x400014c6~0x400015c9 0x400014c4~0x400015c7
*End 0x400015ca~0x400015cd 0x400015c8~0x400015cb
從表中可以看出,實際操作的地址按照4字節(jié)對齊格式得到。例如,當執(zhí)行*Out進行操作時,自動屏蔽bit1和bit0,因此實際發(fā)生變化的是 0x40015c0~0x400015c3,而不是0x400015c2~0x400015c5,由于實際操作地址和編譯器分配地址互相覆蓋,當對*In 操作時,會導致*Out一起變化,對*End操作時,*In也跟著變化。正是由于非對齊的原因?qū)е聞?chuàng)建隊列和對列操作完全錯誤。
當內(nèi)存起始地址為4字節(jié)對齊地址的情況時,編譯器分配地址和實際地址一致,因此不存在上述問題。
結(jié) 論:
在ARM嵌入式系統(tǒng)中,當把一個內(nèi)存區(qū)域初始化為某個結(jié)構(gòu)體時,必須注意字節(jié)對齊的情況。如果該內(nèi)存起始地址為非對齊地址,不僅得不到預期的結(jié)果,還可能 導致一些很奇怪的讓人無法理解表面問題。在C層面上不太容易觀察到這些問題的實質(zhì),只有深入到匯編一層去分析程序,才可能理解這些現(xiàn)象的深層原因。
評論