星期一, 6月 23, 2008

nop? 為什麼程式裡有nop?

今天心情不太好,就多寫一些。

常寫Firmware(韌體)的人應該很常看到這個指令。不是以組合語言的指令nop,就是以c函數_nop_()的方式存在。nop望文生義,是no operation的縮寫。可是電腦的存在就是要來計算的,什麼時候要用到nop呢?

第一個情況是用來做短暫的delay,尤於這種delay在不同時脈上很難計算,所以通常適用於短期的等候上,這種情形不在今天的討論內。

我想要提的是,尤於firmware經常要存取硬體暫存器(register),其實通常在先人或前輩的慘痛教訓之後,你會看到如下的程式。

int rega _at_ 0x800; // 這是fixed address hardware register
rega = 0x55;
_nop_();
rega = 0x80;


這程式是要對某個register下一連串的指令,但是為什麼會中間加一段nop?
先人慘痛教訓千萬不能隨意修改,看不懂的code決對不要去動,你如果覺得這段code毫無義意就輕易拿掉你就會發覺原本會跑的程式不會動了。甚至你拿起LA仔細量起timing都不會發覺為什麼有需要在兩行指令內加上delay,硬體的速度並沒有那麼慢,那這是為什麼呢?


rega = 0x55;
rega = 0x80;


大部份的原因是因為compiler是optimize過的,它認為rega一個記憶體變數,你先寫0x55, 下一行指令又直接設成0x80,夠聰明的編譯器就自動省略第一行指令。變成

rega = 0x80;


那當然不會運作,於是工程師就加上nop指令,讓不是那麼聰明的編譯器不要將這行指令優化省略掉。就是你看到的結果。

故事到這裡還沒有結束

對於解決這種問題加上nop並不是正解,那怎麼辦呢?你會碰到的問題聰明的工程師早就有人跟你遇到同樣的問題。C/C++ 為語言設計了volatile modifier。這是修飾data storage的保留字,宣告變數為volatile就是宣稱此變數為揮發性的,也就是它的值是有可能隨外在環境改變,相對一般變數,你只要一存入,不去改它它會一直保持住,這種變數是non-volatile,編譯器可以任意優化不用怕出錯。而針對這種可能會隨著時間變動的register,你可以宣告成volatile,強制編譯器每次看到此變數一定要去做動作,這樣才不會出錯。

所以我們的程式最終的結果變成如此

volatile int rega _at_ 0x800; // 這是fixed address hardware register
rega = 0x55;
rega = 0x80;


volatile modifier不只用在硬體變數,只要有變數在你的thread內執行期間內有可能被改變(或者是被另一個thread),都適用加上這個volatile關鍵字來確保你程式的正確執行。