2008年11月25日 星期二

Linux啓動分析(2)— bootsect.S、setup.S、head.S分析[轉貼]

bootsect.S,系統引導程序,一般不超過512字節。
在PC系統結構中,綫性地址0xA0000以上,即640K以上用于圖形接口卡和BIOS自身,640K以下為系統的基本内存。如果配置更多的内存,則0x100000,即1MB處開始稱為高内存。當BIOS引導一個系統時,總是把引導扇區讀入到基本内存地址為0x7c00的地方,然後跳轉到此執行引導扇區的代碼。這段代碼將自身搬運到0x90000處,並跳轉到那繼續執行,然後通過BIOS提供的讀磁盤調用“int 0x13”從磁盤上讀入setup和内核映像。其中setup的映像讀入到0x90200處,然後跳轉到setup的代碼中。

從0x90000到0xA0000一共64K,bootsect僅占512字節,所以setup大小理論上可到63.5KB。
在Linux2.4版本以前,在最前面的512字節裏保護了一個mini “boot loader”,只要拷貝啓動代碼運行就可從軟碟啓動;但在2.6版本中不再保護這様的”boot loader”,所以必須在第一個磁盤分區上存儲一個合適的boot loader才能從軟碟啓動,軟碟、硬碟和光驅啓動都是一様的過程。
setup進行映像的解壓縮,從BIOS收集一些數據,在控制台顯示一些信息。

基本内存中開頭一部分空間是保留給BIOS自己用的,另一方面對于Linux内核的引導也需要保留一些運行空間,一共保存了64K。基本内存中用于内核映像的就是8*64K=512K,其中頂端留4K用于引導命令行及從BIOS獲取需要傳遞給内核的數據。内核映像一般都經過壓縮,壓縮後的映像和引導扇區及輔助引導程序的映像拼接在一起,成為内核的引導映像。大小不超過508K的映像稱為小映像zImage,早期版本放在0x10000位置處,否則稱為大内核bzImage,放在0x100000位置處。

CPU在bootsect時處于16位實地址模式,然後在setup的執行過程中轉入32位保護模式。
Setup從BIOS中讀取系統數據(内存大小、顯卡模式、磁盤等參數),將數據保存在0x90000-0x901FF,覆蓋了bootsect的内容。設置32位運行方式:加載中斷描述表寄存器IDTR、全局描述表寄存器GDTR;臨時設置IDT表和GDT表,並在GDT表中設置内核代碼段和數據段的描述符,在Head.S中會根據内核的需要重新設置這些描述符表;開啓A20地址綫;重新設置兩個中斷控制器8259A,將硬件中斷號重新設置為0x20和0x2f;最後設置CPU的控制寄存器CR0(機器狀態字)的保護模式比特(PE)位,從而進入32位保護模式運行;然後跳轉到head.S中的startup_32執行。

對于小内核映像放在0x10000處,Setup會把system從0x10000移到0x0000開始處。對于大内核映像,vmlinux中普通内核代碼被編譯成以PAGE_OFFSET+1MB為起始地址,在Head.S中初始化代碼把虚擬地址减去PAGE_OFFSET就能得到以1MB為起始位置的物理地址,這也正是内核映像在物理内存中的存放位置。
Head.S中的startup_32主要用于開啓頁面單元。初始化工作在編譯過程中開始進行,它先定義一個稱為swapper_pg_dir的數組,使用鏈接器指示在地址0x00101000。然後分彆為兩個頁面pg0和pg1創建頁表項。第一組指向pg0和pg1的指針放在能覆蓋1~9MB内存的位置,第二組指針放在PAGE_OFFSET+1MB的位置。一旦開始頁機制,在上述頁表和頁表項指針建立後可以保證,在内核映像中不論是采用物理地址還是虚擬地址,都可以進行正確的頁面映射。内核其他部分的頁表初始化在paging_init()中完成。映射建立後,通過設置cr0寄存器中的某位開啓頁面映射,然後通過一個跳轉指令保證指令指針的正確性。

