2010年12月21日 星期二

認識Cortex-M3 ARM MCU

前不久買了這塊開發板,配了一片dvd,裡頭資料超多的;心想這麼多的specification要K到何時。而且這顆MCU雖然在速度上只有ARM9的4分之1不到,但畢竟產品定位不同(它其實是arm7的後繼版本)。然而它內建周邊卻比ARM9強很多;由於這些板子都是對岸的同胞們所製作的產品在價格上都非常有親合力,若想真正進入arm這個領域,現在這一款Cortex-M3是很棒的入門版。別以為入門兩個字就把它想的很簡單,因為其內建周邊非常多,功能性及可作的運用也很多,很多應用領域的controller應該也都是使用這類mcu。若是你想直接進入linux嵌入式系統,那麼可能要選擇含有mmu的Cortex-A8的開發板,當然選擇arm 9及11也是可以,否則只能選擇uclinux (for none MMU)。
由於這麼多的週邊設置,因此已經沒有人會想以整個專案都採用組合語言來開發(非常吃力又難維護)。所以ST公司便針對STM32F10XXX發行了韌體函式庫(大陸那邊稱為固件庫)。所以專案可以採用C來開發。其實說的好像很簡單,但是面對一個新的架構怎麼可能會很快上手呢?除了K SPEC以外就是要不斷的實作;然後再K SPEC。為何會寫這一篇文章,主要就是告訴自己警惕自己,學習這塊板子沒有捷徑;即使K的很痛苦,但是值得。
在此提供一pdf教學檔:Cortex-M3權威指南CnR2(電子書).pdf 。
--> 閱讀更多...

2010年10月25日 星期一

GRUB FOR DOS 的總體框架

文章出處:http://www.linuxeden.com/blog/?uid-19847-action-viewspace-itemid-5490
GRUB FOR DOS 的總體框架
bendany 建議我寫一寫 GRUB4DOS 的大致結構,以便準備投入開發 GRUB4DOS 的朋友們能夠對此有一個快速的瞭解。我試試吧,就寫了這篇短文。文章有什麼不妥的,或者需要補充的,希望大家提出來,我再加以完善。幾年前寫過兩三篇 GRUB4DOS 的技術文章,雖然某些內容有些陳舊,但似乎也還可以用。有需要的朋友,可以在各大搜索引擎中搜索grub for dos 或者 grub.exe 或者 grldr 等字眼,應該能夠找到那些文章。這次不重複那些文章的內容了,主要面向準備繼續開發 Grub4dos 的朋友們。
[@more@]
  GRUB for DOS 是 GNU GRUB 的功能擴展,是以 GNU GRUB 為基礎的。有關 GNU GRUB 的技術資訊,可以由 Linux 下的 info grub 命令獲得。這裏主要說說 GRUB for DOS 的特有資訊,並且只是粗略的、輪廓性的介紹。詳細的資訊還是要通過源代碼才能得到。在關鍵的地方,源代碼中都有比較詳細的注釋。
1。啟動過程,啟動方式
  GNU GRUB 的啟動過程大致是這樣的:512 位元組的 stage1,放在 MBR 或者軟碟第一磁區,它從 BIOS 那裏獲得控制,然後它負責查找 stage1.5 或者 stage2。其實,stage1 在被放置到 MBR 或者軟碟第一磁區的時候,已經把 stage1.5 或者 stage2 的物理磁區位置記錄到 stage1 的某個區域中了,這通常稱為硬編碼。這些都不重要,重要的是,stage1 和 stage1.5 都不是 GRUB 的主體,它們都是為了把 stage2 裝入到記憶體中,除此之外,它們沒有別的用處。
  stage2 又分兩部分:第一部分是 512 位元組的開頭,它像 stage1 一樣,裏面含有 stage2 的物理磁區位置列表,整個磁區沒有別的用處,僅僅只是為了尋找 stage2 的後續部分(也就是從第二磁區開始一直到檔結尾)。這第二部分才是 GRUB 的主程序體,它叫做 pre_stage2。也就是說,stage2 = 512 位元組的頭部 + pre_stage2
stage1 和 stage2 中都含有磁區絕對定位資訊,這些資訊,是在將 GNU GRUB 安裝到 MBR 或者軟碟第一磁區時,由 setup 命令計算之後寫入的。
  說了這麼多,其實只是為了說明一點:pre_stage2 才是對我們的 GRUB FOR DOS 有用的。所以,你在程式中會看到 pre_stage2_start 這個標號。
  GRUB FOR DOS 主要有兩大類啟動方式。一類是從作業系統中啟動(grub.exe),一類是由 BIOS 引導啟動(GRLDR)。
  GRLDR 也可以由 NTLDR 啟動,不過這其實也類似於從 BIOS 啟動,因為由 NTLDR 所賦予的啟動環境,沒有破壞 BIOS 中斷向量。
  GRUB.EXE 可以從 DOS/Windows9x 下運行,也可以經由 kexec 而從 Linux 下運行,或者也可以經由 LILO 或者 syslinux 這類引導器而啟動運行。這又分為兩種情況:在 DOS/Windows9x 下,GRUB.EXE 是被作為 DOS 的可執行檔或者可以作為 CONFIG.SYS 裏的設備驅動檔而運行。在其他情況下,GRUB.EXE 是作為 LINUX 內核格式而被啟動運行的。
2。檔結構
  GRLDR 的檔結構相對來說比較單一,源代碼文件是 grldrstart.S。它的第一磁區,是啟動代碼,如果這是在 MBR 上,它負責裝入位於硬碟第一磁軌上的其餘磁區。如果是經由 NTLDR 啟動,那麼 NTLDR 會裝入 GRLDR 的開頭 16 個磁區。這些磁區中的代碼,負責查找各個硬碟各個分區根目錄下的 grldr 檔,並裝載整個 grldr 檔到記憶體中。緊接著 GRLDR 開頭的 16 磁區,就是 pre_stage2 了。GRLDR 開頭的 16 磁區代碼,負責把 pre_stage2 放到 0000:8200 處,並把控制交給 pre_stage2。在 GNU GRUB 中,0000:8000 處放置的是 stage2,由於我們不用 stage2 的第一磁區,所以,我們直接把 pre_stage2 放置在 0000:8200 了。
  bootlace.com 可以用來將 grldr 的啟動代碼寫入到 MBR 或者軟碟的第一磁區上。其實,寫入到 MBR 上的,不完全就是 GRLDR 開頭的 16 磁區,而是 GRLDR.MBR 檔的內容(只有第一磁區的若干個控制位元組會被改動,以及硬碟分區表會被加入到第一磁區,而第二磁區可以放置硬碟上原來的 MBR 作為備份,其餘磁區都原封不動地被拷貝到第一磁軌的相應磁區上)。bootlace.com 是雙重可執行檔格式:它可以在 DOS 下運行(作為 .com 可執行檔),也可以在 Linux 下運行(作為 ELF 可執行檔)。GRLDR.MBR 檔將來可以增大到 63 個磁區那麼長,這是一個磁軌長度的最大上限。但是 GRLDR 檔的開頭部分卻只能有 16 個磁區那麼長,因為 NTLDR 只能裝入這麼長,它不能為我們直接裝入全部的 GRLDR 檔。GRLDR.MBR 的源代碼文件是 mbrstart.S。bootlace.com 的源代碼文件是 bootlacestart.S。
  GRUB.EXE 是三重檔格式。它的結構比較複雜。首次看源代碼的時候,可以先忽略它作為 DOS 設備驅動程式檔格式的那些代碼,因為那可能比較難以理解。而作為 DOS 的 EXE 格式以及作為 LINUX 的 bzImage 格式,都是比較容易理解的,因為這方面的資料很容易找到。源代碼文件是 dosstart.S。像 GRLDR 的情況那樣,GRUB.EXE 也是由開頭的啟動代碼以及 pre_stage2 這兩部分構成的。
  pre_stage2 的源代碼文件是 asm.S,它會調用其他 C 語言程式檔。這些 C 檔和 asm.S 一起編譯生成 pre_stage2 檔。builtins.c 檔含有各種命令的代碼,包括 map 命令。如果要為 GRUB 增添拷貝檔或者創建檔的命令,則需要修改 fsys_*.c 檔,它們是各種檔系統的驅動程式檔。
3。記憶體的使用
在物理位址 0x800 到 0x17FF 這 4K,放置嵌入到 GRUB.EXE 命令行的那些命令。只有從 CONFIG.SYS 中用 device=grub.exe --config-file="GRUB_COMMANDS" 的方式,才可以嵌入接近 4K 的 GRUB 命令,其他方式都受具體環境的限制。比如,DOS 命令行不超過 127 個字元。GRUB.EXE 作為 Linux 內核格式,它也只能接受最多 512 位元組的命令行。
  物理位址 2M 處放置備份的 640K 的 DOS 常規記憶體,以便 quit 命令可以恢復 DOS 的現場。
--> 閱讀更多...

從啟動到run grub

文章出處:http://bbs.tongji.net/thread-258437-1-1.html
從啟動到run grub
下面借用了 劉 吟(劉老大) OS啟動的第一步的Ppt
Os 啟動第一步
1.Bios
Cpu 的初始化
► 主機加電後,啟動時鐘發生器,在匯流排上產生POWERGOOD信號,CPU收到RESET信號,進入初始化過程
CPU轉入8086實模式。
DS = ES = FS = GS = SS = 0
CS = 0xFFFF
IP = 0XFFF0
進入BIOS加電自檢過程(Power On Self Test)
BIOS初始化
關中斷,進行所有的POST檢測
將中斷向量表的起始位址設為0x0000H
0x0000H~0x03FFH中存放了256個中斷
建立了實模式下的中斷向量表
BIOS的啟動程式調用INT 19h中斷
將控制權轉移給Boot Loader
► INT 19h中斷的功能
INT 19h按照BIOS中的啟動設備順序查詢每個啟動設備在軟碟的啟動磁區或者硬碟的MBR中有Boot Loader,那麼這個磁區的最後兩個位元組必然為0xAA55。BIOS將這個磁區(512個位元組)讀入記憶體的0000:7C00開始的位置,然後跳轉到記憶體0000:7C00的地方開始執行。如果在所有的啟動設備上都找不到Boot Loader,那麼就調用INT 18h,將控制權交給BIOS ROM Basic,鎖定機器,並且在螢幕上顯示NO BOOT DEVICE AVAILABLE
► 結論
Boot Loader應當存放在啟動設備的第一個磁區中,對於硬碟是MBR,對於軟碟是啟動磁區安裝了Boot Loader的啟動磁區的最後兩個位元組必須為0xAA55 BIOS在POST過程結束以後,調用INT 19h中斷,將Boot Loader讀入記憶體0000:7C00處。然後釋放控制權,跳轉到0000:7C00開始執行Boot Loader的代碼。
2.最簡單的Boot Loader:FDISK /MBR
硬碟的分區表結構
a) 第一個磁區為MBR
b) 一個硬碟上最多有4個主分區
c) 第一個主分區一般從cylinder 0, head 1, sector 1開始
d) cylinder 0, head 0, sector 2~n一般來說保留不用
e) 其餘的主分區一般從cylinder x, head 0, sector 1開始

