2010-06-14 88 views
6

我正在使用基於x86的內核來操縱32位內存映射寄存器。只有當CPU產生32位寬的讀取和寫入該寄存器時,我的硬件才能正常工作。該寄存器在32位地址上對齊,並且在字節粒度上不可尋址。控制對存儲器映射寄存器的讀寫訪問寬度C

我該怎麼做才能保證我的C(或C99)編譯器只會在所有情況下生成完整的32位寬讀寫?

例如,如果我不喜歡這樣的讀 - 修改 - 寫操作:

volatile uint32_t* p_reg = 0xCAFE0000; 
*p_reg |= 0x01; 

我不希望編譯器耍小聰明的事實,只有底部字節的變化,併產生8- bit寬讀/寫。由於機器代碼對於x86上的8位操作通常更密集,所以我害怕出現不必要的優化。一般禁用優化不是一種選擇。

-----編輯-------
一個有趣和非常相關論文:http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf

+0

對不起「自我推銷」,但您可能會發現該項目對測試內存映射硬件或設置/讀取內存映射寄存器有用:https://code.google.com/p/jeeamtee/wiki/Main。此致,Valentin Heinitz – 2010-10-20 15:31:36

回答

6

您的疑慮包含在volatile限定符中。

6.7.3/6「類型限定符」雲:

具有volatile限定類型可能未知的方式實施或具有其他未知的副作用被修改的對象。因此,任何涉及這樣的對象的表達都應嚴格按照抽象機器的規則進行評估,如5.1.2.3所述。此外,在每個序列點上,最後存儲在對象中的值應與抽象機器規定的值一致,但前面提到的未知因素會對其進行修改。對具有易失性限定類型的對象的訪問是由實現定義的。

5.1.2.3「計劃執行」說(除其他事項外):

在抽象的機器,由語義指定的所有表達式求值。

這之後,其通常被稱爲「AS-如果」規則,其允許實現不遵循抽象機語義的句子如果最終結果是一樣的:

如果實際實現可以推斷出它的值沒有被使用,並且沒有產生所需的副作用(包括由調用函數或訪問volatile對象引起的任何副作用),則實際實現不需要評估表達式的一部分。

但是,6.7.3/6本質上說,表達式中使用的volatile限定類型不能應用'as-if'規則 - 必須遵循實際的抽象機器語義。因此,如果取消指向易失性32位類型的指針,則必須讀取或寫入完整的32位值(取決於操作)。

0

好了,一般來說我不會期望它優化了高位字節,如果你將該寄存器鍵入爲32位易失性。由於使用volatile關鍵字,編譯器不能認爲高位字節的值是0x00。因此,即使您僅使用8位字面值,它也必須寫入完整的32位。我在0x86或Ti處理器或其他嵌入式處理器上從未遇到過這個問題。一般來說volatile關鍵字就足夠了。唯一的一點是,如果處理器本身不支持您要編寫的字大小,但這對32位數字的0x86不應該是個問題。

儘管編譯器可以生成使用4位寫入的指令流,但這不會是單個32位寫入時的處理器時間或指令空間的優化。

+0

volatile限定符不會阻止編譯器將訪問寬度從32位變窄爲8位。從它的角度來看,高24位易失性位未被觸及。而且,8位指令編碼導致更少的指令字節,所以-Os優化有理由偏好這一點。 – srking 2010-06-14 21:02:04

+0

它可以防止編譯器假定該值與讀取的值相同。它不能縮小訪問範圍,因爲它需要寫回所有的32位數據以保證這個值是假設的。 – NoMoreZealots 2010-06-15 13:25:54

0

如果在訪問硬件時不使用字節(無符號字符)類型,編譯器將不會生成8位數據傳輸指令。

volatile uint32_t* p_reg = 0xCAFE0000; 
const uint32_t value = 0x01; // This trick tells the compiler the constant is 32 bits. 
*p_reg |= value; 

你將不得不讀端口爲32位的值,修改該值,然後寫回:

uint32_t reg_value = *p_reg; 
reg_value |= 0x01; 
*p_reg = reg_value; 
+0

同意,但尋找比「更好的機會」更強大的東西。 – srking 2010-06-14 21:02:28

4

只有這樣,才能保證編譯器會做正確的事情是用匯編語言編寫你的負載和存儲例程,並從C中調用它們。我多年來使用的編譯器中有100%可以並且會錯誤(包括GCC)。

有時候優化器會讓你獲得,例如你希望將一些常量在編譯器中看作是一個小數字0x10可以說,寫入一個32位寄存器,這就是你特別提出的問題以及我所看到的其他好東西編譯器試着去做。有些編譯器會決定用8位寫入代替32位寫入並更改指令更便宜。隨着編譯器試圖節省程序空間而不僅僅是內存週期,變量指令長度目標將會使這種情況變得更糟。 (xor ax,ax而不是mov eax,0例如)

隨着一些像gcc一樣不斷髮展的代碼,今天運行的代碼沒有明天的工作保證(甚至不能編譯某些版本的gcc gcc版本)。同樣,在你的辦公桌上使用編譯器的代碼可能不適用於其他人。

進行猜測和試驗,並創建加載和存儲功能。

這樣做的副作用是,如果/當您想以某種方式模擬您的代碼或讓代碼在應用程序空間而不是金屬上運行(反之亦然),則創建一個很好的抽象層,彙編程序功能可以替換爲模擬目標,也可以替換爲通過網絡與設備連接的目標的代碼等。

+0

我已經寫了15年的硬件接口,從來沒有寫過彙編器保證32位寫訪問。實際上,易失性告訴編譯器,它不能假定指令之間存儲器地址的先前值。 – NoMoreZealots 2010-06-15 13:41:49

+0

表示同意,但我失敗了。如果你將你的加載和存儲例程作爲一個獨立的.c文件從你調用它的那個加載和存儲例程,並且不內聯它們,並且不讓說llvm嘗試優化整個應用程序,那麼我會添加到我的評論中你可以避免使用匯編程序,並且可以可靠地工作 – 2010-06-15 19:34:15

+1

如果我們想玩這個遊戲,那麼我已經做了20多年,很多平臺上有很多編譯器。大多數情況下,它可以工作,但有時候你會陷入窘境,而你無法弄清楚編譯器爲什麼優化或改變你的代碼。它可以工作數週或數月,然後添加或更改第n行代碼,並改變編譯方式。用戶說保證,如果你不想要「保證工作」,而是「大多數時間工作」,說超過99%但低於100%,那麼易失性(在單獨的文件中的獨立功能)將滿足該要求。 – 2010-06-15 19:38:09

0

由於對硬件的讀 - 修改 - 寫操作對於幾條指令來說總是有很大的風險,所以大多數處理器都提供了一條指令,用一條不能中斷的單指令來操作寄存器/存儲器。

根據您操作的註冊類型,它可能會在您的修改階段更改,然後您會寫回一個錯誤值。

因爲dwelch建議在彙編中編寫自己的讀取 - 修改 - 寫入功能,所以我會推薦它。

我從來沒有聽說過優化類型的編譯器(進行類型轉換以優化目的)。如果它被聲明爲int32,它總是一個int32,並且將始終在內存中對齊。檢查你的編譯器文檔,看看各種優化是如何工作的。

我想我知道你的關注來自哪裏,結構。結構通常被填充到最佳對齊。這就是爲什麼你需要在它們周圍包裝一個#pragma pack()來讓它們字節對齊。

你可以單步穿過程序集,然後你會看到編譯器如何翻譯你的代碼。我很確定它沒有改變你的類型。