(1).Bootsect啓動過程:
假設用LILO啓動,啓動時用户可以選擇啓動哪個操作系統。LILO將boot loader分為兩部分,一部分放到啓動分區的第一個扇區;
  1. BIOS將MBR或啓動分區的第一個扇區的啓動部分加載到地址0x00007c00處;

  2. 該程序將自身移到0x00096a00,建立實模式棧(從0x00098000到0x000969ff),將LILO的第二部分加載到0x00096c00處,然後跳轉到此執行;

  3. 然後第二部分程序從磁盤讀取一個可啓動的操作系統列表讓用户選擇,最後用户選擇每個OS後,boot loader可以拷貝不啓動分區或者之間拷貝内核映像到RAM中去;

  4. 加載Linux内核映像時,LILO boot loader首先調用BIOS例程顯示”Loading …”信息;

  5. 調用BIOS例程加載内核映像的初始化部分到RAM上,内核映像的前512字節放在0x00090000位置,setup()函數代碼放在0x00090200位置;

  6. 接着調用BIOS例程裝載内核映像的其餘部分,映像可能放在低地址0x00010000(使用make zImage編譯的小内核映像)或者高地址0x00100000(使用make bzImage編譯的大内核映像)。

  7. 然後跳至剛剛setup部分。


(2).Setup.S分析
setup()匯編函數被連接器放在内核映像文件中的0x200偏移處。Setup函數必須初始化計算機中的硬件設備並為内核程序的執行建立環境。
  1. 在ACPI兼容的系統中,調用BIOS例程建立描述系統物理内存布局的表。在早期系統中,它調用BIOS例程返回系統可以的RAM大小;

  2. 設置鍵盤的重復延遲和速率;

  3. 初始化顯卡;

  4. 檢測IBM MCA總綫、PS/2鼠標設備、APM BIOS支持等;

  5. 如果BIOS支持Enhanced Disk Drive Services (EDD),將調用正確的BIOS例程建立描述系統可用硬碟的表;

  6. 如果内核加載在低RAM地址0x00010000,則把它移動到0x00001000處;如果映像加載在高内存1M位置,則不動;

  7. 啓動位於8042鍵盤控制器的A20 pin。

  8. 建立一個中斷描述表IDT和全局描述表GDT表;

  9. 如果有的話,重啓FPU單元;

  10. 對可編程中斷控制器進行重新編程,屏蔽所以中斷,級連PIC的IRQ2不需要;

  11. 設置CR0狀態寄存器的PE位使CPU從實模式切换到保護模式,PG位清0,禁止分頁功能;

  12. 跳轉到startup_32()匯編函數, jmpi 0x100000, __BOOT_CS,終于進入内核Head.S;


(3).Head.S分析
有兩個不同的startup_32()函數,一個在arch/i386/boot/compressed/head.S文件中,setup結束後,該函數被放在0x00001000或者0x00100000位置,該函數主要操作:
  1. 首先初始化段寄存器和臨時堆棧;

  2. 清除eflags寄存器的所有位;

  3. 將_edata和_end區間的所有内核未初始化區填充0;

  4. 調用decompress_kernel( )函數解壓内核映像。首先顯示"Uncompressing Linux..."信息,解壓完成後顯示 "OK, booting the kernel."。内核解壓後,如果時低地址載入,則放在0x00100000位置;否則解壓後的映像先放在壓縮映像後的臨時緩存裏,最後解壓後的映像被放置到物理位置0x00100000處;

  5. 跳轉到0x00100000物理内存處執行;


解壓後的映像開始于arch/i386/kernel/head.S 文件中的startup_32()函數,因為通過物理地址的跳轉執行該函數的,所以相同的函數名並没有什麽問題。該函數未Linux第一個進程建立執行環境,操作如下:
  1. 初始化ds,es,fs,gs段寄存器的最終值;

  2. 用0填充内核bss段;

  3. 初始化swapper_pg_dir數組和pg0包含的臨時内核頁表:

    • 將swapper_pg_dir(0x1000)和pg0(0x2000)清空,swapper_pg_dir作為整個系統的頁目録;

    • 將pg0作為第一個頁表,將其地址賦到swapper_pg_dir的第一個32位字中。

    • 同時將該頁表項也賦給swapper_pg_dir的第3072個入口,表示虚擬地址0xc0000000也指向pg0。

    • 將pg0這個頁表填滿指向内存前4M。

    • 在cr3寄存器中存放PGD的地址,並設置cr0寄存器中的PG位,啓用分頁支持。

  4. 建立進程0idle進程的内核模式的堆棧;

  5. 再次清除eflags寄存器的所有位;

  6. 調用setup_idt()用非空的中斷處理函數填充IDT表;

  7. 將從BIOS獲取的系統參數傳遞到操作系統的第一個頁面幀;

  8. 識別處理器的模式;

  9. 將GDT和IDT表的地址加載到gdtr和idtr寄存器中;

  10. 跳轉到start_kernel函數,這個函數是第一個C編制的函數,内核又有了一個新的開始。


