2010年5月24日 星期一

函式指標的用法

函式指標的應用,可減少判斷的次數;把函式當成參數傳遞。此處轉貼一個學c的連結:C程式語言教學
方法一:
ulong FUNC(ulong param1, ulong param2, ulong param3){
........
........
return retval;
}
void Call_FUNC(int paramX, void* paramY){
........
........
return;
}
void Main_FUNC(){
........
Call_FUNC( x , (void*)FUNC);
........
return;
}
方法二:
struct STRUCTNAME{
int A;
unsigned B;
char *NAME;
long (*FUNC_X)(void);
void (*FUNC_Y)(int,long);
};
static long ptr1_func(void){
......
}
static void ptr2_func(int i, long k){
......
}
static long ptr3_func(void){
......
}
static void ptr4_func(int a, long b){
......
}
static long ptr5_func(void){
......
}
static void ptr6_func(int v, long u){
......
}
static long ptr7_func(void){
......
}
static void ptr8_func(int n, long f){
......
}
static struct STRUCTNAME struct_array[] = {
{ x, x , "xxxx", ptr1_func , ptr2_func(x,y)},
{ x, x , "xxxx", ptr3_func , ptr4_func(d,e)},
{ x, x , "xxxx", ptr5_func , ptr6_func(f,g)},
{ x, x , "xxxx", ptr7_func , ptr2_func(j,k)},
{ x, x , "xxxx", ptr3_func , ptr8_func(r,t)}
};
函式指標呼叫如下
struct_array[index].FUNC_X();
struct_array[index].FUNC_Y(intN,longM);
以下轉貼自程式設計俱樂部http://www.programmer-club.com.tw/ShowSameTitleN/c/33283.html

最近在trace code看到一個函式指標的觀念想請教一下,我寫了一個範例code如下
#include
#include
int fun(int, int);
typedef int(*fun_one)(int, int); //書上大部分介紹函式指標的寫法
typedef int(fun_two)(int, int); //這是在trace linux kernel code看到的寫法
int test1(fun_one);
int test2(fun_two *);
int main(void){
test1(fun);

test2(fun);

return 0;
}

int fun(int x, int y){
printf("x : %d, y : %d\n", x, y);
}
int test1(fun_one f1)//這邊不用多加一個指標(*)的符號
{f1(1,1);}
int test2(fun_two *f2)//要宣告f2是指向fun_two的指標
{f2(2,2);}
這邊兩種方式都可以run。
--> 閱讀更多...

2010年5月6日 星期四

linux SMP 啟動過程學習筆記

SMP 硬體體系結構:
對於 SMP 最簡單可以理解為系統存在多個完全相同的 CPU ,所有 CPU 共用匯流排,擁有自己的寄存器。對於記憶體和外部設備訪問,由於共用匯流排,所以是共用的。 Linux 作業系統多個 CPU 共用在系統空間上映射相同,是完全對等的。
由於系統中存在多個 CPU ,這是就引入一個問題,當外部設備產生中斷的時候,具體有哪一個 CPU 進行處理?
為此, intel 公司提出了 IO APCI 和 LOCAL APCI 的體系結構。
IO APIC 連接各個外部設備,並可以設置分發類型,根據設定的分發類型,中斷信號發送的對應 CPU 的 LOCAL APIC 上。
LOCAL APIC 負責本地 CPU 的中斷處理, LOCAL APIC 不僅可以接受 IO APIC 的中斷,也需要處理本地 CPU 產生的異常。同時 LOCAL APIC 還提供了一個計時器。
如何確定那個 CPU 是引導 CPU ?
根據 intel 公司中的資料,系統上電後,會根據 MP Initialization Protocol 隨機選擇一個 CPU 作為 BSP ,只有 BSP 會運行 BIOS 程式,其他 AP 都進入等待狀態, BSP 發送 IPI 中斷觸發後才可以運行。具體的 MP Initialization Protocol 細節,可以參考 Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1 第 8 章。
引導 CPU 如何控制其他 CPU 開始運行?
BSP 可以通過 IPI 消息控制 AP 從指定的起始位址運行。 CPU 中集成的 LOCAL APIC 提供了這個功能。可以通過寫 LOCAL APIC 中提供的相關寄存器,發送 IPI 消息到指定的 CPU 上。
如何獲取系統硬體 CPU 資訊的?
在系統初始化後,硬體會在記憶體的規定位置提供關於 CPU ,匯流排 , IO APIC 等的資訊,即 SMP MP table 。在 linux 初始化的過程,會讀取該位置,獲取系統相關的硬體資訊。
2. linux SMP 啟動過程流程簡介
setup_arch()
setup_memory();
reserve_bootmem(PAGE_SIZE, PAGE_SIZE);
find_smp_config(); // 查找 smp mp table 的位置
smp_alloc_memory();
trampoline_base = (void *) alloc_bootmem_low_pages(PAGE_SIZE); // 分配 trampoline ,用於啟動 AP 的引導代碼。
get_smp_config(); // 根據 smp mp table ,獲取具體的硬體資訊
trap_init()
init_apic_mappings();
mem_init();
zap_low_mappings(); 如果沒有定義 SMP 的話,清楚用戶空間的位址映射。
rest_init();
kernel_thread(init, NULL, CLONE_FS CLONE_SIGHAND);
init();
set_cpus_allowed(current, CPU_MASK_ALL);
smp_prepare_cpus(max_cpus);
smp_boot_cpus(max_cpus);
connect_bsp_APIC();
setup_local_APIC(); // 初始化 BSP 的 LOCAL APCI 。
map_cpu_to_logical_apicid();
針對每個 CPU 調用 do_boot_cpu(apicid, cpu)
smp_init(); // 每個 CPU 開始進行調度
trampoline.S AP 引導代碼,為 16 進制代碼,啟用保護模式
head.s 為 AP 創建分頁管理
initialize_secondary 根據之前 fork 創建設置的資訊,跳轉到 start_secondary 處
start_secondary 判斷 BSP 是否啟動,如果啟動 AP 進行任務調度。
3. 代碼學習總結
find_smp_config(); ,查找 MP table 在記憶體中的位置。具體協定可以參考 MP 協定的第 4 章。
這個表的作用在於描述系統 CPU ,匯流排, IO APIC 等的硬體資訊。
相關的兩個總體變數: smp_found_config 是否找到 SMP MP table , mpf_found SMP MP table 的線性位址。
smp_alloc_memory() 為啟動 AP 的啟動程式分配記憶體空間。相關總體變數 trampoline_base ,分配的啟動位址的線性位址。
get_smp_config() 根據 MP table 中提供的內容,獲取硬體的資訊。
init_apic_mappings(); 獲取 IO APIC 和 LOCAL APIC 的映射位址 。
zap_low_mappings(); 如果沒有定義 SMP 的話,清楚用戶空間的位址映射。將 swapper_pg_dir 中表項清零。
setup_local_APIC(); 初始化 BSP 的 LOCAL APCI 。
do_boot_cpu(apicid, cpu)
idle = alloc_idle_task(cpu);
task = copy_process(CLONE_VM, 0, idle_regs(&regs), 0, NULL, NULL, 0);
init_idle(task, cpu);
將 init 進程使用 copy_process 複製,並且調用 init_idle 函數,設置可以運行的 CPU 。
idle->thread.eip = (unsigned long) start_secondary;
修改 task_struct 中的 thread.eip ,使得 AP 初始化完成後,就運行 start_secondary 函數。
start_eip = setup_trampoline();
調用 setup_trampoline() 函數,複製 trampoline_data 到 trampoline_end 之間的代碼到 trampoline_base 處, trampoline_base 就是之前在 setup_arch 處申請的記憶體。 start_eip 返回值是 trampoline_base 對應的物理位址。
smpboot_setup_warm_reset_vector(start_eip); 設置記憶體 40:67h 處為 start_eip 為啟動地址。
wakeup_secondary_cpu(apicid, start_eip); 在這個函數中通過操作 APIC_ICR 寄存器, BSP 向目標 AP 發送 IPI 消息,觸發目標 AP 從 start_eip 位址處,從實模式開始運行。
trampoline.S
ENTRY(trampoline_data)
r_base = .
wbinvd # Needed for NUMA-Q should be harmless for others
mov %cs, %ax # Code and data in the same place
mov %ax, %ds
cli # We should be safe anyway
movl $0xA5A5A5A5, trampoline_data - r_base
這個是設置標識,以便 BSP 知道 AP 運行到這裏了。