Master Boot Record
f) 位於硬碟的cylinder 0, head 0, sector 1的位置
g) 大小為512位元組
h) 其中存放了4個主分區的入口,每個入口占16個位元組(這就是為什麼一個磁片最多只能有4個主分區的原因)
i) 最後兩位元為啟動標誌,如果MBR中有Boot Loader的話,則為0xAA55
j) 留給Boot Loader的空間為512-16x4-2=446位元組
MBR中的Boot Loader的功能
k) 初始化,將自身搬移到0000:0600的位置
l) 在主分區入口表中尋找活動的分區
m) 調用INT 13h AH=02將活動的分區的啟動磁區讀入記憶體0000:7C00處
n) 跳轉到0000:7C00處執行活動分區的啟動磁區中的代碼
初始化,將自身搬移到0000:0600的位置
0000:7C00 CLI 關中斷
0000:7C01 XOR AX,AX
0000:7C03 MOV SS,AX 將堆疊段(SS)設為0
0000:7C05 MOV SP,7C00 將棧頂指針(SP)設置為7C00
0000:7C08 MOV SI,SP 將SI也設為7C00
0000:7C0A PUSH AX
0000:7C0B POP ES 將ES設為0000
0000:7C0C PUSH AX
0000:7C0D POP DS 將DS設為0000
0000:7C0E STI 開中斷
0000:7C0F CLD
0000:7C10 MOV DI,0600 將DI設為0600
0000:7C13 MOV CX,0100 準備移動256個字(512位元組)
0000:7C16 REPNZ
0000:7C17 MOVSW 將MBR從0000:7c00移動到0000:0600
0000:7C18 JMP 0000:061D 開始搜索主分區入口表
► 在主分區入口表中尋找活動的分區

0000:061D MOV SI,07BE SI指向分區表入口(在總共512byte的主引導記錄中,MBR的引導程式占了其中的前446個位元組(偏移0H~偏移1BDH),隨後的64個位元組(偏移1BEH~偏移1FDH)為DPT(Disk PartitionTable,硬碟分區表),最後的兩個位元組“55 AA”(偏移1FEH~偏移1FFH)是分區有效結束標誌)
0000:0620 MOV BL,04 一共有4個表項
0000:0622 CMP BYTE PTR [SI],80 是否為活動分區
0000:0625 JZ FOUND_ACTIVE 找到了一個活動表項
0000:0627 CMP BYTE PTR [SI],00 是否為非活動分區
0000:062A JNZ NOT_ACTIVE 不可識別的分區標識
0000:062C ADD SI,+10 指向下一個表項(+16)
0000:062F DEC BL 迴圈標誌減一
0000:0631 JNZ 0000:061D 繼續迴圈
0000:0633 INT 18 未找到可啟動的分區,轉到ROM Basic

► 調用INT13 AH = 02h讀取啟動磁區

0000:0635 MOV DX,[SI] 設置INT 13調用中Head和Driver的值
0000:0637 MOV CX,[SI+02] 設置INT 13調用中Cylinder的值
0000:063A MOV BP,SI 保存活動分區入口表項位址

0000:065D MOV DI,0005 設置讀取的重試次數
0000:0660 MOV BX,7C00 將啟動磁區讀取到0000:7C00(ES:BX)
0000:0663 MOV AX,0201 準備調用INT 13讀取一個磁區AL = 01
0000:0666 PUSH DI 保存重試次數DI
0000:0667 INT 13 調用INT 13讀取一個磁區到0000:7c00
0000:0669 POP DI 恢復重試次數DI
0000:066A JNB INT13OK 如果讀取成功,則跳轉至啟動代碼
0000:066C XOR AX,AX 準備INT 13 AH = 0重定磁片
0000:066E INT 13 調用INT 13重定磁片
0000:0670 DEC DI 重試次數減一
0000:0671 JNZ 0000:0660 進行下一次重試

► 運行啟動磁區中的代碼

0000:067B MOV DI,7DFE 指向啟動磁區中的啟動標識
0000:067E CMP WORD PTR [DI],AA55 檢查啟動表識是否為0xAA55
0000:0682 JNZ DISPLAY_MSG 如果不是,則保錯
0000:0684 MOV SI,BP 恢復SI,指向活動分區入口表項
0000:0686 JMP 0000:7C00 跳轉至啟動磁區的代碼

► 結論
Boot Loader放在可啟動設備的第一個磁區中Boot Loader的大小受磁區大小和其他附加資訊的限制。在MBR中,為446位元組Boot Loader在從BIOS中接手CPU的控制權時,位於記憶體位址0000:7C00處FDISK /MBR產生的Boot Loader不具備啟動OS的能力,其本質上是一個Chain Loader,用於引導一個有啟動OS能力的Boot Loader。
3.如何引導OS:DOS Boot Loader
► Boot Sector的結構
Boot Sector位於每個分區的第一個磁區,Boot Sector的第一個部分是一個跳轉指令和一個NOP,以跳轉實際的Boot Loader的代碼中,BIOS Parameter Block中存放了和這個分區相關的一系列參數BPB之後就是實際的Boot Loader的代碼最後是一個可啟動分區標識0xAA55
► 使用Format /s對Boot Sector做的修改,在0x000h處寫入BPB,在0x03Eh處寫入DOS Boot Loader的代碼,在0x1FEh處寫入0xAA55標識。
► 此外,Format /s還要初始化FAT表,將IO.SYS和MSDOS.SYS寫入,佔據FAT表前兩個表項。
► DOS Boot Loader的功能初始化;計算根目錄所在的FAT表的磁區號讀取根目錄的第一個磁區到0000:0500檢查前兩個表項是否為IO.SYS和MSDOS.SYS將IO.SYS的前3個磁區讀入0000:0700或者0070:0000的位置中在寄存器中保留一些資訊,然後跳轉到0070:0000處執行作業系統代碼DOS Boot Loader和MBR中的Boot Loader的最大區別在於對於檔系統的理解。
► MBR中的Boot Loader不理解檔系統,所以無法啟動特定的OS
► DOS Boot Loader提供了對於FAT檔系統的支援,所以能夠啟動在FAT檔系統上的DOS
► DOS Boot Loader知道OS的內核檔的位置,其主要的工作就是將內核檔讀入記憶體,然後將控制權轉交給OS
4.硬碟定址方式:CHS vs LBA
► 最早期的CHS定址,最早期的磁片定址是通過Cylinder/Head/Sector進行的,INT 13h中給出的CHS參數直接指定了資料的物理位置例:INT 13h AH = 02。
► AH = 02 AL = 讀入的磁區數目
► CH = Cylinder低8位 CL = Cylinder高兩位
► DH = Head DL = 操作的磁片(80h for C:)
 限制:
► Cylinder <= 1024
► Head <= 16
► Sector <= 63
► 總容量 < 1024 x 16 x 63 x 512B = 528MB
► L-CHS & P-CHS;為了解決直接定址的問題,現代的HD中,CHS只有邏輯上的意義,不表達物理上的實際位置。L-CHS用在支援CHS Translation的BOIS的INT 13 AH = 0xh的調用中。
► Cylinder <= 1024
► Head <= 256
► Sector <= 63
► 總容量 <= 1024 x 256 x 63 x 512B = 8GB,P-CHS用在HD的介面上
► Cylinder <= 65535
► Head <= 16
► Sector <= 63
► 總容量 <= 65535 x 16 x 63 x 512B = 136GB
► LBA:Large Block Addressing
► LBA的出現是為了解決CHS模式位址受限的問題
► LBA提供了一種線性的定址方式:Cylinder 0,Head 0,Sector 1相當於LBA地址0。往後每增加一個磁區,LBA位址加一
► LBA位址和CHS位址可以通過如下公式轉換
► LBA = ((cylinder * heads_per_cylinder + heads) * sectors_per_track )+sector-1
► 新型的BOIS提供了INT 13h AH = 4xh的擴展磁片調用以支援LBA模式
► LBA:Large Block Addressing;INT 13h AH = 42h
► AH = 42H DL = 操作的磁片(80h for C:)
► DS:SI = Disk Address Packet;Disk Address Packet的格式
► 結論:定址方式和Boot Loader的關係;BIOS在POST過程以後調用INT 19h,使用的是CHS定址;如果一個OS自身的Boot Loader使用的是CHS定址模式,那麼在老式BIOS上不能啟動528MB以後的分區,在新式BIOS上不能啟動8G以後的分區(LILO的問題);只有支持LBA的Boot Loader才能支持啟動8G以後分區上的OS
5.支持多OS引導的Boot Loader
► 支持多OS引導的Boot Loader是:
 一段在啟動時被BIOS INT 19調用的代碼
 能夠理解系統中安裝的多個不同的OS
 用戶可以選擇啟動特定的OS
 程式根據用戶選擇,通過直接載入或者Chain Load的方法,啟動選中的OS
► 支援多OS引導的Boot Loader需要:
 自身足夠的小,或者支持多階段載入,以能夠安裝在MBR或者Boot Sector中
 能夠理解多種檔系統的格式,以便能夠找到存放在不同檔系統下的系統內核
 能夠理解多種檔格式(ELF/a.out),以便能夠正確的載入不同的系統內核
 支援CHS和LBA兩種磁片訪問模式,以便能夠正確的啟動8G以後的分區
 支持Chain Loader,以便通過特定的Boot Loader載入OS
► 支持多OS引導的Boot Loader在執行32位OS的內核代碼前,需要構建的機器狀態:
 CS必須是一個32位的可讀可執行的代碼段,偏移為0,上限為0xFFFFFFFF
 DS, ES, FS, GS和SS必須為32位的可讀寫段,偏移為0,上限為0xFFFFFFFF
 20號地址線必須在32位位址空間中可用(初始固定為0)
 分頁機制必須被關閉
 處理器中斷標記被關閉
 EAX的值為0x2BADB002
 EBX中存放了一個32位的位址,指向由Boot Loader填充的一系列啟動資訊
引自:Multiboot Specification
6.實例分析:Grub
► Grub是:
 GRand Unified Bootloader的縮寫
 是一個靈活而強大的Boot Loader
 其能夠理解多種不同的檔系統和可執行檔格式,從而能夠引導多種OS
 通過將Boot Loader所需要的功能封裝成一套腳本語言,從而能夠按照特定的方式引導OS
► Grub的I/O
 支援CHS和LBA兩種磁片訪問模式
 (device[,part-num][,bsd-subpart-letter])的方式訪問設備:(hd0), (hd1, 0), (hd0, a), (hd0, 1, a)
 檔訪問
► 通過路徑形式訪問:/boot/grub/menu.lst
► 通過磁區形式訪問:0+1,200+1,300+300
► Grub的腳本:
 root指定一個啟動的設備
 kernel指定作業系統的內核
 boot正式啟動一個OS
 makeactive啟動一個分區
 chainloader調用啟動設備上的Boot Loader
► 啟動Linux
 root (hd0,0)
 kernel /vmlinuz root=/dev/hda1
 boot
► 啟動Windows
 root (hd0,0)
 chainloader +1
 makeactive
 boot
► Grub的組成
 Stage1
► Grub的第一部分,安裝在MBR或者Boot Sector中
► 用於引導Stage2或者Stage1.5
 Stage2
► Grub的核心影像,用於提供Grub的主要功能
 Stage1.5
► Stage1與Stage2之間的橋樑,安裝在0磁軌上第一個磁區之後
► Stage1不理解檔系統,但是Stage1.5可以
► Stage1.5最終調用Stage2
 nbgrub/pxegrub
► Grub的網路啟動模組
► Stage1的結構
 為了保持和FAT/HPFS BIOS的相容性,所以保存BPB
 在BPB之後的Stage1配置資料區,在安裝的時候被填寫
 在資料區之後,才是代碼段
 最後是0xAA55啟動磁區標誌

► Stage1的流程
 在Stage1的配置資料區中存放了Stage2所在的磁片號、LBA位址以及Stage2的載入地址
 Stage1不需要理解任何的檔系統,只需要根據給出的磁區號,讀入Stage2的第一個磁區即可
 Stage1相當於前面分析的MBR中的Boot Loader