(4).start_kernel()分析:
  1. 調度器初始化,調用sched_init();

  2. 調用build_all_zonelists函數初始化内存區;

  3. 調用page_alloc_init()和mem_init()初始化夥伴系統分配器;

  4. 調用trap_init()和init_IRQ()對中斷控制表IDT進行最後的初始化;

  5. 調用softirq_init() 初始化TASKLET_SOFTIRQ和HI_SOFTIRQ;

  6. Time_init()對系統日期和時間進行初始化;

  7. 調用kmem_cache_init()初始化slab分配器;

  8. 調用calibrate_delay()計算CPU時鐘頻率;

通過調用kernel_thread()啓動進程1init進程的内核綫程,然後該綫程再創建其他的内核綫程執行/sbin/init程序。

資料來源:
http://blog.csdn.net/cxylaf/archive/2007/05/26/1626513.aspx

Linux啓動分析(1)— 總體過程[轉貼]

系統引導過程總體介紹
啓動流程:
系統引導過程主要由以下幾個步驟組成(以硬碟啓動為例)
  1. 開機;

  2. BIOS加電自檢(POST——Power On Self Test),包括檢查RAM,keyboard,顯示器,軟硬磁盤等等。Intel系列的CPU首先進入的是實模式,並開始執行位於地址0xFFFF0處的代碼,也就是ROM-BIOS起始位置的代碼;

  3. 搜索啓動的操作系統,根據BIOS設置,可能會依次訪問每個軟碟的第一個扇區、硬碟、CD-ROW等;一旦找到有效的啓動設備,將第一個扇區(0頭0道1扇區,也就是Boot Sector)的内容讀入内存地址0x7c00處;

  4. 檢查(WORD)0000:7dfe是否等于0xaa55.若不等于則轉去嘗試其他介質;如果没有其他啓動介質,則顯示 “No ROM BASIC” ,然後當機;

  5. 跳轉到0000:7c00處執行MBR中的程序bootsect.S;

  6. MBR先將自己復制到0x90000處,然後將緊接其後的setup部分(第二扇區)拷貝到0x90200,將真正的内核代碼拷貝到0x100000。以上這些拷貝動作都是以bootsect.S、setup.S以及vmlinux在磁盤上連續存放為前提的;

  7. bootsect.S完成加載動作後,就直接跳轉到0x90200,這裏正是setup.S的程序入口。 setup.S的主要功能就是將系統參數(包括内存、磁盤等,由BIOS返回)拷貝到 0x90000-0x901FF内存中,這個地方正是bootsect.S存放的地方,這時它將被系統參數覆蓋。以後這些參數將由保護模式下的代碼來讀取。

  8. setup.S還將video.S中的代碼包含進來,檢測和設置顯示器和顯示模式。最後,setup.S將系統轉换到保護模式,並跳轉到0x100000(對于bzImage格式的大内核是 0x100000,對于zImage格式的是0x1000)的内核引導代碼,Bootloader過程結束;

  9. Bootloader跳轉到0x100000, 此處為"arch/I386/init/head.S"中的startup_32, startup_32的代碼只需要設置一下全局變量,然後就跳轉到start_kernel去了;start_kernel()是"init/main.c"中的asmlinkage函數,至此,啓動過程轉入體系結構無關的通用C代碼中;

  10. start_kernel()中設置與體系結構相關的環境、頁表結構初始化、Trap/IRQ初始化、核心進程調度器初始化、時間/定時器初始化、控制台初始化、核心Cache初始化、内存初始化、内部及通用等各種Cache初始化、信號量初始化、其他部分初始化(Init()及smp_init());

  11. 啓動Init()過程,創建第一個進程;Init()中,取得 run-level 信息, 執行 /etc/rc.d/rc.sysinit 脚本, 激活核心的外挂式模塊 (/etc/modules.conf), 然後init 執行 run-level 的各個脚本, 接着執行 /etc/rc.d/rc.local脚本, 最後執行 /bin/login 程序, 登入之後開始以 Shell 控管主機;

  12. 啓動完成。


資料來源:
http://blog.csdn.net/cxylaf/archive/2007/05/26/1626511.aspx

2008年11月18日 星期二