lidtl boot_idt - r_base # load idt with 0, 0
lgdtl boot_gdt - r_base # load gdt with whatever is appropriate
載入 ldt 和 gdt
xor %ax, %ax
inc %ax # protected mode (PE) bit
lmsw %ax # into protected mode
# flush prefetch and jump to startup_32_smp in arch/i386/kernel/head.S
ljmpl $__BOOT_CS, $(startup_32_smp-__PAGE_OFFSET)
啟動保護模式,跳轉到 startup_32_smp 處
# These need to be in the same 64K segment as the above;
# hence we don't use the boot_gdt_descr defined in head.S
boot_gdt:
.word __BOOT_DS + 7 # gdt limit
.long boot_gdt_table-__PAGE_OFFSET # gdt base
boot_idt:
.word 0 # idt limit = 0
.long 0 # idt base = 0L
.globl trampoline_end
trampoline_end:
在這段代碼中,設置標識,以便 BSP 知道該 AP 已經運行到這段代碼,載入 GDT 和 LDT 表基址。
然後啟動保護模式,跳轉到 startup_32_smp 處。
Head.s 部分代碼:
ENTRY(startup_32_smp)
cld
movl $(__BOOT_DS),%eax
movl %eax,%ds
movl %eax,%es
movl %eax,%fs
movl %eax,%gs
xorl %ebx,%ebx
incl %ebx
如果是 AP 的話,將 bx 設置為 1
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3 /* set the page table pointer.. */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* ..and set paging (PG) bit */
ljmp $__BOOT_CS,$1f /* Clear prefetch and normalize %eip */
啟用分頁,
lss stack_start,%esp
使 esp 執行 fork 創建的進程內核堆疊部分,以便後續跳轉到 start_secondary
#ifdef CONFIG_SMP
movb ready, %cl
movb $1, ready
cmpb $0,%cl
je 1f # the first CPU calls start_kernel
# all other CPUs call initialize_secondary
call initialize_secondary
jmp L6
1:
#endif /* CONFIG_SMP */
call start_kernel
如果是 AP 啟動的話,就調用 initialize_secondary 函數。
void __devinit initialize_secondary(void)
{
/*
* We don't actually need to load the full TSS,
* basically just the stack pointer and the eip.
*/
asm volatile(
"movl %0,%%esp\n\t"
"jmp *%1"
:
:"r" (current->thread.esp),"r" (current->thread.eip));
}
設置堆疊為 fork 創建時的堆疊, ip 為 fork 時的 ip ,這樣就跳轉的了 start_secondary 。
start_secondary 函數中處理如下:
while (!cpu_isset(smp_processor_id(), smp_commenced_mask))
rep_nop();
進行 smp_commenced_mask 判斷,是否啟動 AP 運行。 smp_commenced_mask 在 smp_init() 中設置。
cpu_idle();
如果啟動了,調用 cpu_idle 進行任務調度。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/jemmy858585/archive/2009/09/01/4509375.aspx
--> 閱讀更多...

2010年5月4日 星期二

【轉】終生受用的十大經典理論

【轉】終生受用的十大經典理論
1、彼得原理
  每個組織都是由各種不同的職位、等級或階層的排列所組成,每個人都隸屬於其中的某個等級。彼得原理是美國學者勞倫斯·彼得在對組織中人員晉升的相關現象研究後,得出一個結論:在各種組織中,雇員總是趨向于晉升到其不稱職的地位。彼得原理有時也被稱為向上爬的原理。這種現象在現實生活中無處不在:一名稱職的教授被提升為大學校長後,卻無法勝任;一個優秀的運動員被提升為主管體育的官員,而無所作為。對一個組織而言,一旦相當部分人員被推到其不稱職的級別,就會造成組織的人浮於事,效率低下,導致平庸者出人頭地,發展停滯。因此,這就要求改變單純的根據貢獻決定晉升的企業員工晉升機制,不能因某人在某個崗位上幹得很出色,就推斷此人一定能夠勝任更高一級的職務。將一名職工晉升到一個無法很好發揮才能的崗位,不僅不是對本人的獎勵,反而使其無法很好發揮才能,也給企業帶來損失。
2、酒與污水定律
  酒與污水定律是指把一匙酒倒進一桶污水,得到的是一桶污水;如果把一匙污水倒進一桶酒,得到的還是一桶污水。在任何組織裏,幾乎都存在幾個難弄的人物,他們存在的目的似乎就是為了把事情搞糟。最糟糕的是,他們像果箱裏的爛蘋果,如果不及時處理,它會迅速傳染,把果箱裏其他蘋果也弄爛。爛蘋果的可怕之處,在於它那驚人的破壞力。一個正直能幹的人進入一個混亂的部門可能會被吞沒,而一個無德無才者能很快將一個高效的部門變成一盤散沙。組織系統往往是脆弱的,是建立在相互理解、妥協和容忍的基礎上的,很容易被侵害、被毒化。破壞者能力非凡的另一個重要原因在於,破壞總比建設容易。一個能工巧匠花費時日精心製作的陶瓷器,一頭驢子一秒鐘就能毀壞掉。如果一個組織裏有這樣的一頭驢子,即使擁有再多的能工巧匠,也不會有多少像樣的工作成果。如果你的組織裏有這樣的一頭驢子,你應該馬上把它清除掉,如果你無力這樣做,就應該把它拴起來。
3、木桶定律
  水桶定律是講一隻水桶能裝多少水,這完全取決於它最短的那塊木板。這就是說任何一個組織,可能面臨的一個共同問題,即構成組織的各個部分往往是優劣不齊的,而劣勢部分往往決定整個組織的水準。水桶定律與酒與污水定律不同,後者討論的是組織中的破壞力量,最短的木板卻是組織中有用的一個部分,只不過比其他部分差一些,你不能把它們當成爛蘋果扔掉。強弱只是相對而言的,無法消除,問題在於你容忍這種弱點到什麼程度,如果嚴重到成為阻礙工作的瓶頸,你就不得不有所動作。