► Stage2的第一部分Start.S
 Start.S存放在Stage2檔的第一個磁區裏面
 Stage2剩餘部分的LBA位址和記憶體的載入位址是放在Start.S的firstlist和lastlist之間的,這個資料段位於Start.S代碼的尾部,在安裝的時候被寫入,稱為Block List
 Block List以全0項結尾
► Stage2的第一部分Start.S
 在Start.S的代碼開始執行的時候,DS:SI所指向的記憶體位址的內容是Stage1中準備好的,用於為INT 13h調用準備參數
 Start.S的功能就是根據Block List,將Stage2剩餘的部分讀入記憶體,然後跳轉到0x8200h處執行Stage2的功能代碼
► Stage2的第二部分ASM.S
 在ASM.S中定義了一系列的函數的實現,包括Grub得主入口函數main
 在main函數中,完成了如下的工作:
► DS = ES = SS = 0
► 建立實模式/BIOS棧,esp = 0x2000 - 0x10,向低位址方向增長
► 轉入保護模式
► 建立並清空保護模式棧
► 調用cmain,進入Grub的C代碼中(Stage2.c)
► 在cmain中,完成了如下的工作:
 設法打開/boot/grub/menu.lst這個配置檔
 根據配置檔,構建用戶功能表
 如果功能表構建成功,則調用run_menu
 如果功能表構建失敗,則調用enter_cmdline
► 問題:檔系統
 在這個時候,Grub已經開始訪問檔系統
 Grub如何對付不同的檔系統?
► Grub中的檔系統層:Disk_IO.C
 Grub中為每一個檔系統提供了一個抽象層
 檔系統用fsys_entry描述(filesys.h)
struct fsys_entry
{
char *name;
int (*mount_func) (void);
int (*read_func) (char *buf, int len);
int (*dir_func) (char *dirname);
void (*close_func) (void);
int (*embed_func) (int *start_sector, int needed_sectors);
};
 總體變數fsys_table包含了Grub支援所有檔系統,通過fsys_table和fsys_type,從而可以以統一的方式訪問不同的檔系統
 Grub中的命令處理:buildin
 Grub支持的每個命令,均有一個buildin和其對應
 這些buildin被定義在buildins.c中
 分析以下3個命令:
 chainloader
 kernel
 boot

► chainloader
 檢查--force標記
 調用grub_open打開檔,這裏的檔用block list表示(+1)
 調用grub_read讀入一個磁區到0000:7C00的位置
 檢查啟動磁區標誌0xAA55
► kernel
 檢查--type和--no_mem_option標誌
 調用load_image,讀入指定的內核檔
► load_image中處理了ELF和A.OUT的各種變形
► load_image通過對於內核檔的分析,識別出被啟動的OS的種類(通過內核檔的Magic Number)
► load_image針對不同種類的OS的內核提供了特定的載入代碼(這裏的代碼異常複雜,牽涉到了不同的OS的實現細節,未作分析)
► 最終填充mbi (MultiBoot Information)結構
► boot
 通過執行chainloader或者kernel以後,當前需要啟動的內核的類型已經確定了
 在執行boot的時候,根據確定的內核類型,每一種內核均有一種啟動的方法
► 對於linux,最後調用了stop函數實現控制權的轉移
► 對於chainloader,最後也是調用了stop函數實現控制權的轉移
► boot
 通過執行chainloader或者kernel以後,當前需要啟動的內核的類型已經確定了
 在執行boot的時候,根據確定的內核類型,每一種內核均有一種啟動的方法
► 對於linux,最後調用了stop函數實現控制權的轉移
► 對於chainloader,最後也是調用了stop函數實現控制權的轉移
--> 閱讀更多...

2010年10月21日 星期四

GBUB4DOS一開始一定是先處理MBR:stage1.S

