January 31, 2012

C 編譯器跟你想的不一樣

2008 年筆者在 COSCUP 發表了題目為「我是軟體 -- 那些處理器教我的事」的演講,探討因為處理器架構與系統軟體組態或假設不同,導致一連串令人意外的結果,著眼於涉及跨平台開發所潛藏有如未爆彈的種種議題。日前嘗試修改某個客戶的專案,沒想到又踩到另一個地雷,自詡是「慣 C」迷的筆者,萬萬沒想到還得交叉對照組合語言輸出,才克服問題,撰文分享如下。

考慮以下程式碼:(test.c)
#include <stdio.h>
#define DEBUG 1
#define DBG( ... ) \
    if (DEBUG) {  __VA_ARGS__; }
int main(int argc, char *argv[]) {
    char *num;
    switch (argc - 1) {
             case  0: num =  "zero";
        DBG( case  1: num =   "one"; )
        DBG( case  2: num =   "two"; )
        DBG( case  3: num = "three"; )
        DBG( default: num =  "many"; )
        while (--argc)
            printf("%s ", argv[argc]);
        printf("\nArgument count: %s\n", num);
        break;
    }
    return 0;
}
先不看 DBG() macro 的使用,似乎沒什麼特別之處,編譯並執行看看:
$ gcc -Wall -o test test.c
$ ./test 1 && ./test 1 2 && ./test 1 2 3 && ./test 1 2 3 4
1
Argument count: many
2 1
Argument count: many
3 2 1
Argument count: many
4 3 2 1
Argument count: many
完全符合預期,就是把 switch-case 走訪一遍,因為沒有 break 敘述,就到了最終的 "default" 條件,所以 char *nnum 指向字串 "many" 的記憶體位址。但倘若我們將第二行的 DEBUG 定義修改為以下:
#define DEBUG 0
再來重新編譯與執行看看:
$ gcc -Wall -o test test.c
$ ./test 1 && ./test 1 2 && ./test 1 2 3 && ./test 1 2 3 4
1
Argument count: one
2 1
Argument count: two
3 2 1
Argument count: three
4 3 2 1
Argument count: many
這是什麼狀況呢?被一串 if (0) { ... } 包圍的 switch-case 竟然會被處理,而且 break 巧妙地伴隨每個敘述出現。也就是說,原本的 "DBG( case 1: num = "one"; )" 相當於 break; case 1: num = "one; break; 的效力。像這樣的 C 語法結構,可歸納為 [Clifford's Device],簡單來說,就是符合以下的結構:
if (0) { label: ... }
引述網頁描述:
    "it is skipped in the normal flow of execution and is only reached via the goto label, reintegrating with the normal flow of execution and the end of the if (0) statement. It solves a situation where one would usually need to duplicate code or create a state variable holding the information if the additional code block should be called."
在普通的執行流程中,if (0) { ... } 包圍的陳述指令會被忽略,但倘若配合 goto 時 (本例是 switch-case,一個 C 語法的變形),就不是這麼一回事,翻譯成組合語言或機械碼時,其實是很清楚且常見的結構。這樣的結構對於縮減煩冗的程式碼有些特別用途。上述程式碼有趣之處,就在於像 DBG() 這類的 macro 在前期開發階段通常被設定為 "1",在後期則會逐步取消偵錯 / 驗證的程式碼時,將 macro 改為 if (0) { ... } 型態,而使開發者誤踩到「未爆彈」。
Posted by jserv at 1:21 PM | Comments (0)

January 19, 2012

演講:L4 microkernel 的背景知識與最新的研究發展

今年二月 7 日,小弟將分享關於 L4 microkernel 的背景知識與最新的研究發展,詳情請參考 [星系統社群:第5次星系主題-L4 microkernel 的背景知識與最新的研究發展]。

第一次見到 [L4 microkernel],是在 William Stallings 的 [作業系統叢書] 讀到,那時是 1997 年,僅提供七個系統呼叫但可搭建完整作業系統基礎的 microkernel,對當時還在唸高中的我,實在是不小的衝擊。高中住校沒有辦法使用電器設備,遑論使用網際網路,唯一獲取知識的方法,就是利用每天宿舍晚自習結束到關大門前的短暫一個半小時,去附近的書店啃書,William Stallings 的大作闡述作業系統非常透徹,而關於 L4 的部份,更激起我的興趣,但這類的書籍往往售出後,就很少進貨,而我身上根本沒什麼零錢,結果一時鋌而走險,竟然沒購買而擅自攜帶出去。這本書伴隨著我在高中的課餘生涯,也讓「系統程式」與我結下不解之緣。

像這樣去附近的店家「借取」(一開始真的有歸還,但後來就無疾而終) 了好幾本關於作業系統、編譯器設計、Linux / BSD 系統書籍的行徑,是高中生涯很特別的插曲,但「偷書」是事實,估計總價約台幣五千多元。後來接了案子,逐漸有能力償還時,這些店家要不倒閉,不然就是不接受我的賠償,於是愧疚了十多年。這幾年購書,往往會要求不打折,不然就是同一本書一次多買幾份,送給需要的朋友,此外,我持續地作免費的資訊技術分享,就是希望能多作點補償。因為談及 L4 microkernel,也抖出「偷書」的陳年往事,如今探討這個議題,自然是五味雜陳,本議程最早規劃在去年,追憶 L4 發明人 Jochen Liedtke 教授過世十週年,不過當時有事耽擱,只好在今年擇期探討。

L4 在上個世紀末證明,microkernel 的效能落差並非本質的限制,而是設計與實做的議題,也因此,L4 這個高效能的 microkernel 被稱為「第二代 microkernel」, 有鑑於 CMU Mach microkernel 的若干低效能表現 (如 IPC),L4 進一步做出精簡的設計,只 7 個系統呼叫,而且核心本身只有 12 Kbytes,只實現以下三個抽象概念的基礎建設:
  • Address space
  • Thread
  • IPC
藉由充分使用硬體機制來實現,大幅縮減 TLB 與 cache miss 的開銷。這十年來,無論是原本從事 microkernel 抑或 RTOS 開發的廠商與研究單位,則逐漸往虛擬化技術 (virtualization) 靠攏,愈來越多技術發生「大融通」的效應,而本議程則著眼於 L4 microkernel 與日益多元的應用方式,介紹其背景知識與發展,預定提綱如下:
  • Myths of Microkernel
  • Characteristics of second generation microkernel
  • Key services: memory, thread, IPC management
  • L4 in real world: distributed, virtualization, and reliability
如同之前的演講 [Embedded Hypervisor for ARM],小弟也會發表一項新專案,這是個極小的 L4 microkernel 與作業系統環境的實做,在某個程度上,可說是回應了 2007 年發表的 [Orz Microkernel]。期待您的指教與交流,謝謝!
Posted by jserv at 11:36 AM | Comments (0)