4、馬太效應
  《新約·馬太福音》中有這樣一個故事:一個國王遠行前,交給3個僕人每人一錠銀子,吩咐道:你們去做生意,等我回來時,再來見我。國王回來時,第一個僕人說:主人,你交給我的一錠銀子,我已賺了10錠。於是,國王獎勵他10座城邑。第二個僕人報告:主人,你給我的一錠銀子,我已賺了5錠。於是,國王獎勵他5座城邑。第三僕人報告說:主人,你給我的1錠銀子,我一直包在手帕裏,怕丟失,一直沒有拿出來。於是,國王命令將第三個僕人的1錠銀子賞給第一個僕人,說:凡是少的,就連他所有的,也要奪過來。凡是多的,還要給他,叫他多多益善,這就是馬太效應,反應當今社會中存在的一個普遍現象,即贏家通吃。對企業經營發展而言,馬太效應告訴我們,要想在某一個領域保持優勢,就必須在此領域迅速做大。當你成為某個領域的領頭羊時,即便投資回報率相同,你也能更輕易地獲得比弱小的同行更大的收益。而若沒有實力迅速在某個領域做大,就要不停地尋找新的發展領域,才能保證獲得較好的回報。
5、零和遊戲原理
  零和遊戲是指一項遊戲中,遊戲者有輸有贏,一方所贏正是另一方所輸,遊戲的總成績永遠為零,零和遊戲原理之所以廣受關注,主要是因為人們在社會的方方面面都能發現與零和遊戲類似的局面,勝利者的光榮後面往往隱藏著失敗者的辛酸和苦澀。 20世紀,人類經歷兩次世界大戰、經濟高速增長,科技進步、全球一體化以及日益嚴重的環境污染,零和遊戲觀念正逐漸被雙贏觀念所取代。人們開始認識到利已不一定要建立在損人的基礎上。通過有效合作皆大歡喜的結局是可能出現的。但從零和遊戲走向雙贏,要求各方面要有真誠合作的精神和勇氣,在合作中不要小聰明,不要總想占別人的小便宜,要遵守遊戲規則,否則雙贏的局面就不可能出現,最終吃虧的還是合作者自己。
6、華盛頓合作規律
  華盛頓合作規律說的是一個人敷衍了事,兩個人互相推諉,三個人則永無成事之日。多少有點類似於我們三個和尚的故事。人與人的合作,不是人力的簡單相加,而是要複雜和微妙得多。在這種合作中,假定每個人的能力都為1,那麼,10個人的合作結果有時比10大得多,有時,甚至比1還要小。因為人不是靜止物,而更像方向各異的能量,相互推動時,自然事半功倍,相互抵觸時,則一事無成。我們傳統的管理理論中,對合作研究得並不多,最直觀的反映就是,目前的大多數管理制度和行為都是致力於減少人力的無謂消耗,而非利用組織提高人的效能。換言之,不妨說管理的主要目的不是讓每個人做得更好,而是避免內耗過多。
7、手錶定理
  手錶定理是指一個人有一隻表時,可以知道現在是幾點鐘,當他同時擁有兩隻表時,卻無法確定。兩隻手錶並不能告訴一個人更準確的時間,反而會讓看表的人失去對準確時間的信心。手錶定理在企業經營管理方面,給我們一種非常直觀的啟發,就是對同一個人或同一個組織的管理,不能同時採用兩種不同的方法,不能同時設置兩個不同的目標,甚至每一個人不能由兩個人同時指揮,否則將使這個企業或這個人無所適從。手錶定理所指的另一層含義在於,每個人都不能同時選擇兩種不同的價值觀,否則,你的行為將陷於混亂。
8、不值得定律
  不值得定律最直觀的表述是:不值得做的的事情,就不值得做好。這個定律再簡單不過了,重要性卻時時被人們忽視遺忘。不值得定律反映人們的一種心理,一個人如果從事的是一份自認為不值得做的事情,往往會保持冷嘲熱諷,敷衍了事的態度,不僅成功率低,而且即使成功,也不覺得有多大的成就感。因此,對個人來說,應在多種可供選擇的奮鬥目標及價值觀中挑選一種,然後為之奮鬥。選擇你所愛的,愛你所選擇的,才可能激發我們的鬥志,也可以心安理得。而對一個企業或組織來說,則要很好地分析員工的性格特性,合理分配工作,如讓成就欲較強的職工單獨或牽頭完成具有一定風險和難度的工作,並在其完成時,給予及時的肯定和讚揚;讓依附欲較強的職工,多加參與某個團隊協同工作;讓權力欲較強的職工,擔任一個與之能力相適應的主管。同時要加強員工對企業目標的認同感,讓員工感覺到自己所做的工作是值得的,這樣才能激發職工的熱情。
9、蘑菇管理
  蘑菇管理是許多組織對待初出茅廬者的一種管理方法,初學者被置於陰暗的角落(不受重視的部門,或打雜跑腿的工作),澆上一頭大糞(無端的批評、指責、代人受過),任其自生自滅(得不到必要的指導和提攜)。相信很多人都有過這樣一段蘑菇的經歷,這不一定是什麼壞事,尤其是當一切剛剛開始的時候,當幾天蘑菇,能夠消除我們很多不切實際的幻想,讓我們更加接近現實,看問題也更加實際。一個組織,一般對新進的人員都是一視同仁,從起薪到工作都不會有大的差別。無論你是多麼優秀的人才,在剛開始的時候,都只能從最簡單的事情做起,蘑菇的經歷,對於成長中的年輕人來說,就象蠶繭,是羽化前必須經歷的一步。所以,如何高效率地走過生命的這一段,從中盡可能汲取經驗,成熟起來,並樹立良好的值得信賴的個人形象,是每個剛入社會的年輕人必須面對的課題。
10、奧卡姆剃刀定律
  12世紀,英國奧卡姆的威廉主張唯名論,只承認確實存在的東西,認為那些空洞無物的普遍性概念都是無用的累贅,應當被無情地剃除。他主張如無必要,勿增實體。這就是常說的奧卡姆剃刀。這把剃刀曾使很多人感到威脅,被認為是異端邪說,威廉本人也因此受到迫害。然而,並未損害這把刀的鋒利,相反,經過數百年的歲月,奧卡姆剃刀已被歷史磨得越來越快,並早已超載原來狹窄的領域,而具有廣泛、豐富、深刻的意義。  奧卡姆剃刀定律在企業管理中可進一步演化為簡單與複雜定律:把事情變複雜很簡單,把事情變簡單很複雜。這個定律要求,我們在處理事情時,要把握事情的主要實質,把握主流,解決最根本的問題,尤其要順應自然,不要把事情人為地複雜化,這樣才能把事情處理好。
--> 閱讀更多...

(轉)Linux 2.6內核Makefile分析