stage1.s源代碼分析(很詳盡): 文章出處:http://www.civilnet.cn/blog/browse.php?operation=display&type=blog&entryno=85
Stage1.s原始檔案是用古老的at&t彙編編寫而成,是大名鼎鼎的unix家族作業系統引導程式GRUB中的第一個檔。它編譯後產生的二進位碼正好是512位元組(故意的,也是必須的),剛好填充滿硬碟初始的一個磁區,也即0柱面、0磁軌、1磁區。人們稱之為MBR——主引導記錄。它的作用是載入stage2檔(GRLDR)。閱讀本段代碼,gemfield建議你首先具備以下能力:cpu寄存器、BIOS中斷、PC架構、at&t彙編、GRUB背景知識。幸運的,青島之光論壇(bbs.civilnet.cn)的嵌入系統版塊裏都或多或少包含了這些介紹。並且可以從青島之光論壇上查找stage1.s的源代碼,此處不一一羅列了。
程式剛開始處的巨集定義使用了和gcc相同的規範,定義的3個巨集變數在後面用到的地方再由gemfield詳細闡述。在定義了一個總體變數_start後,程式的真正入口就到了。事實上,在二進位碼中,開始部分的代碼是eb48,其中eb就是jmp的機器碼,在標號_start後,緊跟著的就是這個jmp指令,跳轉到after_BPB處。Jmp後的nop指令,恐怕永遠也不會執行了。注意,剛開機時cpu會調用int19h將第一個磁區的內容調到記憶體位址為0x0000:0x7coo處,你要問為什麼是這個地址或者為什麼會發生這樣的調用,原因大抵和usb為什麼是2根資料線是一樣的。
.=_start + 4是一個讓人困惑的語句,其實這個dot是一個特殊的標號,在as彙編規範中,就代表當前的地址。從開始處的_start處填充空間至_start+4處,相當於4個位元組的空間。但是,從_start開始後的jmp nop 和jmp的參數已經佔用了3個位元組的空間,相當於在它們的後面再用0填充1個位元組的空間即可。
後面緊跟的是一系列稱之為彙編directive的“虛擬指令”。這一部分是對磁片等一些參數進行設置。像起始的磁區、磁軌和柱面以及它們的起始地址、還有stage1的版本號、boot_drive變數、force_lba變數、stage2的位址、磁區、段等參數,這在後面的代碼中涉及的時候再由gemfield闡述,到時候gemfield會稱這部分為初始化參數部分,切記。但在這一系列的參數設置中,還有個相似的語句,就是.=_start+STAGE1_BPBEND,照樣是從上一條指令處填充0直至到達_start+0x3e處。
在jmp之後,清中斷允許位,然後陳列80ca這個二進位碼。80ca就相當於orb $ox80,%dl,意思是給dl寄存器賦值80,要知道,在開機初始, BIOS載入完啟動代碼會把%dl寄存器設置成啟動盤號(boot drive number):
***************************************************************
DL = 00h 1st floppy disk ( "drive A:" )
DL = 01h 2nd floppy disk ( "drive B:" )
DL = 80h 1st hard disk
DL = 81h 2nd hard disk
***************************************************************
硬碟的代號是80,所以上面代表的是stage1裝到硬碟上的情形,如果是軟碟的話,就是orb $0x00,%dl,很顯然,軟盤機代號是0x00。關於boot_drive_mask這一部分,包含的ljmp $0,ABS(real_start)指令的意思是,跳轉到cs:ip = 0x0000:$ABS(real_start)這個地方執行指令。程式的開頭部分定義了ABS這個巨集,在此處就相當於real_start-_start+0x7c00。如果是“正常的”int19h中斷,這句就是廢話。因為物理位址是(Segment value * 16) + Offset value,正常情況下MBR被載入到cs:ip = 0x0000:0x7c00上,而有些糟糕的BIOS會將其載入到07c0:0000上,其實這兩個代表的物理位址是完全一樣的(你可以用上上行的公式計算)。有些人從來就不考慮這種事實,那就是大多數人常常把segment值設為0,這樣引導代碼就可以假定任何段寄存器都是0從而只對付ip裏的偏移量。所以,在grub裏,加上這麼一個長轉移,就防止了這類糟糕的BIOS帶來的大麻煩。 接著進入real_start了,ax清零,ds賦值0,ss賦值0,將STAGE1_STACKSEG(0x2000)賦值給sp,這樣就設置了實模式下的堆疊段位址(棧頂位置)ss:sp = 0x0000:0x2000。接著置中斷允許位,然後檢查是否設置了啟動的磁片。先用MOV_MEM_TO_AL宏將boot_drive量存到al中,然後與0xff進行比較,用的是cmpb $0xff,%al ;je 1f。cmpb指令是將兩個運算元進行相減,對標誌位元的影響同sub指令,但是不保存結果。其中,此處用到的是zf標誌位元(因為是je指令),這樣,當運算元相等(即相減為零時)zf被置1。所以,cmpb和je一起使用時,就是指,當運算元相等時,跳轉至je制定的標號。所以,在這裏,若boot_drive等於0xff,則使用BIOS傳遞過來的默認的驅動器進行啟動;如果不是,movb %al,%dl,將boot_drive的值保存至dl中,表示由boot_drive的值確定啟動設備。不管怎麼樣,現在開始正式啟動了……驅動器號資訊壓棧、輸出資訊“GRUB”,注意,在螢幕上輸出資訊時調用了MSG巨集。下面分析一下這個宏,#define MSG(X) movw $ABS(x),%si ;call message 輸出GRUB字樣時,變數是notification_string,相當於將notification_string位址上的16位元內容送入si寄存器,然後調用message函數,而message函數使用了int10中斷來在螢幕上顯示字元。涉及到串操作指令。message函數:lodsb,從%si指向的源位址中逐一讀取一個字元,送入al中,然後檢查al是否為零,如果為零,表示字元已經傳輸完成了(.string虛擬指令會在指定的字串後加入一個位元組的0),此時調用ret返回。而若不為零,表明字元還未傳輸完,此時跳轉到int 10h“中斷前夕”,用int 10h 的oeh子功能在螢幕上以telemode模式寫字元,其中,ah是子功能號,al是字元,bh是頁,bl是前背景色(在圖形模式下)。所以這裏movw $0x0001,%bx ; movb $0x0e,%ah ;int $0x10(顯示一個字元)就ok了。
在螢幕上顯示完GRUB後,要來決定是進入chs模式還是lba模式(也就是看硬碟是否支援LBA模式,因為兩種模式對硬碟的讀寫等操作有很不一樣的地方),但在這之前,你得首先判斷這裏是硬碟而不是軟碟或者根本就沒有盤(言下之意就是,如果不是硬碟,判斷LBA或者CHS模式就沒有意義了),所以,在判斷硬碟是否支援LBA時,先判斷是不是硬碟。這裏用testb $STAGE1_BIOS_HD_FLAG,%dl來判斷,dl寄存器裏裝載的是磁片號,有三大類情況:硬碟(0x80、0x81)、軟碟(0x00、0x01)、無效的盤(0xff)。而前面的宏就是0x80,所以通過testb和jz指令判斷,如果dl中不是80或81(也就是不是硬碟),就跳轉到chs_mode函數下面。另外,如果此處判斷出是硬碟的話,再接著判斷是否支援LBA,使用的工具就是BIOS的int 13h中斷。 通過 BIOS 調用 INT 0x13 來確定是否支援擴展,LBA 擴展功能分兩個子集 , 如下 : 第一個子集提供了訪問大硬碟所必須的功能 , 包括: ****************************************************************
1.檢查擴展是否存在 : ah = 41h , bx = 0x55aa , dl = drive( 0x80 ~ 0xff )
2.擴展讀 : ah = 42h
3.擴展寫 : ah = 43h
4.校驗磁區 : ah = 44h
5.擴展定位 : ah = 47h
6.取得驅動器參數 : ah = 48h
****************************************************************
第二個子集提供了對軟體控制驅動器鎖定和彈出的支援 ,包括: ****************************************************************
1.檢查擴展 : ah = 41h
2.鎖定/解鎖驅動器 : ah = 45h
3.彈出驅動器 : ah = 46h
4.取得驅動器參數 : ah = 48h
5.取得擴展驅動器改變狀態: ah = 49h ****************************************************************
下面開始具體檢測 , 首先檢測擴展是否存在。此時寄存器的值和 BIOS 調用分別是:AH = 0x41,BX = 0x55AA,DL = driver( 0x80 ~ 0xFF ),然後INT 13H,看返回結果:如果支持CF= 0;否則 CF = 1;CF = 0 (支持LBA) 時的寄存器值代表含義: ****************************************************************
ah:擴展功能的主版本號( major version of extensions )
al:內部使用( internal use )
bx :AA55h ( magic number )
cx:Bits Description
0 extended disk access functions
1 removable drive controller functions supported
2 enhanced disk drive (EDD) functions (AH=48h,AH=4Eh) supported.
Extended drive parameter table is valid
3~15 reserved (0) CF = 1 (不支持LBA) 時的寄存器值 :
ah = 0x01 ( invalid function )
****************************************************************
現在stage1.s使用movb $0x41, %ah;movw $0x55aa, %bx;int $0x13; jc chs_mode來進行上述判斷。如果不支援LBA,則cf就是1,跳轉到chs_mode函數運行。有的bios的int 13h中斷會影響到dl,所以此處用pop和push指令將其保護起來。然而cf不等於1也不表示就支持LBA了,還得再判斷bx是不是aa55h,使用cmpb $0xaa55,%bx ;jne chs_mode再判斷一次,如果bx裏存的不是預期的返回值,同樣不支持lba,也要進入chs_mode函數。這裏有個強制LBA模式要注意下,就是說,當cf是1,bx也是aa55,那麼可以不用在判斷就進入強制LBA模式,代碼是這樣寫的,使用MOV_MEM_TO_AL巨集將force_lba變數值傳遞到al,判斷是否為0。不為零強行進入lba_mode函數。然後判斷cx,如果cx為0的話表明不支持擴展第一子集,這時也進入chs_mode函數。所以總結進入chs_mode的情況,如下: *****************************************************************
第一、 磁片號非80h或81h,進入chs_mode
第二、 int13h,41h子功能,返回cf為0,進入進入chs_mode
第三、 int13h,41h子功能,返回bx不為aa55,進入chs_mode
第四、 如果沒有設置強制LBA,而且也不支持擴展第一子集,進入chs_mode
第五、 其他情況,進入lba模式。 *****************************************************************
那我們就先來分析進入chs模式的代碼,你看,我們是以以上種種情況的發生而進入chs模式的,所以進入chs模式時,再來進行一些檢測,來確定具體的情況。首先就是int13h的08功能號的使用。使用08功能可以檢測chs模式中硬碟的參數,保存在各寄存器裏: *****************************************************************
DL:本機軟碟驅動器的數目
DH:最大磁頭號(或說磁面數目)。0表示有1個磁面,1表示有2個磁面
CH:存放10位元磁軌柱面數的低8位(高2位在CL的D7、D6中)。1表示有1個柱面,2表示有2個柱面,依次類推。
CL:0~5位元存放每磁軌的磁區數目。6和7位元表示10位元磁軌柱面數的高2位。
AX=0
BH=0
BL表示驅動器類型:
1=360K 5.25
2=1.2M 5.25
3=720K 3.5
4=1.44M 3.5
ES:SI 指向軟碟參數表
******************************************************************
如果成功返回參數,則進入final_init函數;但是如果調用失敗,進位元標誌CF=1,AH存放錯誤資訊碼。表明不支援硬碟的chs模式(前面也判斷了不支持lba),那就要考慮是不是軟碟了。再使用testb和jz指令,若dl是00或01,則認為是軟碟,就跳轉到floppy_probe函數執行(後文討論此函數)。但是若連軟碟也不是,只好準備報錯了。跳轉到hd_probe_error函數,這個函數調用MSG函數連同general_error函數一道輸出“hard disk error”的字元。
好了,現在我們回來。剛開始經過一些列的判斷,我們進入了LBA模式。然後,代碼做了以下工作,movl 0x10(%si),%ecx,這個代碼就是個廢話,ecx寄存器被置入了一個無意義的值;然後將標號disk_address_packet處的位址賦給si,再接著將[si-1]記憶體處置1(也就是mode被置1,表示LBA擴展讀;如果是0,就是CHS定址讀)、將stage2的磁區數賦予ebx、在[si]和[si+1]處存放10和00(movw $0x0010,(si))、在[si+2]和[si+3]處存放01和00、在[si+4]和[si+5]處存放00和00、在[si+6]和[si+7]處存放0x00和0x70(這是stage1_bufferseg的值)、在[si+8][si+9][si+A][si+B]處存放0x01/0x00/0x00/0x00、在[si+c][si+d][si+e][si+f]處存放0x00/0x00/0x00/0x00。設置完畢後,開始調用int 13h的42功能中斷。如果出錯,就跳轉到chs_mode處。那麼中斷執行成功呢?
由si及其偏移量指向的記憶體保存著磁片參數塊,如下: ******************************************************************
偏移量 大小 位數 描述
00h BYTE 8 資料塊的大小 (10h or 18h)
01h BYTE 8 保留,必須為0
02h WORD 16 傳輸資料塊數,傳輸完成後保存傳輸的塊數
04h DWORD 32 傳輸時的資料緩存位址
08h QWORD 64 起始絕對磁區號(即起始磁區的LBA號碼) ******************************************************************
所以,通過int13h(42)中斷的作用,硬碟上第二個磁區上的內容就被讀到由si偏移量為4h、5h、6h、7h確定的記憶體區域上了,此處是0x7000:0x0000。執行成功,將bx賦值0x7000,然後跳至copy_buffer子函數處。
LBA已完,gemfield在閱讀copy_buffer前再回頭看當初程式跳至chs_mode後是怎麼運行的。上文中已經指出了,到達chs_mode後經過條件判斷,一共產生了三種情況,第一是進入硬碟的chs子函數(final_init);第二是進入軟碟副程式(floppy_probe);第三種情況是進入報錯子函數,在螢幕上輸出一系列錯誤。那就由gemfield從第一種情況開始吧。程式運行到final_init後,將磁區數保存到si、設置mode為0、eax清零為存放磁頭數做準備、將dh中存放的磁頭數保存到al中、使用incw %ax指令(因為磁頭數是以0~n-1方式排列的,所以增1後才是真正的磁頭數)、將磁頭數保存至[si+4][si+5][si+6][si+7]記憶體位址上、清dx為存放磁區數做準備、cl中的0~5位元存放的是磁區數,所以dx邏輯左移2位元後在dh中出現的兩位就是柱面數的高2位,並且把這2位移到ah中,而ch存放的柱面數低8為移至al中,這樣ax裏就是柱面數了,這裏因為同樣的道理要進行incw %ax操作,並且把真正的柱面數放到位址為[si+8][si+9]的記憶體上、然後用同樣的移動方法產生真正的磁區數並保存在位址為[si][si+1][si+2][si+3]的記憶體上。
然後在使用int 13h(0x02)功能前要進行必備的參數設置:eax存放stage2的磁區編號(stage2_sector,默認為1)、清edx寄存器、然後通過(stage2磁區數)/(磁區數)獲得引導磁區數。注意對於div指令來說,eax恒定存放被除數,div後面的寄存器存放的是除數。餘數在edx中存放,第一個餘數(磁區數)放到位址為[si+10]的記憶體上並將edx清零、再用(上一步除法的商) /(磁頭數)得到的餘數為磁頭數,存放在[si+11]記憶體位址上。商為柱面數並存放在eax中並同時保存至[si+12][si+13]記憶體位址上。然後將之前中斷獲得的柱面數與此處stage2所占柱面數相比較,如果stage2柱面數大,那麼明顯錯誤,程式將跳至geometry_error處。
現在,將[si+13]的內容賦值給dl(柱面數的高2位)並且左移6位元、將磁區數放到cl中再增1、然後通過orb %dl,%cl和movb 12(%si),%dh指令達到這麼一種情況,即:cl中存放的是磁區數和柱面數的高2位,ch中存放的是柱面數的低8位、然後恢復驅動器號(popw %dx)、然後將磁頭數放置到dh中,然後將0x7000賦值給es並將bx清零,賦值0x0201給ax(獲得中斷功能號),參數現在設置完畢,開始調用int 13h中斷: ******************************************************************
%al = number of sectors(需要讀的磁區數)
%ah= 0x02(功能號)
%ch = cylinder(起始柱面數)
%cl = sector (bits 6-7 are high bits of "cylinder")
%dh = head
%dl = drive (0x80 for hard disk, 0x0 for floppy disk)
%es:%bx = segment:offset of buffer ******************************************************************
調用中斷後,將0柱面、0磁軌、2磁區的內容讀到0x7000:0x0000記憶體處。然後程式跳轉至copy_buffer處,和LBA殊途同歸呀。
我們看看copy_buffer做了什麼。將0x8000賦值給es、給cx賦值0x100、給ds賦值0x7000、si和di清零、方向標誌DF置零,然後使用rep和movsw指令將ds:si處連續的512位元組內容傳輸到es:di指定的記憶體位址(0x8000:0x0000)。其中,rep指令的含義就是重複執行後一句指令,沒執行一次。cx減1,直至cx為0。這也是前面cx賦值0x100(256)的原因。movsw每次傳輸一個字,256次就是512位元組。然後popw %ds; popa還原寄存器。
接著,程式跳轉到0x8000處繼續執行,到此就開始執行新的模組了,stage1的任務也已經結束了。代碼中*(stage2_address)的星號是at&t彙編的規範:絕對跳轉/調用(相對於與程式計數器有關的跳轉/調用)運算元前面要加星號"*"。
然而,前面所述的chs模式中的第二種情況——軟盤機情況將會帶領gemfield進入floppy_probe子函數,此處要使用int 13h(0x00功能號)來進行軟盤機的復位。成功的話cf=0;然後準備調用int 13h(功能號是0x02),這和chs中的int 13h,ah=0x02是一樣的。所以,先來為中斷準備必須的參數:軟盤機復位後,將[si]處的值賦給cl(cl是起始磁區數),我們知道,由於迴圈,我們給了cl 4次機會,因為迴圈中有incw %si指令,所以si中的值是遞增的,從probe_values開始,在每一次的機會中依次給cl賦予了0x24、0x12、0x0f、0x09這幾種值,當然,試完後還不對的話就要執行報錯函數了。
像以前那樣,依次準備好bx、ah、al、ch、cl、dh的值後,就要int 13h了。成功後,dh賦值1、ch賦值0x4f,dh 設置為 79 , 表示柱面最大值為 79(80柱:0~79),dh 設置為 1 , 表示磁頭數最大值為 1(2頭:0~1),然後跳轉至 final_init,在上文中關於final_init的分析 , 我們知道保存時會把柱面和磁頭分別加 1 , 磁區不變,因此 , 在軟碟載入時 , 將設置 Cylinder : Head : Sector = 80 : 2 : start_sector。最終就跳轉至final_init函數處執行了。
gemfield的本文中,依然要注意的還有為了相容性而設置的windows nt魔術頭標識的偏移、part_start作為標識的分區表起始位址的標記的偏移、以及引導磁區結束標誌0xaa55。
總的說來,在gemfield這篇稍顯淩亂的文章裏,主要介紹了stage1.s的使命,簡介來說,就是開機時首先被BIOS INT19H裝載到記憶體0x7c00處,然後判斷chs和lba模式,然後使用int13h中斷將磁片上第二磁區的內容讀到0x7000處,然後通過子函數copy_buffer再將其調到0x8000的位置上,這個第二磁區的內容就是以後gemfield的嵌入系統版塊中將要介紹的start.s模組。
--> 閱讀更多...

