2010年1月15日 星期五

Linux Kernel: __init, __initdata屬性

在trace Linux核心原始碼很常看到__init和__initdata兩個屬性。其原型如下:
#define __init __section(.init.text) __cold notrace
#define __initdata __section(.init.data)


只要函數被定義__init屬性,代表此函數的所有內容被編譯器放置在.init.text節區。此節區代表該函數只會執行一次,此後就不會再執行,因此執行完該函數,核心會將該函數所佔記憶體空間釋放出來。__init屬性非常適合裝置驅動程式的init_module函數,其範例宣告如下:
static int __init dm_init(void); (from linux-source-2.6.31/drivers/md/dm.c)


利用objdump觀察dm_init函數被放置在哪個節區:
adrian@adrian-laptop:/usr/src/linux-source-2.6.31$ objdump -x drivers/md/built-in.o | grep md_init
00000000 l F .init.text 000000de md_init
00000000 l O .initcall4.init 00000004 __initcall_md_init4


__initdata屬性跟__init屬性的作用大同小異,唯一不同在於前者放置在.init.data節區。

範例:
static int (*_inits[])(void) __initdata = {
     local_init,
     dm_target_init,
     dm_linear_init,
     dm_stripe_init,
     dm_kcopyd_init,
     dm_interface_init,
};

利用objdump觀察_inits變數被放置在哪個節區:
adrian@adrian-laptop:/usr/src/linux-source-2.6.31$ objdump -x drivers/md/built-in.o | grep _inits
00000000 l O .init.data 00000018 _inits


問題來了,釋放記憶體的工作由核心的哪一函數負責呢? 答案是: free_initmem (arch/x86/mm/init.c)

【Reference】
FAQ/InitExitMacros
Linux Kernel Source 2.6.31

An Introduction to Linux Kernel Booting Sequence Part 3

上篇文章提到go_to_protected_mode函式最後會執行由組合語言所撰寫的函式protected_mode_jump,此函式工作如下:


函式呼叫原型:
protected_mode_jump(boot_params.hdr.code32_start, (u32)&boot_params + (ds() << 4));

其程式碼:

/*
* void protected_mode_jump(u32 entrypoint, u32 bootparams);
*/
GLOBAL(protected_mode_jump)
movl %edx, %esi # Pointer to boot_params table

xorl %ebx, %ebx
movw %cs, %bx
shll $4, %ebx
addl %ebx, 2f
jmp 1f # Short jump to serialize on 386/486
1:

movw $__BOOT_DS, %cx
movw $__BOOT_TSS, %di

### 設定x86之cr0暫存器的PE位元,如此便能進入proctected-mode
movl %cr0, %edx
orb $X86_CR0_PE, %dl # Protected mode
movl %edx, %cr0

# Transition to 32-bit mode
.byte 0x66, 0xea # ljmpl opcode
2: .long in_pm32 # offset
.word __BOOT_CS # segment
ENDPROC(protected_mode_jump)

.code32
.section ".text32","ax"
GLOBAL(in_pm32)
# Set up data segments for flat 32-bit mode
movl %ecx, %ds
movl %ecx, %es
movl %ecx, %fs
movl %ecx, %gs
movl %ecx, %ss
# The 32-bit code sets up its own stack, but this way we do have
# a valid stack if some debugging hack wants to use it.
addl %ebx, %esp

# Set up TR to make Intel VT happy
ltr %di

# Clear registers to allow for future extensions to the
# 32-bit boot protocol
xorl %ecx, %ecx
xorl %edx, %edx
xorl %ebx, %ebx
xorl %ebp, %ebp
xorl %edi, %edi

# Set up LDTR to make Intel VT happy
lldt %cx

## 跳至code32_start的標籤
jmpl *%eax # Jump to the 32-bit entrypoint
ENDPROC(in_pm32)


至於code32_start在哪裡呢? 此標籤定義在arch/x86/boot/header.S,如下所示:


code32_start: # here loaders can put a different
# start address for 32-bit code.
.long 0x100000 # 0x100000 = default for big kernel



也就是說,核心已做完real-mode Linux程式碼該做的事情,並跳至protected-mode Linux程式碼的起始位址 (1MB),請看下圖:

linux kernel ram content after loading the image into the ram

[Reference]
【The Kernel Boot Process】
【Linux Kernel Source 2.6.31】

2010年1月12日 星期二

An Introduction to Linux Kernel Booting Sequence Part 2

上篇文章Linux核心被載入記憶體並執行start_of_setup標籤(arch/x86/boot/header.S),在start_of_setup標籤末段會執行由C語言撰寫的main函式 (arch/x86/boot/main.c),此函式工作如下所示:

1. 複製開機標頭 (boot header) 至 zero page
2. 設定heap_end變數
3. 偵測記憶體layout
4. 設定鍵盤重複率 (Keyboard Repeat Rate)
5. 查詢MCA (Micro Channel Architecture) 資訊
6. 查詢Intel SpeedStep
7. 設定video模式
8. 進入protected-mode (go_to_protected_mode,arch/x86/boot/pm.c)

其中3-6項,經由BIOS呼叫 (intcall,arch/x86/boot/bioscall.S),以便向BIOS取到相關的資訊。
main函式最後呼叫go_to_protected_mode函式,以便從real-mode轉換至protected-mode。

在真正進入protected-mode之前,有幾項工作必須先被執行,也就是go_to_protected_mode函式裡所做的事情,如下所示:

1. 設定A20位址線,如此便能存取超過1MB以後的資料 (因為在real-mode,只能存取1MB以下的資料)。

2. 設定idt (interrupt descriptor table)。在real-mode,interrupt vector table的起始位址是從記憶體零的位址開始算起。然而,在protected-mode,interrupt vector table是儲存於CPU的暫存器 (IDTR,Interrupt Descriptor Table Register),因此轉換至protected-mode之前,必須先設定idt。

3. 設定gdt (global descriptor table)。由於,real-mode與proctected-mode位址轉換 (由邏輯位址 [logical address] 轉換成線性位址 [linear address]),詳見x86_memory_segmentation。因此轉換至protected-mode之前,必須先設定gdt,以便能正確地根據logical address轉換成linear address。


在go_to_protected_mode函式最後會執行由組合語言所撰寫的函式protected_mode_jump,此函式便是設定CPU為protected-mode,細節留到下回做進一步解釋。

【Reference】 The Kernel Boot Process

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 ;
}