(轉)Linux 2.6內核Makefile分析
由於Linux的獨特優勢,使越來越多的企業和科研機構把目光轉向Linux的開發和研究上。目前Linux最新的穩定內核版本為2.6.17,但是當今絕大部分對於Linux Makefile的介紹文章都是基於2.4內核的,可以說關於2.6內核Makefile相關的文章鳳毛麟角,筆者抽時間完成了這篇分析文章,讓讀者迅速熟悉Linux最新Makefile體系,從而加深對內核的理解,同時也希望能對Linux在公司的推廣起到一定的推動作用,算是抛磚引玉吧!
1 Makefile組織層次
Linux的Make體系由如下幾部分組成:
Ø 頂層Makefile
頂層Makefile通過讀取配置檔,遞迴編譯內核代碼樹的相關目錄,從而產生兩個重要的目標檔:vmlinux和模組。
Ø 內核相關Makefile
位於arch/$(ARCH) 目錄下,為頂層Makefile提供與具體硬體體協結構相關的資訊。
Ø 公共編譯規則定義檔。
包括Makefile.build 、Makefile.clean、Makefile.lib、Makefile.host等檔組成。這些檔位於scripts目錄中,定義了編譯需要的公共的規則和定義。
Ø 內核配置檔 .config
通過調用make menuconfig或者make xconfig命令,用戶可以選擇需要的配置來生成期望的目標檔。
Ø 其他Makefile
主要為整個Makefile體系提供各自模組的目標檔定義,上層Makefile根據它所定義的目標來完成各自模組的編譯。
2 Makefile的使用
在編譯內核之前,用戶必須首先完成必要的配置。Linux內核提供了數不勝數的功能,支援眾多的硬體體系結構,這就需要用戶對將要生成的內核進行裁減。內核提供了多種不同的工具來簡化內核的配置,最簡單的一種是字元介面下命令行工具:
make config
這個工具會依次遍曆內核所有的配置項,要求用戶進行逐項的選擇配置。這個工具會耗費用戶太多時間,除非萬不得以(你的編譯主機不支援其他配置工具)一般不建議使用。
用戶還可以使用利用ncurse庫編制的圖形介面工具,這就是大名鼎鼎的:
make menuconfig
相信以前對2.4內核比較熟悉的用戶一定不會陌生。當然在2.6內核中提供了更漂亮和方便的基於X11的圖形配置工具:
make xconfig
當用戶使用這個工具對Linux內核進行配置時,介面下方會出現這個配置項相關的幫助資訊和簡單描述,當你對內核配置選項不太熟悉時,建議你使用這個工具來進行內核配置。
當用戶完成配置後,配置工具會自動生成.config檔,它被保存在內核代碼樹的根目錄下。用戶可以很容易找到它,當然用戶也可以直接對這個檔進行簡單的修改。但是當你修改過配置檔之後,你必須通過下面的命令來驗證和更新配置:
make oldconfig
跟2.4版本的不同之處在於,用戶不需要顯示的調用make dep命令來生成依賴檔,內核會自動維護代碼間的依賴關係。
當一切工作完成以後,用戶只需要簡單鍵入make,剩下所有的工作makefile就會自動替你完成了。
3 Makefile編譯流程
當用戶使用Linux的Makefile編譯內核版本時,Makefile的編譯流程如下:
Ø 使用命令行或者圖形介面配置工具,對內核進行裁減,生成.config配置檔
Ø 保存內核版本資訊到 include/linux/version.h
Ø 產生符號鏈結 include/asm,指向實際目錄 include/asm-$(ARCH)
Ø 為最終目標檔的生成進行必要的準備工作
Ø 遞迴進入 /init 、/core、 /drivers、 /net、 /lib等目錄和其中的子目錄來編譯生成所有的目標檔
Ø 鏈結上述過程產生的目標檔生成vmlinux,vmlinux存放在內核代碼樹的根目錄下
Ø 最後根據 arch/$(ARCH)/Makefile檔定義的後期編譯的處理規則建立最終的映象bootimage,包括創建引導記錄、準備initrd映象和相關處理
4 Makefile關鍵規則和定義描述
1) 目標定義
目標定義是Makefile檔的核心部分,目標定義通知Makefile需要生成哪些目標檔、如何根據特殊的編譯選項鏈結目標檔,同時控制哪些子目錄要遞迴進入進行編譯。
這個例子Makefile檔位於/fs/ext2目錄 :
#
# Makefile for the linux ext2-filesystem routines.
#
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o \
ioctl.o namei.o super.o symlink.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o
ext2-$(CONFIG_EXT2_FS_POSIX_ACL) += acl.o
ext2-$(CONFIG_EXT2_FS_SECURITY) += xattr_security.o
ext2-$(CONFIG_EXT2_FS_XIP) += xip.o
這表示與ext2相關的目標檔由 ext2-y定義的檔列表組成,其中ext2-$(*)是由內核配置檔.config中的配置項決定,最終Makefile會在這個目錄下統一生成一個目標檔ext2.o(由obj-$(CONFIG_EXT2_FS)決定)。其中obj-y表示為生成vmlinux檔所需要的目標檔集合,具體的檔依賴於內核配置。
Makefile會編譯所有的$(obj-y)中定義的檔,然後調用鏈結器將這些檔鏈結到built-in.o文件中。最終built-in.o檔通過頂層Makefile鏈結到vmlinux中。值得注意的是$(obj-y)的檔順序很重要。列表檔可以重複,檔第一次出現時將會鏈結到built-in.o中,後來出現的同名檔將會被忽略。檔順序直接決定了他們被調用的順序,這一點讀者需要特別注意。
讀者可能會在某些Makefile中發現lib-y定義,所有包含在lib-y定義中的目標檔都將會被編譯到該目錄下一個統一的庫檔中。值得注意的是lib-y定義一般被限制在 lib 和arch/$(ARCH)/lib 目錄中。
體系makefile檔和頂層makefile檔共同定義了如何建立vmlinux檔的規則。
$(head-y) 列舉首先鏈結到vmlinux的物件檔。
$(libs-y) 列舉了能夠找到lib.a檔的目錄。
其餘的變數列舉了能夠找到内嵌物件檔的目錄。
$(init-y) 列舉的物件位於$(head-y)物件之後。
然後是如下位置順序:
$(core-y), $(libs-y), $(drivers-y) 和 $(net-y)。
頂層makefile定義了所有通用目錄,arch/$(ARCH)/Makefile檔只需增加體系相關的目錄。
例如: #arch/i386/Makefile
libs-y += arch/i386/lib/
core-y += arch/i386/kernel/ \
arch/i386/mm/ \
arch/i386/$(mcore-y)/ \
arch/i386/crypto/
drivers-$(CONFIG_MATH_EMULATION) += arch/i386/math-emu/
drivers-$(CONFIG_PCI) += arch/i386/pci/
…………………………………………
2) 目錄遞迴
Makefile檔只負責當前目錄下的目標檔,子目錄中的檔由子目錄中的makefile負責編譯,編譯系統使用obj-y 和 obj-m來自動遞迴編譯各個子目錄中的檔。
對於fs/Makefile:
obj-$(CONFIG_EXT2_FS) += ext2/
如果在內核配置檔.config中,CONFIG_EXT2_FS被設置為y或者m,則內核makefile會自動進入ext2目錄來進行編譯。內核Makefile只使用這些資訊來決定是否需要編譯這個目錄,子目錄中的makefile規定哪些檔編譯為模組,哪些檔編譯進內核。
3) 依賴關係
Linux Makefile通過在編譯過程中生成的 .檔案名.o.cmd(比如對於main.c檔,它對應的依賴檔案名為.main.o.cmd)來定義相關的依賴關係。
一般檔的依賴關係由如下部分組成:
Ø 所有的前期依賴檔(包括所有相關的*.c 和 *.h)
Ø 所有與CONFIG_選項相關的檔
Ø 編譯目標檔所使用到的命令行
位於init目錄下的main.c檔的依賴檔.main.o.cmd內容如下,讀者可以結合起來理解上述檔依賴關係的三個組成部分:
cmd_init/main.o := gcc -m32 -Wp,-MD,init/.main.o.d -nostdinc -isystem /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/include -D__KERNEL__ -Iinclude -Iinclude2 -I/home/linux/linux-2.6.17.11/include -include include/linux/autoconf.h -I/home/linux/linux-2.6.17.11/init -Iinit -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Os -fomit-frame-pointer -pipe -msoft-float -mpreferred-stack-boundary=2 -march=i686 -mcpu=pentium4 -mregparm=3 -ffreestanding -I/home/linux/linux-2.6.17.11/include/asm-i386/mach-default -Iinclude/asm-i386/mach-default -D"KBUILD_STR(s)=\#s" -D"KBUILD_BASENAME=KBUILD_STR(main)" -D"KBUILD_MODNAME=KBUILD_STR(main)" -c -o init/.tmp_main.o /home/linux/linux-2.6.17.11/init/main.c
deps_init/main.o := \
/home/linux/linux-2.6.17.11/init/main.c \
$(wildcard include/config/x86/local/apic.h) \
$(wildcard include/config/acpi.h) \
# 由於篇幅的關係,此處略去一些定義
……………………………………..
include2/asm/mpspec_def.h \
/home/linux/linux-2.6.17.11/include/asm-i386/mach-default/mach_mpspec.h \
include2/asm/io_apic.h \
include2/asm/apic.h \
init/main.o: $(deps_init/main.o)
$(deps_init/main.o):
4) 特殊規則
特殊規則使用在內核編譯需要規則定義而沒有相應定義的時候。典型的例子如編譯時頭檔的產生規則。其他例子有體系makefile編譯引導映射的特殊規則。特殊規則寫法同普通的makefile規則。
編譯程序在makefile所在的目錄不能被執行,因此所有的特殊規則需要提供前期檔和目標檔的相對路徑。
定義特殊規則時將使用到兩個變數:
$(src): $(src)是對於makefile檔目錄的相對路徑,當使用代碼樹中的檔時
使用該變數$(src)。
$(obj): $(obj)是目標檔目錄的相對路徑。生成檔使用$(obj)變數。
例如: #drivers/scsi/Makefile
$(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl
$(CPP) -DCHIP=810 - < $< | ... $(src)/script_asm.pl
這就是使用普通語法的特殊編譯規則。
目標檔依賴於兩個前提檔。目標檔的首碼是$(obj), 前提檔的首碼是
$(src)(因為它們不是生成檔)。
5) 引導映象
體系makefile檔定義了編譯vmlinux檔的目標物件,將它們壓縮和封裝成引導代碼,並複製到合適的位置。這包括各種安裝命令。在Linux中Makefile無法為所有的體系結構提供標準化的方法,因此常需要具體硬體體系結構下makefile提供附加處理規則。
附加處理過程常位於arch/$(ARCH)/下的boot/目錄。
內核編譯體系無法在boot/目錄下提供一種便捷的方法創建目標系統檔。因此arch/$(ARCH)/Makefile要調用make命令在boot/目錄下建立目標系統檔。建議使用的方法是在arch/$(ARCH)/Makefile中設置調用,並且使用完整路徑引用arch/$(ARCH)/boot/Makefile。
例如: #arch/i386/Makefile
boot := arch/i386/boot
bzImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
建議使用"$(Q)$(MAKE) $(build)=
"方式在子目錄中調用make命令。
當執行不帶參數的make命令時,將首先編譯第一個目標物件。在頂層makefile中第一個目標物件是all:。
一個體系結構需要定義一個默認的可引導映射。
增加新的前提檔給all目標可以設置不同於vmlinux的默認目標物件。
例如: #arch/i386/Makefile
all: bzImage
當執行不帶參數的"make"命令時,bzImage文件將被編譯。
6) 常用編譯命令
if_changed
如果必要,執行傳遞的命令。
用法:
$(builtin-target): $(obj-y) FORCE
$(call if_changed,link_o_target)
當這條規則被使用時它將檢查哪些檔需要更新,或命令行被改變。後面這種情況將迫使
重新編譯編譯選項被改變的執行檔。使用if_changed的目標物件必須列舉在$( builtin-target)中,否則命令行檢查將失敗,目標一直會編譯。
if_changed_dep
如果必要,執行傳遞的命令並更新依賴檔。
用法:
%.o: %.S FORCE
$(call if_changed_dep,as_o_S)
當這條規則被使用時它將檢查哪些檔需要更新,或命令行被改變。同時它會重新檢測依賴關係的改變並將生成新的依賴檔。這是與if_changed命令的區別。
7) 定制命令
當正常執行帶編譯命令時命令的簡短資訊會被顯示(要想顯示詳細的命令,請在命令行中加入V=1)。要讓定制命令具有這種功能需要設置兩個變數:
quiet_cmd_ - 將被顯示的內容
cmd_ - 被執行的命令
例如: #
quiet_cmd_image = BUILD $@
cmd_image = $(obj)/tools/build $(BUILDFLAGS) \
$(obj)/vmlinux.bin > $@
targets += bzImage
$(obj)/bzImage: $(obj)/vmlinux.bin $(obj)/tools/build FORCE
$(call if_changed,image)
@echo 'Kernel: $@ is ready'
執行make命令編譯$(obj)/bzImage目標時將顯示:
BUILD arch/i386/boot/bzImage
8) 預處理鏈結腳本
當編譯vmlinux映射時將使用arch/$(ARCH)/kernel/vmlinux.lds鏈結腳本。
相同目錄下的vmlinux.lds.S檔是這個腳本的預處理的變體。內核編譯系統知曉.lds
文件。並使用規則*lds.S -> *lds。
例如: #arch/i386/kernel/Makefile
always := vmlinux.lds
#Makefile
export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)
$(always)賦值語句告訴編譯系統編譯目標是vmlinux.lds。$(CPPFLAGS_vmlinux.lds)
賦值語句告訴編譯系統編譯vmlinux.lds目標的編譯選項。
編譯*.lds時將使用到下面這些變數:
CPPFLAGS : 定義在頂層Makefile
EXTRA_CPPFLAGS : 可以設置在編譯的makefile檔中
CPPFLAGS_$(@F) : 目標編譯選項。注意要使用檔全名。
9) 主機輔助程式的編譯
內核編譯系統支援在編譯階段編譯主機可執行程式。為了使用主機程式需要兩個步驟:第一個步驟使用hostprogs-y變數告訴內核編譯系統有主機程式可用。第二步給主機程式添加潛在的依賴關係。有兩種方法,在規則中增加依賴關係或使用$(always)變數。這一部分的內容相對於其他內核檔的編譯要簡單的多,感興趣的讀者可以參考scripts/Makefile.build中的相關內容。
10) Clean機制
clean命令清除在編譯內核生成的大部分檔,例如主機程式,列舉在 $(hostprogs-y)、$(hostprogs-m)、$(always)、$(extra-y)和$(targets)中目標檔都將被刪除。代碼目錄數中的"*.[oas]"、"*.ko"檔和一些由編譯系統產生的附加檔也將被刪除。
附加檔可以使用$(clean-files)進行定義。
例如: #drivers/pci/Makefile
clean-files := devlist.h classlist.h
當執行"make clean"命令時, "devlist.h classlist.h"兩個檔將被刪除。內核編譯系統默認這些檔與makefile具有相同的相對路徑,否則需要設置以'/'開頭的絕對路徑。
刪除整個目錄使用以下方式:
例如: #scripts/package/Makefile
clean-dirs := $(objtree)/debian/
這樣就將刪除包括子目錄在內的整個debian目錄。如果不使用以'/'開頭的絕對路徑內核編譯系統見默認使用相對路徑。
通常內核編譯系統根據"obj-* := dir/"進入子目錄,但是在體系makefile中需要顯式使用如下方式:
例如: #arch/i386/boot/Makefile
subdir- := compressed/
上面賦值語句指示編譯系統執行"make clean"命令時進入compressed/目錄。
在編譯最終的引導映射檔的makefile中有一個可選的目標物件名稱是archclean。
例如: #arch/i386/Makefile
archclean:
$(Q)$(MAKE) $(clean)=arch/i386/boot
當執行"make clean"時編譯器進入arch/i386/boot並象通常一樣工作。arch/i386/boot 中的makefile檔可以使用subdir-標識進入更下層的目錄。
注意1: arch/$(ARCH)/Makefile不能使用"subdir-",因為它被包含在頂層makefile檔中,在這個位置編譯機制是不起作用的。
注意2: 所有列舉在core-y、libs-y、drivers-y和net-y中的目錄將被"make clean"命令清除。
4 小結
隨著Linux的飛速發展,越來越多的開發人員將關注的焦點集中到Linux的研究和開發上。如果想對Linux內核進行研究和開發,就必須首先熟悉Linux 內核Makefile的組織和編譯過程。目前Linux最新的穩定內核版本為2.6.17,但是當今絕大部分對於Linux Makefile的介紹都是基於2.4內核的,可以說關於2.6內核Makefile相關的文章鳳毛麟角,我特意抽時間完成了這篇分析文章,讓讀者迅速熟悉Linux最新Makefile體系,從而加深對內核的理解,同時也希望能對Linux在公司的推廣起到一定的推動作用。
本文來自CSDN博客,http://blog.csdn.net/mbtrend/archive/2008/10/05/3016889.aspx
--> 閱讀更多...