2010年10月20日 星期三

GRUB2太龐大,所以先轉戰GRUB4DOS

圖一
圖二
下載http://nufans.net/grub4dos/current_release/ 之grub4dos-0.4.4-2009-10-16-src.zip,在我的ubuntu910 下./configure結果產生錯誤如下:
checking whether objcopy works for absolute addresses... no
configure: error: GRUB requires a working absolute objcopy; upgrade your binutils
結果查了一下我的binutils版本已經是最新的。隨後又到其他連結download其它版本的source code,編譯一樣產生相同錯誤。所以本來想放棄,後來想說反正我的virtualbox裡頭還有ubuntu710版本,其gcc也相對比較舊。果不其然原來是binutils太新了,就這樣可以正常編譯成功了。後來我又試了其他版本的binutils 其objcopy版本只要大於GNU objcopy (GNU Binutils for Ubuntu) 2.18.0,則執行./configure就會出錯。
它的使用方法網路上已經很多了在此就不贅述。還是來研究它的source code,比較實際。
首先進入source code目錄,並下make clean及make distclean,接著再作一次./configure並產生Makefile。查看目錄結構如圖一,圖二為其他一起產生的子目錄Makefile;今天我們先說明這個Makefile如何產生的吧!其實這個在之前已經說明過,而且又查獲此處講解autoconf及automake,說明的太詳細了,所以直接參考它就可以了。
--> 閱讀更多...

2010年10月3日 星期日

Bochs install for smp



Bochs安裝在windows時從http://sourceforge.net/projects/bochs/files/bochs/2.4.5/下載下來的Bochs-2.4.5.exe 雖然可以直接使用,而且還很好用,在windows下若要使用debug可以呼叫bochsdbg.exe,正常使用狀況一般都呼叫bochs.exe;並針對配置檔:bochsrc.bxrc作修改便可以使用。但是今天我的須求必須能夠執行在smp的狀況下;然而使用Bochs-2.4.5.exe 所安裝的版本是不支援smp的,因此必須下載source code,自己編譯及安裝,由於sourceforge提供了bochs-2.4.5-msvc-src.zip for windows vs2008的source code,下載下來安裝總是失敗,我想應該是什麼選項沒有去打開所造成的;這部分等我以後編譯成功再來說明。也因為沒有辦法在windows下使用smp並作debug,因此我便下載bochs-2.4.5.tar.gz ,並到linux下去build;以下是building and install的過程說明:
要在linux下安裝bochs首先必須先安裝幾個套件:g++、libc6-dev、build-essential、xorg-dev、libgtk2.0-dev;若是你使用ubuntu的Synaptic可以很快速的將這些套件安裝完成;若你是使用red hat linux,則請自行找到相對應的rpm檔。
或安裝build-essential
我們知道編譯不過是幾個指令便可完成:./configure及make和make install。然而我們必須清楚到底我們要安裝哪些功能;
因此我們可以先下./configure --help從畫面上便可得知可設定的選項有哪些。所以我為了能夠順利編譯for SMP功能的bochs;所下指令如下:
(1)./configure --enable-smp --enable-x2apic --enable-debugger --enable-disasm --enable-vmx=2 --enable-configurable-msrs --enable-x86-64 --enable-a20-pin --enable-acpi --enable-pci --enable-gdb-stub
(2)make
(3)sudo make install
註:--enable-gdb-stub--enable-debugger是互斥的
install完成時,在/usr/local/bin會有三個執行檔:bochs bxcommit bximage;但和windows狀況下不一樣的是少了bochsdbg;其實應該說debugger的功能也整合到bochs執行檔了。
接下來是如何使用bochs;首先是建立一個image檔,這部分可參考很多地方,請自行搞定;接著修改bochsrc配置檔:由於我的例子是使用floppy來boot,若是有使用ide boot請自行修正;
###############################################################
# bochsrc begin.
###############################################################
# how much memory the emulated machine will have
megs: 32cpu: count=2, ips=10000000# filename of ROM images
romimage: file=$BXSHARE/BIOS-bochs-latestvgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
# what disk images will be used
floppya: 1_44=MultiCore.img, status=inserted

# hard disk
# ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
# ata0-master: type=disk, path="hd10meg.img", cylinders=306, heads=4, spt=17

# choose the boot disk.
boot: a

# default config interface is textconfig.
#config_interface: textconfig
#config_interface: wx

#display_library: x
# other choices: win32 sdl wx carbon amigaos beos macintosh nogui rfb term svga

# where do we send log messages?
log: bochsout.txt
############################################################### bochsrc end.
###############################################################
有了cpu: count=2, ips=10000000,這一行,便可產生雙核心了。
只要到含有bochsrc檔的目錄下執行以下指令便可開始進行debug了!
--> 閱讀更多...

2010年9月20日 星期一

關於VirtualBox安裝ubuntu時的網路設定

由於每個人的網路環境不同,但大致上都是使用ADSL撥接或是直接設定固定IP(或連接IP分享器,使用DHCP配置IP)。然而再網路搜尋如何設定ubuntu的網路芳鄰及上網設定,很多人都遇到不少問題,而且設定方式都不相同;我想這是因為沒有考慮到VirtualBox的版本問題才會這樣。因此要設定網路分享必須安裝samba,這部分的介紹很多,我就不提,然而3.0之前的版本必須在virtualbox先安裝Host only adapter的Driver,並且還要到控制台/網路連線去設定橋接。其實說麻煩倒是還好,但是有時還是會失敗,失敗原因我們現在也不考究;因為我們今天要說明的是virtualbox3.0以後的版本。或許有些人會使用2.x版的作法來設定3.x版;其實這樣不是很正確。這也是我之前常犯的錯誤。首先我們先來了解其網路設定提供了哪些function:
VirtualBox 提供五種網路模式:
1.not attached----未附掛網路,就是沒有網路。
2.NAT (network address translation)----將本機 (HOST) 當作是 gateway。這是預設值,特性是 VM 能上網,但是別人不能跟他溝通。
3.Bridged Adapter----橋接器。直接透過實體網卡連外,也就是說可上網而 HOST 和其他 VM 都能跟他對連。
4.internal networking----內部網路。把 HOST 當作 hub,VM 有內網可以互相溝通但不能跟 HOST 溝通。當然也不能連外網路。
5.Host-only Adapter。顧名思義只能跟 HOST 互通,其他 VM 和對外網路都不行。
看到這裡我想大家都已經知道要選擇第3項,但是別急;一開始我就說畢竟每個網路環境不同;因此,若是你用ADSL撥接,那麼你可能還要作別的事。
首先我們先來確定需求:
1連上網際網路(Internet) 2透過網路芳鄰讓所有電腦不管是host還是guest都可以共享資料。
現在說明如何設定:
Virtualbox 在 3.0 版後簡化了網路設定,要設定 bridge adapter只需在網路設定頁面 Attached to: 選擇 Bridged Adapter,Name: 選擇要和 VM 作 bridge 的實體網卡即可。(若你是固定ip或ip分享器使用者,那麼你已經完成了virtualbox的設定,只剩下ubuntu的設定就可以了:這部分包括samba及將你在ubuntu的etnx設成和host同業的網段,等一下會舉例)。現在是針對ADSL撥接使用者的說明,雖然Bridge Adapter可以連上網,但是當透過adsl連線時,Bridge是無法與其對應的,這時你我們就可以新增一張NAT網卡,我們知道NAT就是將本機 (HOST) 當作是 gateway,若是HOST可以上網,那麼VM就可以上網。但是設定時有一點要注意:首先移除既有的Bridge網卡,然後新增NAT網卡,此時在只有一張NAT網卡的狀況下,先將ubuntu開機,並連上internet測試是否連線正常,若確定正常上網就先關機,然後再新增第二張Bridge Adapter網卡;為了能順利在ubuntu區別這兩張網卡,請先記錄其MAC Address。再此開機進入ubuntu時,進入網路的編輯,並對Bridge網卡從DHCP選項改成手動選項,假設HOST目前網路設定如下IP:192.168.1.12 MASK:255.255.255.0 Gateway:None DNS:None;那麼我們就將這張Bridge手動設定為IP:192.168.1.13 MASK:255.255.255.0 Gateway:192.168.1.12 DNS:None;此時設定完成請重新開機;因為不保證以下指令有效sudo /etc/init.d/networking restart;重開機後若是你的samba已設定完成,則網路芳鄰應該就可以使用了。請切記一定要先在單一NAT網卡先測試能正常連internet,再去新增bridge網卡,否則順序相反會造成網路芳鄰有效,但連internet失敗。
--> 閱讀更多...

2010年8月12日 星期四

關於APIC的見解

APIC(高級可編程中斷控制器)對計算機來講有兩個作用, 管理IRQ的分配,可以把傳統的16個IRQ擴展到24個(傳統的管理方式叫PIC),以適應更多的設備。
管理多CPU。由於Nf2主板並不支援多CPU,所以,APIC關閉直接的影響是減少了可用的IRQ。 不過,如果板卡不是非常多的話,關閉 APIC對系統是沒有什麼影響的。要實現SMP功能,我們使用的CPU必須具備以下要求:CPU 內部必須內置APIC單元。Intel 多處理規範的核心就是高級可編程中斷控制器(Advanced Programmable Interrupt Controllers–APICs)的使用。CPU通過彼此發送中斷來完成它們之間的通信。通過給中斷附加動作(actions),不同的CPU可以在某種程度上彼此進行控制。每個CPU有自己的APIC(成為那個CPU的本地APIC),並且還有一個I/O APIC來處理由I/O設備引起的中斷,這個I/O APIC是安裝在主板上的,但每個CPU上的APIC則不可或缺,否則將無法處理多CPU之間的中斷協調。以上內容截自http://boy-asmc.blogspot.com/2009/11/acpi-vs-apic.html
一般而言,若要得知BIOS是否支援APIC時會先到e0000H~f0000h之間尋找20445352h(" DSR")及20525450h(" RTP")字樣;若找到後接下來CHECK接下來的20Bytes其check_sum是否等於0。接著以目前的es:dword ptr[10h]的值為index並以es:word ptr[index+4]為cx當成loop迴圈,來搜尋"CIPA"字樣,便可找到APIC Table。
更詳細的內容請參考Intel64 and IA-32 Architectures Software Developer's Manual-3A.pdf 之Chapter 10。另外參考:
http://www.cs.usfca.edu/~cruse/cs630f06/lesson27.ppt
http://faculty.qu.edu.qa/rriley/cmpt507/minix/apic_8c-source.html
--> 閱讀更多...

2010年7月13日 星期二

關於linux/ctype.h