SystemC 時間

SystemC支援的時間單位包括SC_FS、SC_PS、SC_NS、SC_US、SC_MS及SC_SEC,而用在時間值的資料型別都是未帶號(unsigned)64位元的整數。
enum sc_time_unit
{
SC_FS = 0, // femtosecond
SC_PS, // picosecond
SC_NS, // nanosecond
SC_US, // microsecond
SC_MS, // millisecond
SC_SEC // second
};

語法如下:
sc_time var_name(time_value, time_unit);

如果想要產生一個clock信號,可以使用下面的寫法:
sc_time t(20, SC_NS);
sc_clock clk("clk", t);


更詳細的範例可參考:
http://www.eecs.berkeley.edu/~hiren/docs/sc_intro.pdf

2008年11月13日 星期四

Verilog and SystemC 比較

(1)Module的寫法
Verilog
module module_name(input/output declaration)
variable declaration
computation block
endmodule

SystemC
SC_MODULE(module_name){
input/output declaration
internal variable
constructor (computation block)
};

(2)input/output定義
Verilog
Input:input var1, ...;
Output:output var2, ...;
Type

SystemC
Input:sc_in var1, ...;
Output:sc_out var2, ...;
Type
  • C++ primitive type: int, float, char, ...
  • hardware type: sc_int, sc_uint, ...
  • user defined type

(3)Computation Block
Verilog
Event trigger: always@(a or b or c)
Edge trigger: always@(posedge clk)

SystemC
SC_CTOR(module_name){
SC_METHOD(function name);
sensitive << a << b << c;
...
}

參考資料: http://twins.ee.nctu.edu.tw/courses/soc_sys_overview_04fall/lab/systemc_chapter1.ppt

全加器範例程式

這是一個SystemC的全加器模型程式
共有main.cpp,module.cpp module.h三支程式
裏面有3個物件(1)full adder[全加器](2)pattern generator[訊號產生器](3)monitor[監控程式]
main.cpp
#include <systemc.h>
#include <iostream>
#include "module.h"

int sc_main(int argc, char** argv){
        sc_signal<int> a, b, carry, co, sum;

        //module ptr
        full_adder adder("adder");
        adder.carry(carry);
        adder.a(a);
        adder.b(b);
        adder.sum(sum);
        adder.co(co);

        pattern_gen pg("pattern_gen");
        pg.d_a(a);
        pg.d_b(b);
        pg.d_carry(carry);

        monitor mon("monitor waveform");
        mon.m_a(a);
        mon.m_b(b);
        mon.m_carry(carry);
        mon.m_sum(sum);
        mon.m_cout(co);


        sc_start(50, SC_NS);

        return 0;
}

module.h
#include <systemc.h>

SC_MODULE(full_adder){
        sc_in<int> carry;
        sc_in<int> a;
        sc_in<int> b;

        sc_out<int> sum;
        sc_out<int> co;


        void proc_full_adder();

        SC_CTOR(full_adder){
                SC_METHOD(proc_full_adder);
                sensitive << carry << a << b;
        }
};

SC_MODULE(pattern_gen){
        sc_out<int> d_a;
        sc_out<int> d_b;
        sc_out<int> d_carry;

        void proc_pattern_gen();

        SC_CTOR(pattern_gen){
                SC_THREAD(proc_pattern_gen);
        }
};

SC_MODULE(monitor){
        sc_in<int> m_a, m_b, m_carry, m_sum, m_cout;

        void proc_monitor();

        SC_CTOR(monitor){
                SC_THREAD(proc_monitor);
                //sensitive << m_a << m_b << m_carry << m_sum << m_cout;
                sensitive << m_a << m_b << m_carry;
        }
};

module.cpp
#include <systemc.h>
#include <iostream>
#include "module.h"
//using namespace std;

void full_adder::proc_full_adder(){
        sum = a ^ b ^ carry;
        co = (a & b) | (b & carry) | (carry & a);
}

void pattern_gen::proc_pattern_gen(){
        sc_uint<3> pattern;
        pattern = 0;

        while (1){
                d_a = pattern[0];
                d_b = pattern[1];
                d_carry = pattern[2];

                wait(5, SC_NS);
                pattern++;
        }
}

void monitor::proc_monitor(){
        while (1){
                cout << "At time " << sc_time_stamp() << "::";
                cout << "(a, b, carry): ";
                cout << m_a << m_b << m_cout;
                cout << " (sum, carry_out): " << m_sum << m_cout << endl;
                wait();
        }
}