linux kernel 配置系統----新舊版差異

由於每個人接觸到的核心版本有新有舊,因此會在編譯核心並配置編譯選項後會想去查看各個核心樹裡的Makefile,而要看懂這些makefile須要得知哪些編譯選項是被選取的,然而這種配置方式在2.6.X版後有了更新。因此轉載這兩種不同配置方式如下:
linux内核配置系统分析 内核的 Kconfig & Makefile
linux内核配置系统(舊版)
隨著 Linux 作業系統的廣泛應用,特別是 Linux 在嵌入式領域的發展,越來越多的人開始投身到 Linux 內核級的開發中。面對日益龐大的 Linux 內核原始程式碼,開發者在完成自己的內核代碼後,都將面臨著同樣的問題,即如何將原始程式碼融入到 Linux 內核中,增加相應的 Linux 配置選項,並最終被編譯進 Linux 內核。這就需要瞭解 Linux 的內核配置系統。
  眾所周知,Linux 內核是由分佈在全球的 Linux 愛好者共同開發的,Linux 內核每天都面臨著許多新的變化。但是,Linux 內核的組織並沒有出現混亂的現象,反而顯得非常的簡潔,而且具有很好的擴展性,開發人員可以很方便的向 Linux 內核中增加新的內容。原因之一就是 Linux 採用了模組化的內核配置系統,從而保證了內核的擴展性。
  本文首先分析了 Linux 內核中的配置系統結構,然後,解釋了 Makefile 和設定檔的格式以及配置語句的含義,最後,通過一個簡單的例子--TEST Driver,具體說明如何將自行開發的代碼加入到 Linux 內核中。在下面的文章中,不可能解釋所有的功能和命令,只對那些常用的進行解釋,至於那些沒有討論到的,請讀者參考後面的參考文獻。