/include/linux/ctype.h此路徑為相對路徑,其絕對路徑依照linux源碼樹為參考:例如我的源碼樹存放在/opt/FriendlyARM/mini2440/linux-2.6.32.2;所以絕對路徑如下:/opt/FriendlyARM/mini2440/linux-2.6.32.2/include/linux/ctype.h
/include/linux/ctype.h代碼如下
#ifndef _LINUX_CTYPE_H
#define _LINUX_CTYPE_H
/*
* NOTE! This ctype does not handle EOF like the standard C
* library is required to.
*/
#define _U 0x01 /* upper */ /* 大寫字母A-Z*/
#define _L 0x02 /* lower */ /* 小寫字母a-z */
#define _D 0x04 /* digit */ /* 數字0-9 */
#define _C 0x08 /* cntrl */ /* 控制字元 */
#define _P 0x10 /* punct */ /* 標點符號*/
#define _S 0x20 /* white space (space/lf/tab) */ /* 空白字元:空格、\t、\n 等 */
#define _X 0x40 /* hex digit */ /* 十六進位數 */
#define _SP 0x80 /* hard space (0x20) */ /* 空格字元0x20 */
extern unsigned char _ctype[];
#define __ismask(x) (_ctype[(int)(unsigned char)(x)])

#define isalnum(c) ((__ismask(c)&(_U∣_L∣_D)) != 0)
#define isalpha(c) ((__ismask(c)&(_U∣_L)) != 0)
#define iscntrl(c) ((__ismask(c)&(_C)) != 0)
#define isdigit(c) ((__ismask(c)&(_D)) != 0)
#define isgraph(c) ((__ismask(c)&(_P∣_U∣_L∣_D)) != 0)
#define islower(c) ((__ismask(c)&(_L)) != 0)
#define isprint(c) ((__ismask(c)&(_P∣_U∣_L∣_D∣_SP)) != 0)
#define ispunct(c) ((__ismask(c)&(_P)) != 0)
#define isspace(c) ((__ismask(c)&(_S)) != 0)
#define isupper(c) ((__ismask(c)&(_U)) != 0)
#define isxdigit(c) ((__ismask(c)&(_D∣_X)) != 0)

#define isascii(c) (((unsigned char)(c))<=0x7f) #define toascii(c) (((unsigned char)(c))&0x7f)
static inline unsigned char __tolower(unsigned char c) { if (isupper(c)) c -= 'A'-'a'; return c; } static inline unsigned char __toupper(unsigned char c) { if (islower(c)) c -= 'a'-'A'; return c; } #define tolower(c) __tolower(c)
#define toupper(c) __toupper(c)
#endif

以下是/lib/ctype.c
#include
unsigned char _ctype[] = {
_C,_C,_C,_C,_C,_C,_C,_C, /* 0-7 */
_C,_C∣_S,_C∣_S,_C∣_S,_C∣_S,_C∣_S,_C,_C, /* 8-15 */
_C,_C,_C,_C,_C,_C,_C,_C, /* 16-23 */
_C,_C,_C,_C,_C,_C,_C,_C, /* 24-31 */
_S∣_SP,_P,_P,_P,_P,_P,_P,_P, /* 32-39 */
_P,_P,_P,_P,_P,_P,_P,_P, /* 40-47 */
_D,_D,_D,_D,_D,_D,_D,_D, /* 48-55 */
_D,_D,_P,_P,_P,_P,_P,_P, /* 56-63 */
_P,_U∣_X,_U∣_X,_U∣_X,_U∣_X,_U∣_X,_U∣_X,_U, /* 64-71 */
_U,_U,_U,_U,_U,_U,_U,_U, /* 72-79 */
_U,_U,_U,_U,_U,_U,_U,_U, /* 80-87 */
_U,_U,_U,_P,_P,_P,_P,_P, /* 88-95 */
_P,_L∣_X,_L∣_X,_L∣_X,_L∣_X,_L∣_X,_L∣_X,_L, /* 96-103 */
_L,_L,_L,_L,_L,_L,_L,_L, /* 104-111 */
_L,_L,_L,_L,_L,_L,_L,_L, /* 112-119 */
_L,_L,_L,_P,_P,_P,_P,_C, /* 120-127 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 128-143 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 144-159 */
_S∣_SP,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P, /* 160-175 */
_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P, /* 176-191 */
_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U, /* 192-207 */
_U,_U,_U,_U,_U,_U,_U,_P,_U,_U,_U,_U,_U,_U,_U,_L, /* 208-223 */
_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L, /* 224-239 */
_L,_L,_L,_L,_L,_L,_L,_P,_L,_L,_L,_L,_L,_L,_L,_L}; /* 240-255 */

所以由此我們可以來驗證:
大寫S=58h=88 查_ctype得到_U = 0x01
因此我們以以下幾個巨集來求證:
isalnum("S")=(0x01)& (ox01 ∣ 0x02 ∣ 0x04) =1,所以得到true
isalpha("S")=(0x01)& (ox01 ∣ 0x02) =1,所以得到true
iscntrl("S")=(0x01)& (ox08) =0,所以得到false
isdigit("S")=(0x01)& (ox04) =0,所以得到false

♫=0eh=14 查_ctype得到_C = 0x08
因此我們以以下幾個巨集來求證:
isalnum("S")=(0x08)& (ox01 ∣ 0x02 ∣ 0x04) =0,所以得到false
isalpha("S")=(0x08)& (ox01 ∣ 0x02) =0,所以得到false
iscntrl("S")=(0x08)& (ox08) =1,所以得到true
isdigit("S")=(0x08)& (ox04) =0,所以得到false
參考更多特殊字碼:http://cowwu.myweb.hinet.net/note/text/930421a.htm
ASCII code:http://home.educities.edu.tw/wanker742126/index.html
--> 閱讀更多...

2010年7月11日 星期日

如何在32bits保護模式下作debug

以下我所論及的主題並非是在os的保護模式底下的debug方式;而是當你有自己的執行系統,這個執行系統是從16bits模式執行後進入32bits保護模式,並且執行自己定義的ap。就類似memtest86這種從booting到run ap都能一手掌控的代碼。然而memtest在保護模式下都是C語言代碼,因此要debug相對容易,所以我們要討論的是以assembly為架構的debug方式。
首先我們知道frame buffer是以為址0xb8000~0xb8fff為vga顯示控制區域;當然其他模式mapping到的位址也不同;但我們討論的是前者。雖然在dos或16bits模式可以使用int 10h來讀寫螢幕,我們也知道int 10h的作法也是去控制frame buffer。然而進入保護模式下傳統的bios及dos中斷已不能調用。但是我們還是可以直接去存取frame buffer;所以我寫了比下代碼以assembly的方式來顯示一些除錯用途的資訊:
以下資訊為如何在保護模式下debug,並利用健盤做單步debug
;清除所有的輸出緩衝器資料
push ax
FlushOBF: in al,64h
test al,1
jz NotOBF
in al,60h
jmp short FlushOBF
NotOBF: pop ax


push ecx
push ebx
mov ecx,edi
POS_XY bx,20,23
call sub32_hexprint
pop ebx
pop ecx

;等待8042輸出緩衝器有資料送進來
push eax
WaitOBFLoop:
in al,64h
test al,1
jz WaitOBFLoop

pop eax
;以上這小段完成健盤單步執行判斷,做法是當無按鍵被按下時,便一直執行迴圈
;用法:
;先push ecx和ebx
;將要顯示的資料存到ecx
;POS_XY bx,__x,__y
;call sub32_hexprint
;pop ecx和ebx

sub32_hexprint proc
pushad
mov eax,160
mul bl
add eax,SCREEN_ADR
xchg eax,edx
mov eax,2
mul bh
add edx,eax
mov bx,8 ;count
xor edi,edi
NEXT:
call cl2digital
mov BPTR[edx+edi],al
shr ecx,4
add edi,2
dec bx
jnz NEXT
popad
ret
sub32_hexprint endp

cl2digital proc
xor eax,eax
push ecx
and ecx,0fh
cmp cl,10
jae isA2F
add cl,30h
jmp exit
isA2F:
sub cl,10
add cl,41h
exit:
mov al,cl
pop ecx
ret
cl2digital endp

下面是marco
SCREEN_ADR equ 0b8000h
POS_XY macro r_, x_, y_
mov r_, (((x_) SHL 8) + (y_))
endm


以下為鍵盤控制指令參考:
; ========================================================
; 以下為8042界面函數之內部呼叫函數
; ========================================================
;
Flush8042 PROC
; 清除所有的輸出緩衝器資料
push ax
FlushOBF: in al,64h
test al,1
jz NotOBF
in al,60h
jmp short FlushOBF
NotOBF: pop ax
retn
Flush8042 ENDP
;
WaitIBF PROC
; 等待8042輸入緩衝器有空
WaitIBFLoop: push ax
in al,64h
test al,2
pop ax
jnz WaitIBFLoop
retn
WaitIBF ENDP
;
WaitOBF PROC
; 等待8042輸出緩衝器有資料送來
WaitOBFLoop: push ax
in al,64h
test al,1
pop ax
jz WaitOBFLoop
retn
WaitOBF ENDP
;
Read8042Data PROC
; 讀取8042回應資料
; 傳回: AL = 回應資料
call WaitOBF ; 等待資料回庄
in al,60h
retn
Read8042Data ENDP
;
Write8042Data PROC
; 送出資料給8042
; 參數: AL = 8042系統命令或資料
; 備註: 本函數亦為送出系統命令或資料的函數
call WaitIBF ; 等待輸入緩衝區有空
out 60h,al ; 送出資料
call WaitIBF ; 確認8042收到
retn
Write8042Data ENDP
;
RealSend8042Cmd PROC
; 送出一般命令碼給8042
; 參數: AL = 8042一般控制命令
; 備註: 8042命令之參數或傳回值由外界處理
call WaitIBF ; 等待輸入緩衝區有空
out 64h,al ; 送出命令
call WaitIBF ; 確認8042收到
retn
RealSend8042Cmd ENDP
;
RealSend8042Sys PROC
; 送出系統命令碼或參數給8042
; 參數: AL = 8042系統控制命令或參數
; 傳回: AL = 8042回應值
; 備註: 除回音外,其餘命令或參數應檢查是否傳回ACK
call Flush8042
call Write8042Data
call Read8042Data
retn
RealSend8042Sys ENDP
;
; ========================================================
; 以下為8042界面函數,使用前必須使用CLI將岔斷禁能
; ========================================================
;
Send8042Cmd PROC
; 送出一般命令碼給8042
; 參數: AL = 8042一般控制命令
call RealSend8042Cmd
retn
Send8042Cmd ENDP
;
Read8042Cmd PROC
; 送出讀取命令給8042
; 參數: AL = 8042讀取命令
; 傳回: AL = 讀取值
call Flush8042
call RealSend8042Cmd
call Read8042Data
retn
Read8042Cmd ENDP
;
Write8042Cmd PROC
; 送出寫入命令給8042
; 參數: AL = 8042寫入命令
; AH = 寫入資料
; 備註: AX值會被破壞
call RealSend8042Cmd
xchg al,ah
call Write8042Data
retn
Write8042Cmd ENDP
;
Echo8042 PROC
; 送出8042回音命令
; 傳回: AL = EEh
mov al,0EEh ; 回音命令
call RealSend8042Sys
retn
Echo8042 ENDP
--> 閱讀更多...

2010年7月6日 星期二

u-boot 顯示logo,並說明其原理part1