Makefile
LIB_DIR=-L/usr/systemc/lib-linux
CPPFLAGS=-I/usr/systemc/include

LIB=-lsystemc

APP=main
OBJS = main.o module.o

$(APP):$(OBJS)
        g++ -o $@ $^ $(LIB_DIR) $(LIB)
.c.o:
        g++ $(CPPFLAGS) -c $@ $<

clean:
        rm -rf $(APP) *.o


執行結果:
At time 0 s::(a, b, carry): 000 (sum, carry_out): 00
At time 5 ns::(a, b, carry): 100 (sum, carry_out): 00
At time 10 ns::(a, b, carry): 010 (sum, carry_out): 10
At time 15 ns::(a, b, carry): 110 (sum, carry_out): 10
At time 20 ns::(a, b, carry): 001 (sum, carry_out): 01
At time 25 ns::(a, b, carry): 100 (sum, carry_out): 10
At time 30 ns::(a, b, carry): 011 (sum, carry_out): 01
At time 35 ns::(a, b, carry): 111 (sum, carry_out): 01
At time 40 ns::(a, b, carry): 001 (sum, carry_out): 11
At time 45 ns::(a, b, carry): 100 (sum, carry_out): 00
資料來源:
http://twins.ee.nctu.edu.tw/courses/soc_sys_overview_04fall/lab.html

2008年11月8日 星期六

EFI UGA & Simple Pointer[轉貼]

年前學習了有關UGA & Simple Pointer的知識,今天拿出來,總結一下。方便自己以後的理解和查閲。
UGA
UGA是 Universal Graphics Adapter (通用圖形適配器)的縮寫。在本質上講,UGA 是一個EFI driver。它在OS導入前和OS運行時都能被使用。

1,Universal Graphics Adapter Protocols主要描述了有關在EFI環境下的圖象顯示。這個部分包含了 UGA Draw Protocols 和 UGA I/O protocol 。前者描述了如何在 pre-OS space 繪入圖象,在圖象屏幕上顯示出來;後者描述了如何訪問圖象屏幕,以及支持視頻控制器的子設備,例如:圖象顯示設備。同時,後者的目標是在 OS 當前的環境下,實現初步的使用。

2,UGA ROM 是一個軟件的概念,它的目標是來支持可預知圖形硬件,并不要求VGA硬件。(不是很清楚)

3,UGA Draw Protocol 支持三個成員函數,來支持 pre-OS space 有限的圖形需求。這些成員函數允許調用者draw到虚擬的 frame buffer 裏面,來獲得當前的視頻模式,以及設置視頻模式。這些簡單的原始函數已經能充分的滿足pre-OS firmware code的總的需要。

4,在 EFI_UGA_DRAW_PROTOCOL 基本圖形操作是 Blt(Block Transfer)。Blt操作允許數據讀出和寫入視頻適配器的視頻存儲器裏。frame buffer 視頻顯示是由一組像素。每個像素在視頻顯示上的位置由 X 和 Y 坐標描述。X 代表了一個掃描行。一個掃描行是指顯示的水平的像素大小。 software Blt buffer 也是由一組像素來組成的。在buffer 裏的第一個元素是Pixel (0, 0) 。Blt buffer 可以看成是一系列的掃描行。一個像素在視頻顯示上的位置和 Blt buffer 裏相對應的轉化:Blt buffer array index = Y * Width + X 。Blt buffer 裏描述像素是有32位,字節0到字節2分彆代表了在像素中紅,緑,藍三色各含的成分,字節3是保留位,始終為0。

5,UGA I/O Protocol 支持I/O 請求模式,目的在于給 OS high performance driver 提供服務。這些I/O請求是經由 EFI_UGA_IO_PROTOCOL DispatchService() 成員函數來訪問的。I/O請求服務包含的能力由 EFI_UGA_DRAW_PROTOCOL 來支持。

6,EFI_UGA_DRAW_PROTOCOL 的三個成員函數:GetMode()、SetMode()、Blt();
EFI_UGA_IO_PROTOCOL 的成員函數:CreateDevice()、DeleteDevice()、DispatchService();

Simple Pointer
1,指定一個簡單的方法來進入指針設備。通常説來,主要是指鼠標。EFI_SIMPLE_POINTER_PROTOCOL 允許返回指針設備的有關信息。這包含了按鍵的狀態和最新訪問時鼠標的運動狀態。