1. 配置系統的基本結構
  Linux內核的配置系統由三個部分組成,分別是:
  Makefile:分佈在 Linux 內核原始程式碼中的 Makefile,定義 Linux 內核的編譯規則;
  設定檔(config.in):給使用者提供配置選擇的功能;
  配置工具:包括配置命令直譯器(對配置腳本中使用的配置命令進行解釋)和配置使用者介面(提供基於字元介面、基於 Ncurses 圖形介面以及基於 Xwindows 圖形介面的使用者配置介面,各自對應於 Make config、Make menuconfig 和 make xconfig)。
  這些配置工具都是使用指令碼語言,如 Tcl/TK、Perl 編寫的(也包含一些用 C 編寫的代碼)。本文並不是對配置系統本身進行分析,而是介紹如何使用配置系統。所以,除非是配置系統的維護者,一般的內核開發者無須瞭解它們的原理,只需要知道如何編寫 Makefile 和設定檔就可以。所以,在本文中,我們只對 Makefile 和設定檔進行討論。另外,凡是涉及到與具體 CPU 體系結構相關的內容,我們都以 ARM 為例,這樣不僅可以將討論的問題明確化,而且對內容本身不產生影響。
2. Makefile
2.1 Makefile 概述
  Makefile 的作用是根據配置的情況,構造出需要編譯的原始檔案清單,然後分別編譯,並把目標代碼連結到一起,最終形成 Linux 內核二進位檔案。
  由於 Linux 內核原始程式碼是按照樹形結構組織的,所以 Makefile 也被分佈在目錄樹中。Linux 內核中的 Makefile 以及與 Makefile 直接相關的檔有:
  Makefile:頂層 Makefile,是整個內核配置、編譯的總體控制文件。
  .config:內核設定檔,包含由使用者選擇的配置選項,用來存放內核配置後的結果(如 make config)。
  arch/*/Makefile:位於各種 CPU 體系目錄下的 Makefile,如 arch/arm/Makefile,是針對特定平臺的 Makefile。
  各個子目錄下的 Makefile:比如 drivers/Makefile,負責所在子目錄下原始程式碼的管理。
  Rules.make:規則檔,被所有的 Makefile 使用。
  使用者通過 make config 配置後,產生了 .config。頂層 Makefile 讀入 .config 中的配置選擇。頂層 Makefile 有兩個主要的任務:產生 vmlinux 檔和內核模組(module)。為了達到此目的,頂層 Makefile 遞迴的進入到內核的各個子目錄中,分別調用位於這些子目錄中的 Makefile。至於到底進入哪些子目錄,取決於內核的配置。在頂層 Makefile 中,有一句:include arch/$(ARCH)/Makefile,包含了特定 CPU 體系結構下的 Makefile,這個 Makefile 中包含了平臺相關的資訊。
  位於各個子目錄下的 Makefile 同樣也根據 .config 給出的配置資訊,構造出當前配置下需要的原始檔案清單,並在檔的最後有 include $(TOPDIR)/Rules.make。
  Rules.make 文件起著非常重要的作用,它定義了所有 Makefile 共用的編譯規則。比如,如果需要將本目錄下所有的 c 程式編譯成彙編代碼,需要在 Makefile 中有以下的編譯規則:
    %.s: %.c
    $(CC) $(CFLAGS) -S $< -o $@   有很多子目錄下都有同樣的要求,就需要在各自的 Makefile 中包含此編譯規則,這會比較麻煩。而 Linux 內核中則把此類的編譯規則統一放置到 Rules.make 中,並在各自的 Makefile 中包含進了 Rules.make(include Rules.make),這樣就避免了在多個 Makefile 中重複同樣的規則。對於上面的例子,在 Rules.make 中對應的規則為:     %.s: %.c     $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(*F)) $(CFLAGS_$@) -S $< -o $@
2.2 Makefile 中的變數
  頂層 Makefile 定義並向環境中輸出了許多變數,為各個子目錄下的 Makefile 傳遞一些資訊。有些變數,比如 SUBDIRS,不僅在頂層 Makefile 中定義並且賦初值,而且在 arch/*/Makefile 還作了擴充。
  常用的變數有以下幾類:
  1) 版本資訊
  版本資訊有:VERSION,PATCHLEVEL, SUBLEVEL, EXTRAVERSION,KERNELRELEASE。版本資訊定義了當前內核的版本,比如 VERSION=2,PATCHLEVEL=4,SUBLEVEL=18,EXATAVERSION=-rmk7,它們共同構成內核的發行版本本KERNELRELEASE:2.4.18-rmk7
  2) CPU 體系結構:ARCH
  在頂層 Makefile 的開頭,用 ARCH 定義目標 CPU 的體系結構,比如 ARCH:=arm 等。許多子目錄的 Makefile 中,要根據 ARCH 的定義選擇編譯原始檔案的列表。
  3) 路徑資訊:TOPDIR, SUBDIRS
  TOPDIR 定義了 Linux 內核原始程式碼所在的根目錄。例如,各個子目錄下的 Makefile 通過 $(TOPDIR)/Rules.make 就可以找到 Rules.make 的位置。
  SUBDIRS 定義了一個目錄清單,在編譯內核或模組時,頂層 Makefile 就是根據 SUBDIRS 來決定進入哪些子目錄。SUBDIRS 的值取決於內核的配置,在頂層 Makefile 中 SUBDIRS 賦值為 kernel drivers mm fs net ipc lib;根據內核的配置情況,在 arch/*/Makefile 中擴充了 SUBDIRS 的值,參見4)中的例子。
  4) 內核組成資訊:HEAD, CORE_FILES, NETWORKS, DRIVERS, LIBS
  Linux 內核檔 vmlinux 是由以下規則產生的:
  vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs
   $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o --start-group $(CORE_FILES) $(DRIVERS) $(NETWORKS) $(LIBS) --end-group -o vmlinux
  可以看出,vmlinux 是由 HEAD、main.o、version.o、CORE_FILES、DRIVERS、NETWORKS 和 LIBS 組成的。這些變數(如 HEAD)都是用來定義連接生成 vmlinux 的目的檔案和庫檔列表。其中,HEAD在arch/*/Makefile 中定義,用來確定被最先連結進 vmlinux 的檔清單。比如,對於 ARM 系列的 CPU,HEAD 定義為:
  HEAD      := arch/arm/kernel/head-$(PROCESSOR).o           arch/arm/kernel/init_task.o
  表明 head-$(PROCESSOR).o 和 init_task.o 需要最先被連結到 vmlinux 中。PROCESSOR 為 armv 或 armo,取決於目標 CPU。 CORE_FILES,NETWORK,DRIVERS 和 LIBS 在頂層 Makefile 中定義,並且由 arch/*/Makefile 根據需要進行擴充。 CORE_FILES 對應著內核的核心檔,有 kernel/kernel.o,mm/mm.o,fs/fs.o,ipc/ipc.o,可以看出,這些是組成內核最為重要的文件。同時,arch/arm/Makefile 對 CORE_FILES 進行了擴充:
  # arch/arm/Makefile
  # If we have a machine-specific directory, then include it in the build.
  MACHDIR     := arch/arm/mach-$(MACHINE)
  ifeq ($(MACHDIR),$(wildcard $(MACHDIR)))
  SUBDIRS     += $(MACHDIR)
  CORE_FILES   := $(MACHDIR)/$(MACHINE).o $(CORE_FILES)
  endif
  HEAD      := arch/arm/kernel/head-$(PROCESSOR).o           arch/arm/kernel/init_task.o
  SUBDIRS     += arch/arm/kernel arch/arm/mm arch/arm/lib arch/arm/nwfpe
  CORE_FILES   := arch/arm/kernel/kernel.o arch/arm/mm/mm.o $(CORE_FILES)
  LIBS      := arch/arm/lib/lib.a $(LIBS)
  5) 編譯資訊:CPP, CC, AS, LD, AR,CFLAGS,LINKFLAGS
  在 Rules.make 中定義的是編譯的通用規則,具體到特定的場合,需要明確給出編譯環境,編譯環境就是在以上的變數中定義的。針對交叉編譯的要求,定義了 CROSS_COMPILE。比如:
  CROSS_COMPILE  = arm-linux-
  CC       = $(CROSS_COMPILE)gcc
  LD       = $(CROSS_COMPILE)ld
  ......
  CROSS_COMPILE 定義了交叉編譯器首碼 arm-linux-,表明所有的交叉編譯工具都是以 arm-linux- 開頭的,所以在各個交叉編譯器工具之前,都加入了 $(CROSS_COMPILE),以組成一個完整的交叉編譯工具檔案名,比如 arm-linux-gcc。
  CFLAGS 定義了傳遞給 C 編譯器的參數。
  LINKFLAGS 是連結生成 vmlinux 時,由連結器使用的參數。LINKFLAGS 在 arm/*/Makefile 中定義,比如:
  # arch/arm/Makefile
  LINKFLAGS    :=-p -X -T arch/arm/vmlinux.lds
  6) 配置變數CONFIG_*
  .config 檔中有許多的配置變數等式,用來說明使用者配置