jpegtopnm Benson.jpg l ppmquant 31 l ppmtobmp -bpp 8 > Benson-8bit.bmp
紅色l是管線符號,因無法正常顯示所以用小寫L取代
以上這個指令必須先安裝netpbm,其作用是將jpeg檔轉成特定格式的bmp檔。將Benson-8bit.bmp更改檔名為denx.bmp並存到tools\logos,重新compile u-boot,便可完成,如上圖。接著我們來說明其draw logo原理:
在/tools/Makefile:
LOGO_H = $(OBJTREE)/include/bmp_logo.h
ifeq ($(LOGO_BMP),)
LOGO_BMP= logos/denx.bmp
endif
ifeq ($(VENDOR),atmel)
LOGO_BMP= logos/atmel.bmp
endif
$(LOGO_H):$(obj)bmp_logo $(LOGO_BMP)
$(obj)./bmp_logo $(LOGO_BMP) >$@ 這個$@就是bmp_logo.h
也就是說$(LOGO_H)這個目標的必要條件是bmp_logo denx.bmp這兩個檔案,才能產生bmp_logo.h這個主角,它就是denx.bmp的位元圖陣列。
所以在uboot主目錄中的makefile中有這麼一行:@rm -f $(obj)include/bmp_logo.h。在clean 時會將其刪除。
以上資訊若使用Source Insight根本無法搜尋得到,終就還是得到linux使用gerp。像這次我的搜尋指令如下:grep -Hwn "bmp_logo.h" -r ./ 接著我們來了解一下bmp_logo.c:這是一支單一檔案所完成的獨立執行檔;我先把代碼列出;再來加入註解;另外也必須了解點陣圖的格式,請參考點陣圖(Bitmap)檔案格式
#include (stdio.h)
#include (stdlib.h)
#if defined(__linux__)
#include (stdint.h)
#else
#ifdef __CYGWIN__
#include "elf.h"
#else
#include (inttypes.h)
#endif
#endif

typedef struct bitmap_s { /* bitmap description */
uint16_t width;
uint16_t height;
uint8_t palette[256*3]; //調色盤資料
uint8_t *data; //圖的bit_map資料指標
} bitmap_t;

#define DEFAULT_CMAP_SIZE 16 /* size of default color map */

/* Neutralize little endians. */
uint16_t le_short(uint16_t x) //因為檔案格式是依照LITTLE ENDIAN排列,因此要找出實際數值必須作移位。
{
uint16_t val;
uint8_t *p = (uint8_t *)(&x);
val = (*p++ & 0xff) 左移 0;
val = (*p & 0xff) 左移 8;
return val;
}
void skip_bytes (FILE *fp, int n)
{
while (n-- > 0) fgetc (fp);
}

int main (int argc, char *argv[])
{
int i, x;
FILE *fp;
bitmap_t bmp;
bitmap_t *b = &bmp;
uint16_t data_offset, n_colors;

if (argc < color="#cc33cc">//判斷參數少於2便顯示fail
fprintf (stderr, "Usage: %s file\n", argv[0]);
exit (EXIT_FAILURE);
}
if ((fp = fopen (argv[1], "rb")) == NULL) {//開啟檔案,若失敗顯示fail
perror (argv[1]);
exit (EXIT_FAILURE);
}
if (fgetc (fp) != 'B' fgetc (fp) != 'M') { //判斷檔頭前2個byte是否為"BM",否則顯示錯誤
fprintf (stderr, "%s is not a bitmap file.\n", argv[1]);
exit (EXIT_FAILURE);
}
/*
* read width and height of the image, and the number of colors used;
* ignore the rest
*/
skip_bytes (fp, 8);
fread (&data_offset, sizeof (uint16_t), 1, fp);
skip_bytes (fp, 6);
fread (&b->width, sizeof (uint16_t), 1, fp);
skip_bytes (fp, 2);
fread (&b->height, sizeof (uint16_t), 1, fp);
skip_bytes (fp, 22);
fread (&n_colors, sizeof (uint16_t), 1, fp);
skip_bytes (fp, 6);
/*
* Repair endianess. 求出實際數值,包括data_offset b->width b->height n_colors
*/
data_offset = le_short(data_offset);
b->width = le_short(b->width);
b->height = le_short(b->height);
n_colors = le_short(n_colors);

/* assume we are working with an 8-bit file */
if ((n_colors == 0) or (n_colors > 256 - DEFAULT_CMAP_SIZE)) {
/* reserve DEFAULT_CMAP_SIZE color map entries for default map */
n_colors = 256 - DEFAULT_CMAP_SIZE;
}
//使用printf便可印到bmp_logo.h那是因為$(obj)./bmp_logo $(LOGO_BMP) >$@ 這個">"轉向指令
以下的代碼就不用再加註解,只要比對一下產生出來的bmp_logo.h,就知道了
printf ("/*\n"
" * Automatically generated by \"tools/bmp_logo\"\n"
" *\n"
" * DO NOT EDIT\n"
" *\n"
" */\n\n\n"
"#ifndef __BMP_LOGO_H__\n"
"#define __BMP_LOGO_H__\n\n"
"#define BMP_LOGO_WIDTH\t\t%d\n"
"#define BMP_LOGO_HEIGHT\t\t%d\n"
"#define BMP_LOGO_COLORS\t\t%d\n"
"#define BMP_LOGO_OFFSET\t\t%d\n"
"\n",
b->width, b->height, n_colors,
DEFAULT_CMAP_SIZE);

/* allocate memory */
if ((b->data = (uint8_t *)malloc(b->width * b->height)) == NULL) {
fclose (fp);
printf ("Error allocating memory for file %s.\n", argv[1]);
exit (EXIT_FAILURE);
}

/* read and print the palette information */
printf ("unsigned short bmp_logo_palette[] = {\n");

for (i=0; ipalette[(int)(i*3+2)] = fgetc(fp);
b->palette[(int)(i*3+1)] = fgetc(fp);
b->palette[(int)(i*3+0)] = fgetc(fp);
x=fgetc(fp);

printf ("%s0x0%X%X%X,%s",
((i%8) == 0) ? "\t" : " ",
(b->palette[(int)(i*3+0)] >> 4) & 0x0F,
(b->palette[(int)(i*3+1)] >> 4) & 0x0F,
(b->palette[(int)(i*3+2)] >> 4) & 0x0F,
((i%8) == 7) ? "\n" : ""
);
}

/* seek to offset indicated by file header */
fseek(fp, (long)data_offset, SEEK_SET);

/* read the bitmap; leave room for default color map */
printf ("\n");
printf ("};\n");
printf ("\n");
printf ("unsigned char bmp_logo_bitmap[] = {\n");
for (i=(b->height-1)*b->width; i>=0; i-=b->width) {
for (x = 0; x <>width; x++) {
b->data[(uint16_t) i + x] = (uint8_t) fgetc (fp) + DEFAULT_CMAP_SIZE;
}
}
fclose (fp);

for (i=0; i<(b->height*b->width); ++i) {
if ((i%8) == 0)
putchar ('\t');
printf ("0x%02X,%c", b->data[i], ((i%8) == 7) ? '\n' : ' ');
}
printf ("\n"
"};\n\n"
"#endif /* __BMP_LOGO_H__ */\n"
);

return (0);
}
--> 閱讀更多...

2010年6月29日 星期二

memtest86關於smp實作

As we know memtest86+ is to originate from memtest86.但是memtest86 3.5裡頭關於SMP的源碼,在memtest86+ 4.0及4.1獲得延續。4.0及4.1雖然從代碼仍可看出SMP足跡,但實際上已經沒有支援SMP了。因此我們若要查看SMP的完整實作,必需Download memtest86 3.5 source code。
在main.c/test_start/initialise_cpus();首先要支援SMP就必需先調用這個routine,此處的test_start相當於memtest86+版的do_test。
void initialise_cpus(void)
{
int cpu_num, j;
smp_init_bsp();
num_cpus = smp_num_cpus();
/* let the BSP initialise the APs. */
for(cpu_num = 1; cpu_num < color="#3333ff">smp_boot_ap(cpu_num);
if(!smp_mode) {
/* some error in booting the AP,
* halt the already started APs */
for (j = 1; j <=cpu_num; j++) { jmp_address[j] = (void *)ap_halt; } break; } } } smp_init_bsp要看懂這個routine要先查詢一些資訊:小木偶裡頭有關cpuid的指令說明;Intel64 and IA-32 Architectures Software Developer's Manual-3A.pdf第8章。主要是找出CPU的核心數及尋找mp_config_table表。
smp_boot_ap這個routine才是重點。
以下資料取自於http://blog.chinaunix.net/u1/41699/showart_537820.html
多核初始化和啟動過程
多核初始化協議定義了兩類處理器:bootstrap processor(BSP) 和 application processors (APs). 上電或者MP重定後,系統硬體動態選擇一個作為BSP,其餘的作為APs。BSP的IA32_APIC_BASE中的BSP flag將被置位,其他的處理器中該位將被清空.上電或者復位後,AP等待BSP的SIPI(startup signal)信號,接收到SIPI信號後,AP執行BIOS中的AP配置代碼,然後進入halt狀態。對於支援Hyper_Threading的處理器,每個邏輯處理器將被作為一個單獨的處理器對待,都會有一個唯一的APIC ID。
志強(xeon)處理器MP初始化協定演算法:
1)基於系統拓撲結構,每個邏輯處理器將分配一個8位的APIC ID.這個ID將被寫入到每個處理器的local APIC ID寄存器中.
2)BSP被確定後,BSP創建一個ACPI的table和一個MP的table並將自己的初始APIC ID加到這些表中
3)在boot-strap流程的最後,BSP設置處理器數目為1,並開始廣播SIPI. 這裏,SIPI包含BIOS AP初始化代碼的向量
4)AP收到SIPI後,第一個得到信號量的AP開始執行初始化代碼,將自己的APIC ID加到ACPI和MP的表裏面,並將處理器數目加1.完成初始化後,AP執行一個CLI指令並halt自己
5)所有的AP都初始化完後,BSP得到一個系統處理器的個數,完成boot-strap代碼,進入OS.
6)此時AP保持halt狀態,等待INITs, NMIs和SMIs.
就我所了解以上的部分應該是BIOS所完成的,參考:http://www.biosren.com/thread-1395-1-1.html

BSP初始化過程
1. 初始化memory
2. Load microcode到處理器
3. 初始化記憶體範圍寄存器(MTRRs)
4. enable cache
5. 確定BSP是否是"GenuineIntel"
6. 執行CPUID,保存CPU資訊為將來使用
7. load AP的啟動代碼到低1M的一個4K的頁裏
8. 切換到保護模式
9. 轉換4K的頁基址為一個8位的向量. 例如 0x000BD000H --> 0xBDH
10.設置APIC的SVR的bit8來enable local APIC
11.建立錯誤處理handler
12.初始化鎖信號量
13.探測系統中的AP, 方法如下:
- 設置處理器COUNT為1
- 啟動一個timer,BSP開始等待
- 此時AP開始初始化,並將COUNT加1
- timer到期,BSP檢查COUNT,如果沒有增加,就表示系統中沒有AP.
14. 等timer中斷,檢查COUNT並建立處理器數目
AP初始化
1. 獲取信號量,開始初始化
2. load microcode到處理器
3. 初始化記憶體範圍寄存器(MTRRs)
4. enable cache
5. 檢查AP是否是"GenuineIntel"
6. 保存CPUID資訊,為將來使用
7. 切換到保護模式
8. 配置AP的共存記憶體介面執行環境
9. 將處理器個數加1
10.釋放信號量
11. 執行CLI並且進入halt狀態
12.等待INIT IPI

了解以上步驟變可從代碼來查看如何實現. update中....
--> 閱讀更多...

2010年6月16日 星期三

從start_armboot routine來了解u-boot--PART2

接續上回談到env_init,列出相關代碼:注意,這裡的代碼分散在不同的檔案
typedef struct environment_s {
uint32_t crc; /* CRC32 over data bytes */
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
unsigned char flags; /* active/obsolete flags */
#endif
unsigned char data[ENV_SIZE]; /* Environment data */
} env_t;


