2009年12月25日 星期五

An Introduction to Linux Kernel Booting Sequence Part 1

此系列文章將著重於探討Linux x86核心開機過程,
其中, 假設BIOS跟Boot Loader (GRUB, for example) 運行正常, 且Boot Loader已將核心 (/boot/vmlinuz-2.6.31-16-generic) 載入記憶體並跳至載入記憶體之起始位置執行.

Linux核心主要可以分為兩大部份: 1. 執行於CPU之real mode的核心程式碼 2. 執行於CPU之protected mode的核心程式碼. 然而這兩大部份被載入至不同的記憶體區段: 1. real mode核心程式碼載入至0-640KB的某一區間, 2. protected mode核心程式碼載入至1MB記憶體位址. 如下所示:

linux kernel ram content after loading the image into the ram

Figure 1. Linux kernel RAM content after loading the Linux kernel image into RAM


首先第一個被執行的Linux核心檔案為arch/x86/boot/header.S,此檔案由組合語言所撰寫,此檔案前512位元組 (其中有15個位元組為核心標頭) 為早期的核心開機磁區,但現今的boot loader直接跳過此512位元組。緊接著在512位元組之後,即為Linux核心執行的起始點,底下展示此起始點的兩行程式碼 (以Linux核心原始碼版本2.6.31為例):
.byte 0xeb # short (2-byte) jump
.byte start_of_setup-1f


此兩行代表跳至start_of_setup標籤。".byte 0xeb" (arch/x86/boot/header.S:112)代表two-bytes jmp組合語言指令。".byte start_of_setup-1f" (arch/x86/boot/header.S:113)的"f"代表forward short jump,"start_of_setup-1"代表用"start_of_setup" (arch/x86/boot/header.S:241)標籤起始位址減去"1" (arch/x86/boot/header.S:114)標籤起始位址。

所以上述兩個位元組即代表: 以目前的位址向下跳至n個位元組 (即start_of_setup-1),因此便能正確地跳至start_of_setup標籤。

範例 (以Ubuntu 9.10之2.6.31-16-generic核心為例)

此範例利用hexdump將Linux核心dump出來,並觀看其16進制碼用以對照上述之論述。

adrian@adrian-desktop:/boot$ hexdump vmlinuz-2.6.31-16-generic > ~/vmlinuz-2.6.31-16-generic-hexdump
adrian@adrian-desktop:/boot$ gvim ~/vmlinuz-2.6.31-16-generic-hexdump

其結果如下所示:
hexdump
Figure 2. Hexdump from Linux Kernel

其中,0xAA55為Linux早期的核心開機磁區最後一個位元組(第512個位元組,arch/x86/boot/header.S:103)。0x62eb代表往下跳至62個位元組,即0x00000262位址。

本篇文章只介紹Linux之real-mode核心程式碼的entry point,往後會有陸陸續續的文章繼續探討。

【Reference】
The Kernel Boot Process

2009年12月18日 星期五

[Ubunut] 解決Firefox Flash網頁亂碼

Follow up:
1. sudo rm -f /etc/fonts/conf.d/49-sansserif.conf
2. restart firefox

大功告成.

[Reference] http://blog.xuite.net/maxkerr/blog/17037089

2009年5月21日 星期四

likely() and unlikely() macro in Linux kernel

Linux核心原始碼,經常出現兩個巨集:likely() and unlikely(),如下所示:

#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)


重點就是__builtin_expect這個函式的意義。此函式用來告訴編譯器,哪些程式區段可做預測。
底下為__builtin_expect的原型:
long __builtin_expect(long EXP, long C)
此函式__builtin_expect有兩大重點:
  1. __builtin_expect的回傳值即EXP這個判斷式。
  2. __builtin_expect語意上,是期待(EXP == C)。
例子一:
if (__builtin_expect(x, 1))
    do_something();

此敘述告知編譯器,x變數期待是1 (上述重點2), 且由於x=1, 所以__builtin_expect回傳值便是1(上述重點1),因此編譯器可以大膽預測do_something()一定會被執行到。因此便能將處理器的管線(Pipe Line)功能發揮的淋漓盡致。

例子二:
if (__builtin_expect(x, 0))
    do_something1();
else
    do_something2();

此敘述告知編譯器,x變數期待是0 (上述重點2), 且由於x=0, 所以__builtin_expect回傳值便是0(上述重點1),因此編譯器可以大膽預測do_something2()一定會被執行到,而不是預測執行do_something1()。


總結likely()與unlikely()巨集:

if(likely(x)) {
    預測想要執行的原始碼
} else {
}

if(unlikely(x)) {
} else {
    預測想要執行的原始碼
}


[Reference]
1. FAQ/LikelyUnlikely
2. richliu's blog

2009年5月19日 星期二

The Stringify Operator (#) in C

繼上次提到Token-pasting operator (##) in C文章後, 這次講一下在C語言define巨集參數使用的另一種方法, 在參數名稱前加一個#運算子, 此運算子代表所帶入的參數會被編譯器視為"字串", 所以#運算子又被稱為字串化運算子, 底下為一簡單的例子:


#define GET_RESULT(exp) printf(#exp "=%d\n", exp)

int main(void)
{
    GET_RESULT(3+2);
    return ;
}

2009年5月18日 星期一

Token-pasting operator(##) in C

我們都知道C語言define前端處理假指令用來定義變數、字串或幾行的原始碼(統稱為巨集, Macro)。
當某一巨集被定義為參數帶入之巨集, 我們最常用的就是把該參數當作變數或指標來使用,如下所示:
#define INC_IDX(val, size) (++val % size) -> 以參數為變數之值帶入此巨集
#define GET_DATA(ptr) (ptr->data) -> 以參數為指標帶入此巨集

然而,我們可以把巨集所帶入的參數當作為識別子(Token)的一部分,如下所示:
 #include <stdio.h>  
   
 int class_num = 9;  
 int class_stds = 10;  
   
 #define GET_MEMBER(postfix) class_ ## postfix  
   
 int main(void)  
 {  
    printf("class_num: %d\n", GET_MEMBER(num));  
    printf("class_stds: %d\n", GET_MEMBER(stds));  
    return 0;  
 }  
如上列所示,class_num可以經由GET_MEMBER巨集(帶入部份識別子, 也就是num)取得,此參數傳遞方法稱為Token-pasting operator,這是一個非常好用的方法, 提供給大家參考。