linux内核配置系统(新版) 使用內核的 Kconfig & Makefile
在內核編譯中如何將各個目錄樹中的檔組織起來編譯是一個很重要的問題,並且要根據使用者配置來編譯特有的內核。為瞭解決這個問題,內核使用兩種檔,MakefieKconfig。分佈到各目錄的Kconfig構成了一個分散式的內核配置資料庫,每個Kconfig分別描述了所屬目錄來原始檔案相關的內核配置功能表,就是我們使用命令 make menuconfig(或者xconfig)後產生的配置功能表,此功能表包含多層,每個層次都是由各個目錄中的Kconfig產生的。使用者根據需求來選擇如何編譯內核,然後將配置結果保存到.config中,然後執行Makefile時就會根據.config的結果來實現內核的編譯。
這個過程是由kbuild系統來完成的,Linux編譯系統會兩次掃描Linux的Makefile:首先編譯系統會讀取Linux內核頂層的Makefile,然後根據讀到的內容第二次讀取Kbuild的Makefile來編譯Linux內核。內核編譯系統或者說kbuild,是一種在編譯內核時,可以對內核配置選項進行選擇的機制。2.6內核樹中已經更新了這種機制,新版本的kbuild 不僅高速而且備有更完善的文檔。Kbuild機制完全依賴於原始程式碼的層次結構。
Makefile文件
面對樹狀結構的內核源碼目錄,內核編譯採用了各個子目錄擁有自己目錄相關的Makefile(被稱為sub-Makefile或kbuild Makefile),內核編譯依賴於各個子目錄下的子makefile(sub-Makefile)檔,這些sub-Makefile定義了根據該子目錄下的源碼檔構建目的檔案的規則,並且僅對該目錄下的檔作適當的修改。頂層Makefile採用遞迴的方式調用位元於init/, drivers/, sound/, net/, lib/ ,usr/等目錄下的各個子目錄中的 Makefile檔。在遞迴呼叫之前,kbuild首先要確定是否已經滿足一些必要的條件,包括在必要時更新include/Linux/version.h檔,並設置符號連結include/asm,使之指向與目標體系結構相關的檔。例如,如果為PPC編譯代碼,則include/asm指向include/asm-ppc。kbuild還要對文件include/Linux/autoconf.h和include/Linux/config進行編譯。之後,從根目錄開始進行遞迴。
各個子Makefile檔比較簡單,指出了該如何編譯目的檔案,例如/mm目錄下的Makefile片段:
16 obj-$(CONFIG_PROC_PAGE_MONITOR) += pagewalk.o
17 obj-$(CONFIG_BOUNCE) += bounce.o
18 obj-$(CONFIG_SWAP) += page_io.o swap_state.o swapfile.o thrash.o
19 obj-$(CONFIG_HAS_DMA) += dmapool.o
20 obj-$(CONFIG_HUGETLBFS) += hugetlb.o
Kconfig文件
Kconfig的作用就是為了讓使用者配置內核,在Kconfig中定義了一些變數,使用者通過設置變數的值來選擇如何個性化自己的系統內核。定義的變數將在
每個功能表都有一個關鍵字標識,最常見的就是config
語法:
config
symbol是個新的標記的功能表項目,options是在這個新的功能表項目下的屬性和選項
其中options部分有:

1、類型定義:
每個config功能表項目都要有類型定義,bool布林類型、 tristate三態:內建、模組、移除 string字串、 hex十六進位、 integer整型
例如config HELLO_MODULE
bool “hello test module”
bool 類型的只能選中或不選中,tristate類型的功能表項目多了編譯成內核模組的選項,假如選擇編譯成內核模組,則會在.config中生成一個 CONFIG_HELLO_MODULE=m的配置,假如選擇內建,就是直接編譯成內核影響,就會在.config中生成一個 CONFIG_HELLO_MODULE=y的配置.
2、依賴型定義depends on或requires
指此功能表的出現和否依賴於另一個定義
config HELLO_MODULE
bool “hello test module”
depends on ARCH_PXA
這個例子表明HELLO_MODULE這個功能表項目只對XScale處理器有效。
3、幫助性定義
只是增加幫助用關鍵字help或—help—

.config文件
上面提到了利用內核配置工具自動生成名為.config的內核設定檔,這是編譯內核的第一步。.config檔位於原始程式碼根目錄下,描述所有內核配置選項,可以借助內核配置工具來選擇這些選項。每個內核配置選項都有相關的名字和變數值。其名字形如CONFIG_,其中是對相關選項的標識,在Kconfig檔中定義;變數可以有三個值:y,m或n。y代表“yes”,表示該選項將會被編譯到內核原始程式碼中,或者說會被編譯到系統中。m代表“module” ,表示該選項將會以模組的方式編譯到內核中。如果未選擇該選項(即將該選項的變數值設為n,代表“no”),那麼.config檔中就會出現下列注釋:“CONFIG_ is not set”。.config檔中選項的位置根據它們在內核配置工具中的位置進行排序,注釋部分說明該選項位於哪個功能表下。我們來看看一個.config文件的節選:
1 #
2 # Automatically generated make config: don’t edit
3 #
4 CONFIG_X86=y
5 CONFIG_MMU=y
6 CONFIG_UID16=y
7 CONFIG_GENERIC_ISA_DMA=y
8
9 #
10 # Code maturity level options
11 #
12 CONFIG_EXPERIMENTAL=y
13 CONFIG_CLEAN_COMPILE=
14 CONFIG_STANDALONE=y
15 CONFIG_BROKEN_ON_SMP=y
16
17 #
18 # General setup
19 #
20 CONFIG_SWAP=y
21 CONFIG_SYSVIPC=y
22 #CONFIG_POSIX_MQUEUE is not set
23 CONFIG_BSD_PROCESS_ACCT=y
上述.config檔指出第4到第7行的選項位於頂層功能表中,第12到第15行的選項位於代碼成熟度選項功能表中,第20行到第23行的選項位於通用設置選項功能表中。
所有配置工具都會產生上述功能表,並且已經看到前幾個選項、代碼成熟度選項、及通用設置選項都位於頂層。後面兩個選項被擴展為包含多個選項的子功能表。這些功能表都是在調用xconfig命令時,由qconf配置工具提供的。配置工具顯示的功能表都預設用於X86體系結構。
五、DIY:向內核添加自己的程式
A.在Linux內核中增加自己的程式步驟(注意這裡只是程式檔):
1.將編寫的原始程式碼複製到Linux內核原始程式碼的相應目錄中。
2.在目錄的Kconfig檔中增加新原始程式碼對應專案的編譯配置選項
3.在目錄的Makefile檔中增加對新原始程式碼的編譯條目。
B.在Linux內核drivers/目錄中增加目錄和子目錄步驟:
1.所加目錄為daiq,檔如下:
[daiq@localhost daiq]$ tree
.
-- Kconfig
-- Makefile
-- led
-- Kconfig
-- Makefile
`-- led.c
`-- test.c

#注意此時各個目錄中的Makefile和Kconfig檔是空的
2.在新增的相應目錄添加Kconfig和Makefile檔,上面的目錄中已經添加。
3.修改新增目錄的父目錄的Kconfig和Makefile檔,以便新增的Kconfig和
Makefile能被引用。向父目錄中的Makefile添加:
obj-y += daiq/
表示在編譯過程中包含子目錄daiq目錄。然後修改Kconfig檔,添加:
source “drivers/daiq/Kconfig”
表示在配置時引用子目錄daiq中的設定檔Kconfig。
4.實際上,要讓drivers/daiq/Kconfig有效,要在arch/arm/Kconfig文件中添加:
source “drivers/daiq/Kconfig”
父目錄drivers/Kconfig的修改可以不要。
5.經過上面一步,內核就可以找到所加的目錄daiq了,然後就是編輯各個目錄中的Makefile和Kconfig檔,在你添加的目錄daiq中的Makefile加入:
obj-$(CONFIG_TEST) += test.o #因為在daiq目錄中要編譯test.c文件
#所以會根據CONFIG_TEST來決定編譯選項
obj-y += led/#編譯daiq目錄中的子目錄led
然後Kconfig文件是:
menu "DaiQ device support" #在make menuconfig時要顯示的功能表入口
config DAIQ_TEST
bool "Test"
help
DaiQ device support
source "drivers/daiq/led/Kconfig"
endmenu
注意:menu和endmenu的前後要加回車,不然make menuconfig的時候會出錯。
再看led目錄下的Makefile和Kconfig:
Makefile為文件:
obj-$(CONFIG_LED)+=led.o
Kconfig文件:
config LED
tristate “led support”
5.現在可以make menuconfig來配置添加自己目錄daiq的驅動了!
--> 閱讀更多...

2010年5月3日 星期一

linux study 重點截錄

深入理解 Linux 2.6 的 initramfs 機制
ARM Linux源代码分析(1) for :linux/arch/arm/kernel/head.S
基于arm的Linux的启动分析
linux内核配置系统分析
Kconfig语法_config.in的新格式
MCUOL.com
内核的 Kconfig & Makefile
arm kconfig Serach
内核启动源码分析
為什麼我要學習linux,到底又要學些什麼?至少要有個明確的目標,否則"study linux"這幾個英文字母可是會耗掉我很多的青春(其實我已經沒有青春)。不論是一窩蜂或是潮流影響所致,linux的確給人帶來很大的改變,當大家都在談論它時或許只是一時的流行趨勢;然而那不是我想跟隨的流行;我只想跟著我的想法前進。那麼我的想法是什麼呢?"as far as possible close to Low Level"儘可能的貼進底層
唯有這樣才可能真正了解KERNEL,了解KERNEL的目的是什麼?說真的好處太多,但我只針對我的需求:1.了解KERNEL使我更符合企業需求. 2.通盤了解才可能創新(雖然很難辦到). 3.讓更多的硬體可正常的運作於Linux. 4.所謂通盤了解並不是指要把整個核心 Source Code全部看懂過一遍,當然那是不可能達成的,而是針對主架構的程式碼要懂.....如此內心才不致覺的空虛. 5.提升編程能力.6.消磨時間、使腦袋瓜不會空轉。
--> 閱讀更多...