#define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
# define ENV_HEADER_SIZE (sizeof(uint32_t) + 1)
#else
# define ENV_HEADER_SIZE (sizeof(uint32_t))
#endif


#ifdef ENV_IS_EMBEDDED //這個marco未定義
extern uchar environment[];
env_t *env_ptr = (env_t *)(&environment[0]); //因此以上兩行未執行
#else /* ! ENV_IS_EMBEDDED */
env_t *env_ptr = 0;
#endif /* ENV_IS_EMBEDDED */


#define local static
#define ZEXPORT


#ifdef DYNAMIC_CRC_TABLE //在u-boot的code裡並未定義這個MARCO;所以並不會動態產生CRC_TABLE
local int crc_table_empty = 1;
local uint32_t crc_table[256];
local void make_crc_table OF((void));


local void make_crc_table()
{
uint32_t c;
int n, k;
uLong poly; /* polynomial exclusive-or pattern */
/* terms of polynomial defining this crc (except x^32): */
static const Byte p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26};

/* make exclusive-or pattern from polynomial (0xedb88320L) */
poly = 0L;
for (n = 0; n < n =" 0;" c =" (uLong)n;" k =" 0;" c =" c">> 1) : c >> 1;
crc_table[n] = c;
}
crc_table_empty = 0;
}
#else
/* ========================================================================
* Table of CRC-32's of all single-byte values (made by make_crc_table)
*/
local const uint32_t crc_table[256] = { //這個crc_table會被配置
0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
0x2d02ef8dL
};
#endif /*DYNAMIC_CRC_TABLE */


#ifndef OF /* function prototypes */
# ifdef STDC
# define OF(args) args
# else
# define OF(args) ()
# endif
#endif


#define DO1(buf) crc = crc_table[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8);
#define DO2(buf) DO1(buf); DO1(buf);
#define DO4(buf) DO2(buf); DO2(buf);
#define DO8(buf) DO4(buf); DO4(buf);

//以下這個routine在lib_generic/crc32.c

uint32_t ZEXPORT crc32 (uint32_t crc, const Bytef *buf, uInt len)
{
#ifdef DYNAMIC_CRC_TABLE
if (crc_table_empty) make_crc_table(); //這行不會被執行
#endif
crc = crc ^ 0xffffffffL;
while (len >= 8)
{
DO8(buf);
len -= 8;
}
if (len) do {
DO1(buf);
} while (--len);
return crc ^ 0xffffffffL;
}


先說明關於CRC,以CRC-16為例來說明其生成過程:
CRC-16碼由兩個位元組構成,在開始時CRC寄存器的每一位都預置為1,然後把CRC寄存器與8-bit的資料進行異或(異或:二進位運算相同為0,不同為1;0^0=0;0^1=1;1^0=1;1^1=0),之後對CRC寄存器從高到低進行移位,在最高位(MSB)的位置補零,而最低位(LSB,移位後已經被移出CRC寄存器)如果為1,則把寄存器與預定義的多項式碼進行異或,否則如果LSB為零,則無需進行異或。重複上述的由高至低的移位8次,第一個8-bit資料處理完畢,用此時CRC寄存器的值與下一個8-bit資料異或並進行如前一個資料似的8次移位元。所有的字元處理完成後CRC寄存器內的值即為最終的CRC值。
下面為CRC的計算過程:
来源:(http://blog.sina.com.cn/s/blog_639260ff0100g1ms.html) - 循环冗余校验CRC_Striker_新 1.設置CRC寄存器,並給其賦值FFFF(hex)。
2.將資料的第一個8-bit字元與16位元CRC寄存器的低8位進行異或,並把結果存入CRC寄存器。
3.CRC寄存器向右移一位,MSB補零,移出並檢查LSB。
4.如果LSB為0,重複第三步;若LSB為1,CRC寄存器與多項式碼相異或。
5.重複第3與第4步直到8次移位元全部完成。此時一個8-bit資料處理完畢。
6.重複第2至第5步直到所有資料全部處理完成。
7.最終CRC寄存器的內容即為CRC值。
常用的CRC迴圈冗餘校驗標準多項式如下:
CRC(16位) = X16+X15+X2+1
CRC(CCITT) = X16+X12 +X5+1
CRC(32位) = X32+X26+X23+X16+X12+X11+X10+ X8+X7+X5+X4+X2+X+1

--> 閱讀更多...

2010年6月14日 星期一

從start_armboot routine來了解u-boot--PART1

有關這方面ㄉ相關資訊參考:http://blog.chinaunix.net/u2/70445/showart_1852111.html
http://6xudonghai.blog.163.com/blog/static/33640629200911364735696/
http://www.63da.com/?All1-uboot%D4%B4%C2%EB%B7%D6%CE%F6/
(1):typedef int (init_fnc_t) (void);
(2):init_fnc_t *init_sequence[] ={
cpu_init, /* basic cpu dependent setup */ (1)
board_init, /* basic board dependent setup */(2)
interrupt_init, /* set up exceptions */ (3)
env_init, /* initialize environment */ (4)
init_baudrate, /* initialze baudrate settings */(5)
serial_init, /* serial communications setup */ (6)
console_init_f, /* stage 1 init of console */ (7)
display_banner, /* say that we are here */ (8)
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */(9)
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */ (10)
#endif
#if defined(CONFIG_HARD_I2C) defined(CONFIG_SOFT_I2C)
init_func_i2c, (11)
#endif
dram_init, /* configure available RAM banks */ (12)
display_dram_config, (13)
NULL,
};

(3):init_fnc_t **init_fnc_ptr;

(4)for (init_fnc_ptr = init_sequence ; *init_fnc_ptr ; ++init_fnc_ptr){
if ((*init_fnc_ptr)() != 0)
{hang (); }
}
以上四段code分散在board.c中,但是它們息息相關,而最下面的for迴圈更是高竿的程式寫法:以下我們就先針對這四段code了解其如何對硬體層面作完整的初始化。
我想大家對c語言有了解的應該都知道int main(int argc,char**argv){ };其實也可以這樣寫int main(int argc,char*argv[]){ };當然以下這樣也行int main(int argc,char argv[][]){ }; 所以上面的code不言自明,說穿了也是函式指標的應用。
剛才講到for迴圈,它主要的工作是硬體週邊初始化;和硬體週邊相關的代碼必需查datasheet,才能了解其實作,前三個:cpu_init、board_init、interrupt_init我認為較簡單,而且代碼上都有註解。其他部份我想未來有談到再補充。
在此我認為較難懂的是env_init,因為實際上在u-boot中這個routine就有9個之多,所以我在上面貼了一個參考連結,專門講述這個routine。雖然裡頭並未說明---實際呼叫的是env_nand.c或是env_dataflash.c或是env_flash.c...中的env_init();實際查看在common/makefile如下:
# environment
COBJS-y += env_common.o
COBJS-$(CONFIG_ENV_IS_IN_DATAFLASH) += env_dataflash.o
COBJS-$(CONFIG_ENV_IS_IN_EEPROM) += env_eeprom.o
COBJS-y += env_embedded.o
COBJS-$(CONFIG_ENV_IS_IN_FLASH) += env_flash.o
COBJS-$(CONFIG_ENV_IS_IN_NAND) += env_nand.o
COBJS-$(CONFIG_ENV_IS_IN_NVRAM) += env_nvram.o
COBJS-$(CONFIG_ENV_IS_IN_ONENAND) += env_onenand.o
COBJS-$(CONFIG_ENV_IS_IN_SPI_FLASH) += env_sf.o
COBJS-$(CONFIG_ENV_IS_NOWHERE) += env_nowhere.o
然後再查看include\configs\smdk2410.h如下:
#define CONFIG_ENV_IS_IN_FLASH 1
#define CONFIG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */
●所以在此會被編譯的只有env_flash.c
然而在tekkaman所改版的部分它將smdk2410.h重新命名為mini2440.h,並修改其中內容如下:
//#define CONFIG_ENV_IS_IN_EEPROM 1/* use EEPROM for environment vars */
//#define CONFIG_ENV_OFFSET 0x000 /* environment starts at offset 0 */
//#define CONFIG_ENV_SIZE 0x400 /* 1KB */
#define CONFIG_ENV_IS_IN_NAND 1
//#define CONFIG_ENV_IS_IN_FLASH 1 //注意此行被mark掉了
#define CONFIG_ENV_OFFSET 0X60000
#define CONFIG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */
所以在此會被編譯的是env_nand.c

另外題外話:最近使用arm-linux-objdump -D u-boot >u-boot.asm這個指令將u-boot反組譯後有助於trace參考之方便性。
--> 閱讀更多...

2010年6月10日 星期四

S3C2440與NAND FLASHK9F1208的接線分析

圖一
圖二
圖三
圖四
圖五
本文來自: 高校自動化網(http://www.zdh1909.com/) 詳細出處參考(轉載請保留本鏈結):http://www.zdh1909.com/html/MCS51/8883.html
NAND FLASH的接線方式和NOR FLASH,SDRAM都不一樣。以TQ2440開發板用的K9F1208為例,分析NAND FLASH的接線方式。K9F1208結構如圖一所示:
K9F1208位寬是8bit。
一頁: 512byte + 16byte 最後16byte是用於存儲校驗碼和其他資訊用的,不能存放實際的資料。
一個塊有32 page:(16k+512)byte
K9F1208有4096個塊:(64M+2M)byte,總共有64Mbyte可操作的晶片容量
NAND FLASH以頁為單位讀寫資料,以塊為單位擦除數據
S3C24440和K9F1208的接線如圖二:
圖三是S3C2440的NAND FLASH引腳配置:
當選定一個NAND FLASH的型號後,要根據選定的NAND FLASH來確定S3C2440的NCON,GPG13,GPG14,GPG15的狀態。
圖四是S3C2440中4個腳位元狀態的定義:
K9F1208的一頁是512byte所以NCON接低電平,GPG13接高電平
K9F1208需要4個定址命令,所以GPG14接高電平K9F1208的位寬是8,所以GPG15接低電平。
NAND FLASH定址對K9F1208來說,地址和命令只能在I/O[7:0]上傳遞,資料寬度是8位元。
地址傳遞分為4步,如圖五
第1步發送列位址,既選中一頁512BYTE中的一個位元組。512byte需要9bit來選擇,這裏只用了A0-A7,原因是把一頁分成了2部分,每部分256位元組。通過發送的讀命令字來確定是讀的前256位元組還是後256位元組。
當要讀取的起始地址(Column Address)在0~255內時我們用00h命令,當讀取的起始位址是在256~511時,則使用01h命令。
一個塊有32頁,用A9-A13共5位來選擇一個塊中的某個頁。
總共有4096個塊,用A14-A25共12位來選擇一個塊。
K9F1208總共有64Mbyte,需要A0-A25共26個位址位。
例如要讀NAND FLASH的第5000位元組開始的內容。把5000分解成列地址和行地址。
Column_address = 5000%512 = 392
Page_address = (5000>>9) = 9
因為column_address>255,所以用01h命令讀
發送命令和參數的順序是:
NFCMMD = 0x01;從後256位元組開始讀
NFADDR = column_address & 0xff;取column_address的低8位元送到數據線
NFADDR = page_address & 0xff;發送A9-A16
NFADDR = (page_address >>8) & 0xff; 發送A17-A24
NFADDR = (page_address >> 16) & 0xff;發送A25
上面的NFCMMD,NFADDR.是S3C2440的NAND FLASH控制寄存器。讀取的數據會放在NFDATA中。
--> 閱讀更多...

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
--> 閱讀更多...