2,每個EFI_SIMPLE_POINTER_PROTOCOL都必須安裝Handle 來給EFI Drivers和EFI App提供一些服務讓它們來利用。同時,EFI_DEVICE_PATH 也必須安裝同様的Handle。

相關的概念理解:
1,EBC:
對于不同的處理器和平台,Option ROMs要求有不同的可執行的映像。EFI定義了EFI 字節碼編譯器(EBC)虚擬機來處理這些不同。這個虚擬器的注釋器是firmware的一部分。C語言能够被編譯成EBC,然後和相關的驅動連接,在注釋器上運行。
C語言編譯成 EBC ,同時創建 EBC 映像,它可以被系統在 EFI1.10、UEFI2.0,或更後的Spec下執行。這些系統包含 EBC 注釋器,EBC 注釋器加載和解釋 EBC image,允許 image 能够在多種平台和機制上執行,包括那些基于英特爾? 安騰?處理器, 32位英特爾? 架構處理器,以及64位英特爾體系架構處理器。

2,OPROM:
(Option ROM) Firmware 在適配器卡上控制可啓動的外圍設備。系統 BIOS 會詢查 option ROMs,來確定有哪些設備可以被啓動導入。
(Option ROM) Firmware on adapter cards that control bootable peripherals. The system BIOS interrogates the option ROMs to determine which devices can be booted.

Option ROM 有固件組成,由系統 BIOS 來調用。舉例來説:適配器卡控制導入設備,設備可能包含固件,一旦 Option ROM 被加載,固件用來將設備連接到系統。
An Option ROM typically consists of firmware that is called by the system BIOS. For example, an adapter card that controls a boot device might contain firmware that is used to connect the device to the system once the Option ROM is loaded.

資料來源:
http://blog.sina.com.cn/s/blog_4b265789010008fy.html
EFI Image綜述

EFI Driver Template Code

#include "Tiano.h"
#include "EfiDriverLib.h"
#include "DrvSampleSetup.h"
#include "DrvSample.h"

//
// Local function declarations
//

EFI_STATUS
DrvSampleEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
);

EFI_DRIVER_ENTRY_POINT (DrvSampleEntry)

EFI_STATUS
DrvSampleEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
/*++

Routine Description:

DrvSample driver entry point function.

Arguments:

ImageHandle - image handle for this driver image
SystemTable - pointer to the EFI System Table

--*/

{
EFI_STATUS Status;
EFI_HANDLE Handle;

Status = EFI_SUCCESS;
Handle = NULL;
EfiInitializeDriverLib (ImageHandle, SystemTable);
//
// TODO: Add implementation code
//
//
// Initialize our setup/forms data
//

Status = SetupInit (ImageHandle, SystemTable, &Handle);
if (EFI_ERROR(Status)) {
//
// TODO: Handle error
//

}
//
// Initialize and produce our driver binding protocol
//

Status = DriverBindingProtocolInit (ImageHandle, SystemTable, &Handle);
if (EFI_ERROR(Status)) {
//
// TODO: Handle error
//

}
//
// Initialize and produce our driver diagnostics protocol
//

Status = DriverDiagnosticsProtocolInit (ImageHandle, SystemTable, &Handle);
if (EFI_ERROR(Status)) {
//
// TODO: Handle error
//

}
//
// Initialize and produce our component name protocol
//

Status = ComponentNameProtocolInit (ImageHandle, SystemTable, &Handle);
if (EFI_ERROR(Status)) {
//
// TODO: Handle error
//

}
//
// Initialize and produce our driver configuration protocol
//

Status = DriverConfigurationProtocolInit (ImageHandle, SystemTable, &Handle);
if (EFI_ERROR(Status)) {
//
// TODO: Handle error
//

}
return Status;
}


資料來源:
http://blog.sina.com.cn/s/blog_4b265789010006gu.html
EFI Programming on Mac OS X

一個小故事讓我們明白資金流通的意義

“又是炎熱小鎮慵懶的一天。太陽高掛,街道無人,每個人都債台高築,靠信用度日。這時,從外地來了一位有錢的旅客,他進了一家旅館,拿出一張1000 元鈔票放在櫃檯,說想先看看房間,挑一間合適的過夜,就在此人上樓的時候---- 店主抓了這張1000 元鈔,跑到隔壁屠戶那裡支付了他欠的肉錢...