1) 史前時代:匯編在FLASH中運(yùn)行的日子(匯編指令參見《See MIPS Run》一書):
U-Boot的開始執(zhí)行始于用匯編語言編寫的CPU依賴的程序,程序是從cpu/mips/start.S文件中的_start代碼段開始執(zhí)行的。由于此時DRAM未初始化,所以程序是從存儲U-Boot程序的FLASH中開始運(yùn)行的。下面就從_start開始代碼之旅。
/***************************************************************************************/
程序一開始就出現(xiàn)了一大片令人迷惑的代碼:
_start:
RVECENT(reset,0) /* U-boot entry point */
RVECENT(reset,1) /* software reboot */
RVECENT(romReserved,3)
RVECENT(romReserved,4)
……
/* Reserve extra space so that when we use the boot bus local memory
** segment to remap the debug exception vector we don't overwrite
** anything useful */
……
而宏RVECENT的定義為:
#define RVECENT(f,n) \
b f; nop
可見該指令只是一個簡單的跳轉(zhuǎn)指令b Label。
而romReserved代碼為:
romReserved:
b romReserved
nop
……
可見是沒有意義的死循環(huán)代碼。
再結(jié)合注釋,原來程序開始的一大片令人迷惑的代碼的作用如下:
_start:
RVECENT(reset,0) /* U-boot entry point */ /*U-Boot開始執(zhí)行的代碼起始地址*/
RVECENT(reset,1) /* software reboot */ /*軟重啟時U-Boot開始執(zhí)行的起始地址*/
RVECENT(romReserved,3) /*保留本代碼所在的地址,重新映射調(diào)試異常向量時可以使用該空間*/
RVECENT(romReserved,4) /*同上……*/
……
/***************************************************************************************/
接著reset段的代碼往下看:
首先是一些COP0的狀態(tài)寄存器的設(shè)置:講COP0_STATUS_REG寄存器的5-7三個bit置1。結(jié)合CPU手冊可以看到三個bit的含義。
然后是調(diào)試模式下的GPIO初始化,然后是檢查是否使用FAILSAFE模式加載BootLoader,接著才真正開始CPU初始化。
當(dāng)看到一段注釋時:
/* Check what core we are - if core 0, branch to init tlb
** loop in flash. Otherwise, look up address of init tlb
** loop that was saved in the boot vector block.
*/
可以發(fā)現(xiàn)下面這段對每個core的TLB(Translation LookasideBuffer)進(jìn)行初始化時,是對core0與其他cores的TLB初始化有區(qū)別的:如果是core0,由于DRAM沒有初始化,代碼只能繼續(xù)在FLASH中執(zhí)行;而如果是其他cores,則可以直接調(diào)轉(zhuǎn)到DRAM中相應(yīng)的這段代碼的地址進(jìn)行TLB初始化。
接著下面的代碼可以連續(xù)看到兩個Label:
.globl InitTLBStart
InitTLBStart:
InitTLBStart_local:
第一個Label是為了將下面的代碼拷貝到DRAM后可以直接在C語言中用函數(shù)的方式調(diào)用,第二個Label是為了core0中執(zhí)行TLB初始化時跳轉(zhuǎn)。
從下面的注釋中可以證實(shí)這一點(diǎn):
/* This code run on all cores - core 0 from flash,
** the rest from DRAM. When booting from PCI, non-zero cores
** come directly here from the boot vector - no earlier code in this
** file is executed.
*/
/* Some generic initialization is done here as well, as we need this done on
** all cores even when booting from PCI
*/
對TLB初始化的代碼中使用了很多mfc0與mtc0指令,可見是對一些COP0的寄存器的讀寫。
接著往下又是一些COP0的狀態(tài)寄存器的設(shè)置,設(shè)置scratch memory等。
/***************************************************************************************/
再往下可以看到一段注釋:
/* Check if we are core 0, if we are not then we need
** to vector to code in DRAM to do application setup, and
** skip the rest of the bootloader. Only core 0 runs the bootloader
** and sets up the tables that the other cores will use for configuration
*/
可見以下的代碼執(zhí)行在不同的core上開始出現(xiàn)不同:core0繼續(xù)往下執(zhí)行匯編代碼;而如果是其余cores,則從內(nèi)存中找到BOOT_VECTOR_BASE地址,直接跳入內(nèi)存執(zhí)行應(yīng)用程序的初始化。
假設(shè)當(dāng)前仍是core0,繼續(xù)往下看??吹阶⑨專?br>
/* If we don't have working memory yet configure a bunch of
** scratch memory, and set the stack pointer to the top
** of it. This allows us to go to C code without having
** memory set up
*/
可見如果內(nèi)存還沒有初始化,這里首先初始化一塊臨時內(nèi)存作為??臻g,這使得程序可以在內(nèi)存初始化之前用來調(diào)用C程序。
/***************************************************************************************/
再往下是:
/* Initialize GOT pointer.
** Global symbols can't be resolved before this is done, and as such we can't
** use any global symbols in this code. We use the bal/ move xxx,ra combination to access
** data in a PC relative manner to avoid this. This code will correctly set the
** gp regardless of whether the code has already been relocated or not.
** This code determines the current gp by computing the link time (gp - pc)
** and adding this to the current pc.
** runtime_gp = runtime_pc + (linktime_gp - linktime_pc)
** U-boot is running from the address it is linked at at this time, so this
** general case code is not strictly necessary here.
*/
其中,GOT=Global Offset Table,GP=GOT pointer,PC=Program counter??梢姵绦蜷_始為調(diào)用其他匯編文件中定義的函數(shù)或C程序中定義的函數(shù)進(jìn)行準(zhǔn)備而建立符號表指針(GOT pointer)。
/***************************************************************************************/
初始化完GOT pointer后,接著往下就可以調(diào)用其他匯編文件中定義的函數(shù)(或代碼段),可以看到初始化內(nèi)存、緩存的代碼:
/* Initialize any external memory. */
jal memsetup /*memsetup是定義在board/tb0229/文件夾下的memsetup.S中的代碼段*/
nop
/* Initialize caches... */
sync
cache 0, 0($0)
sync
cache 9, 0($0)
sync
jal mips_cache_reset /*mips_cache_reset也是定義在其他文件中的代碼段*/
nop
/* ... and enable them. */
li t0, CONF_CM_CACHABLE_NONCOHERENT
mtc0 t0, CP0_CONFIG
/* Set up temporary stack. */
li a0, CFG_INIT_SP_OFFSET
jal mips_cache_lock /*mips_cache_lock同樣是定義在其他文件中的代碼段*/
nop
這段代碼主要是調(diào)用依賴某個板子的對memory進(jìn)行參數(shù)設(shè)置、對cache進(jìn)行初始化的代碼,借以完成對某個板子的內(nèi)存、緩存初始化。
/***************************************************************************************/
接著再往下可以看到代碼:
la t9, board_init_f /* doesn't return... */ /*board_init_f是定義在lib_mips/board.c中的C函數(shù)*/
j t9
nop
這里開始轉(zhuǎn)到board_init_f代碼段開始執(zhí)行程序,board_init_f實(shí)質(zhì)上是C語言中定義的函數(shù),雖然后面的代碼仍在flash中存放,但是已經(jīng)可以使用一部分scratch memory作為臨時棧空間進(jìn)行函數(shù)調(diào)用,可以用C語言進(jìn)行批量初始化了,純匯編的時代暫時告一段落。
2) 石器時代:FLASH中的C代碼在臨時??臻g中活躍:
這部分的代碼的使命是致力于建立一個“正常”的C運(yùn)行環(huán)境,主要是內(nèi)存的初始化及整個尋址空間的部分初始化。而這部分代碼本身所運(yùn)行的環(huán)境受到較多限制,只有一個大小受限的scratch memory作為臨時運(yùn)行的??臻g。
/***************************************************************************************/
board_init_f()函數(shù)一開始出現(xiàn)一個宏,DECLARE_GLOBAL_DATA_PTR,查看該宏的定義(include/asm-mips/Global_data.h):
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("k0")
結(jié)合注釋可以了解到,這些關(guān)于系統(tǒng)信息的結(jié)構(gòu)體(GD是指Global Data, BD是指Board infoData)應(yīng)該存放于在DRAM控制器未初始化之前就能使用的空間中,比如鎖定的緩存中。在這里我們可以暫時把它放在已經(jīng)初始化好的臨時??臻gscratch memory中。
GD和BD是很重要的結(jié)構(gòu)體,后面當(dāng)DRAM初始化完成后,會將其拷貝入DRAM空間保存。
/***************************************************************************************/
接著往下是循環(huán)調(diào)用init_sequence函數(shù)指針數(shù)組中的成員,來依次調(diào)用數(shù)組列表中的函數(shù)進(jìn)行初始化。
init_sequence的定義如下(將部分預(yù)編譯指令去掉后的代碼):
init_fnc_t * init_sequence[] = {
octeon_boot_bus_init,
timer_init,
env_init, /* initialize environment */
early_board_init,
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f,
display_banner, /* say that we are here */
init_dram,
dram_test,
init_func_ram,
NULL,
};
/***************************************************************************************/
從調(diào)用完init_sequence中的函數(shù)后往下看:
/*
* Now that we have DRAM mapped and working, we can
* relocate the code and continue running from DRAM.
*/
#if defined(CONFIG_NO_RELOCATION) && defined(CONFIG_RAM_RESIDENT)
/* If loaded into ram, we can skip relocation , and run from the spot we were loaded into */
addr = CFG_MONITOR_BASE;
u_boot_mem_top = CFG_SDRAM_BASE + MIN(gd->ram_size, (1*1024*1024));
#else
/* Locate at top of first Megabyte */
addr = CFG_SDRAM_BASE + MIN(gd->ram_size, (1*1024*1024));
u_boot_mem_top = addr;
其中CFG_SDRAM_BASE=0x8000 0000,是MIPS虛擬尋址空間中kseg0段的起始地址(參考《See MIPS Run》),它經(jīng)過CPU TLB翻譯后是DRAM內(nèi)存的起始物理地址。
這里顯然是將u_boot_mem_top和addr指向了DRAM中1M地址處(從DRAM起始地址到1M地址處的1M空間是為U-boot自己運(yùn)行分配的), 即從0x8000 0000到0x8010 0000的1M空間是U-boot自己活躍的天下了。
/***************************************************************************************/
現(xiàn)在U-boot有了對自己來說很富裕的1M字節(jié)可以自由分配的DRAM空間了,下面很大一段代碼都是對這1M內(nèi)存的劃分和分配。
這部分代碼精簡后,加入詳細(xì)注釋如下:
/* We can reserve some RAM "on top" here. */ //從0x8010 0000處開始向下劃分勢力范圍
/* round down to next 4 kB limit. */
addr &= ~(4096 - 1); //addr &= ~0x0FFF這種計(jì)算是常用的地址對齊的算法,這里是向下4K字節(jié)對齊
/* Reserve memory for U-Boot code, data & bss*/
addr -= MAX(len, (512*1024)); //為code, data, bss段保留512K的空間
/* round down to next 64k (allow us to create image at same addr for debugging)*/
addr &= ~(64 * 1024 - 1); //向下64K字節(jié)對齊
/* Reserve memory for malloc() arena. */
addr_sp = addr - TOTAL_MALLOC_LEN; //劃分malloc()使用的空間,即所謂的堆空間,大小有宏來確定
/* (permanently) allocate a Board Info struct and a permanent copy of the "global" data*/
addr_sp -= sizeof(bd_t); //分配BD結(jié)構(gòu)體大小的空間
bd = (bd_t *)addr_sp;
gd->bd = bd; //GD中的指針關(guān)聯(lián)到此處的BD結(jié)構(gòu)體地址
addr_sp -= sizeof(gd_t); //分配GD結(jié)構(gòu)體大小的空間
id = (gd_t *)addr_sp; //id指針指向GD結(jié)構(gòu)體地址
/* Reserve memory for boot params. */
addr_sp -= CFG_BOOTPARAMS_LEN; //分配boot param的空間,這里的宏大小是128K字節(jié)
bd->bi_boot_params = addr_sp; //在BD中記錄此boot param空間的地址
/* Finally, we set up a new (bigger) stack. Leave some safety gap for SP, force alignment on 16 byte boundary */
addr_sp -= 16; //向下一幀,保證??臻g的開始沒有和之前分配的空間沖突
addr_sp &= ~0xF; //棧空間16字節(jié)對齊
#define STACK_SIZE (16*1024UL)
bd->bi_uboot_ram_addr = (addr_sp - STACK_SIZE) & ~(STACK_SIZE - 1); //將棧地址16K對齊后記錄入BD
bd->bi_uboot_ram_used_size = u_boot_mem_top - bd->bi_uboot_ram_addr; //在BD中記錄使用的DRAM大小
/* Save local variables to board info struct */
bd->bi_memstart = CFG_SDRAM_BASE; /* start of DRAM memory */ //0x80000000
bd->bi_memsize = gd->ram_size; /* size of DRAM memory in bytes */
bd->bi_baudrate = gd->baudrate; /* Console Baudrate */
memcpy (id, (void *)gd, sizeof (gd_t)); //將在臨時??臻gscratch memory中的GD數(shù)據(jù)拷貝入DRAM中,至此,BD和GD都已經(jīng)存在于DRAM中了。
根據(jù)這部分代碼可以畫出U-boot這1M空間的示意圖:
/***************************************************************************************/
1M空間瓜分完畢后,出現(xiàn)一條語句:
relocate_code (addr_sp, id, addr);
該語句使程序回到cpu/mips/start.S的匯編中,在之后的匯編中,U-boot將自己的代碼段、數(shù)據(jù)段、BSS段等搬到在DRAM中新家,為以后跨入速度時代而過渡。
3) 青銅時代:短暫的回歸cpu/mips/start.S:
/***************************************************************************************/
重新回到匯編的天下,找到代碼:
.globl relocate_code
.ent relocate_code
relocate_code:
下面的代碼就是搬家了。
直到出現(xiàn)代碼:
move a0, a1
la t9, board_init_r /* doesn't return, runs main_loop() */
j t9
程序搬家基本完成,后面的程序就可以全部在內(nèi)存DRAM中執(zhí)行了,速度會比之前在FLASH和scratchmemory中運(yùn)行的速度快上很多。這里跳入的代碼段board_init_r是在C程序中定義的函數(shù),仍然在剛才的那個C語言文件lib_mips/board.c中。
4) 白銀時代:終于有正常的C環(huán)境接著進(jìn)行初始化了:
/***************************************************************************************/
進(jìn)入board_init_r函數(shù)之前,有一段讓人振奮的注釋:
/* This is the next part if the initialization sequence: we are now
* running from RAM and have a "normal" C environment, i. e. global
* data can be written, BSS has been cleared, the stack size in not
* that critical any more, etc.
*/
然后注意到該函數(shù)有兩個傳入的參數(shù),參數(shù)是之前匯編中用a0寄存器傳入的,在這里可以看出這兩個參數(shù)的含義:
id: 之前在U-boot的1M空間中分配的GD結(jié)構(gòu)體的地址
dest_addr: U-boot重新定位到DRAM之后的代碼起始地址
程序接著向下是將id的值用k0寄存器保存,將GD結(jié)構(gòu)體中的一些字段進(jìn)行設(shè)置,包括記錄U-boot自身的代碼在內(nèi)存中的偏移地址等。
/***************************************************************************************/
接著是重新計(jì)算命令表(cmdtable)的地址。什么是命令表?因?yàn)閁-boot啟動完成后可以進(jìn)入命令行模式,這時候用戶可以從串口輸入命令來指示U-boot下一步做什么,每個命令對應(yīng)的名稱、用法、描述、執(zhí)行的函數(shù)等信息,用一個命令表結(jié)構(gòu)體保存,這樣每一個命令在內(nèi)存中有對應(yīng)的一個命令表。結(jié)構(gòu)體的定義在include/Command.h中,定義如下:
struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
int repeatable; /* autorepeat allowed? */
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); /* Implementation function */
char *usage; /* Usage message (short) */
char *help; /* Help message (long) */
} __attribute__ ((aligned (8)));
而這里給命令表重新計(jì)算地址其實(shí)只是將從__u_boot_cmd_start到__u_boot_cmd_end之間的每個命令表中的成員指針的地址加上U-boot在DRAM中的偏移地址,這樣獲得命令表在DRAM中的地址??磥磙D(zhuǎn)換之前的命令表中的地址應(yīng)該是相對地址(?!)。
注:這里,注意到__attribute((XXX))比較奇特的語法,其實(shí)這個是GCC對C語言的擴(kuò)充,GCC允許聲明函數(shù)、變量和類型的特殊屬性,以便手工的代碼優(yōu)化和更仔細(xì)的代碼檢查。要指定一個聲明的屬性,在聲明后寫
__attribute__ (( ATTRIBUTE ))
其中 ATTRIBUTE 是屬性說明,多個屬性以逗號分隔。GNU C 支持十幾個屬性,如noreturn, unused, aligned等。
/***************************************************************************************/
然后是初始化malloc()堆空間:
mem_malloc_init();
其實(shí)是將全局變量mem_malloc_start和mem_malloc_end和mem_malloc_brk三個指針指向之前分配好的堆空間。
然后是重定位或者初始化環(huán)境變量的指針:
env_relocate();
將env_ptr指針及其指向的地址初始化,用來存放環(huán)境變量結(jié)構(gòu)體,然后將flash中的環(huán)境變量拷貝到內(nèi)存中。
然后是其余設(shè)備的初始化devices_init(),這是在前面的堆空間(malloc)、環(huán)境變量、PCI總線初始化后的基礎(chǔ)之上才能進(jìn)行的,這里的設(shè)備包括:
i2c_init ();
drv_lcd_init ();
drv_video_init ();
drv_keyboard_init ();
drv_logbuff_init ();
drv_system_init ();
drv_usbtty_init ();
...
然后是將標(biāo)準(zhǔn)的輸入輸出std*變量由通過串口改為通過pci_console_active進(jìn)行輸入輸出。
然后是jump table的初始化,這里似乎是將一些函數(shù)指針記錄進(jìn)GD結(jié)構(gòu)體。
然后是console初始化。
然后是再次打印board/chip info,這里打印是為了自檢和板子確認(rèn)。
然后是確認(rèn)loadaddr和bootfile環(huán)境變量的有效性。
然后是miscellaneous platform dependent的初始化,函數(shù)misc_init_r ()。
然后是網(wǎng)卡的初始化,eth_initialize()。
然后是IDE的檢測和初始化,ide_init()。
然后是debugger的設(shè)置。
最后是一個空函數(shù)late_board_init(),用來添加比較晚的初始化代碼。
下面就進(jìn)入了一個死循環(huán),循環(huán)調(diào)用main_loop()函數(shù),這意味著U-boot基本啟動完畢,進(jìn)入命令行模式。
5) 鉆石時代:專題篇
下面是一些個人學(xué)習(xí)的專題。記錄備忘。
/***************************************************************************************/
MIPS CPU地址空間簡介(整理自《See MIPS Run》和CPU文檔):
注:首先需要明確的是CPU物理地址空間不僅僅包括RAM物理內(nèi)存的空間,還包括CPU內(nèi)部的一些總線、寄存器的編址。
一個MIPS CPU可以運(yùn)行在兩種優(yōu)先級別上, 用戶態(tài)和核心態(tài)。MIPSCPU從核心態(tài)到用戶態(tài)的變化并不是CPU工作不一樣,而是對于有些操作認(rèn)為是非法的。在用戶態(tài),任何一個程序地址的首位是1的話,這個地址是非法的,對其存取將會導(dǎo)致異常處理。另外,在用戶態(tài)下,一些特殊的指令將會導(dǎo)致CPU進(jìn)入異常狀態(tài)。
在32位CPU下,程序地址空間劃分為4個大區(qū)域。每個區(qū)域有一個傳統(tǒng)的名字。對于在這些區(qū)域的地址,各自有不同的屬性:
kuseg: 虛擬空間0x0000 0000 - 0x7FFF FFFF(低端2G):這些地址是用戶態(tài)可用的地址。在有MMU的機(jī)器里,這些地址將一概被MMU作轉(zhuǎn)換,除非MMU的設(shè)置被建立好,否則這2G地址是不可用的。對于沒有MMU的機(jī)器,存取這2G地址的方法依具體機(jī)器相關(guān),你的CPU具體廠商提供的手冊將會告訴你關(guān)于這方面的信息。如果想要你的代碼在有或沒有MMU的MIPS處理器之間有兼容性,盡量避免這塊區(qū)域的存取。
kseg0: 虛擬空間0x8000 0000 - 0x9FFFFFFF(512M): 這些地址映射到物理地址簡單的通過把最高位清零,然后把它們映射到物理地址低段512M(0x0000 0000 -0x1FFFFFFF)。因?yàn)檫@種映射是很簡單的,通常稱之為“非轉(zhuǎn)換的”地址區(qū)域。幾乎全部的對這段地址的存取都會通過快速緩存(cache)。因此在cache設(shè)置好之前,不能隨便使用這段地址。通常一個沒有MMU的系統(tǒng)會使用這段地址作為其絕大多數(shù)程序和數(shù)據(jù)的存放位置。對于有MMU的系統(tǒng),操作系統(tǒng)核心會存放在這個區(qū)域。
kseg1: 虛擬空間0xA000 0000 - 0xBFFF FFFF(512M):這些地址通過把最高3位清零的方法來映射到相應(yīng)的物理地址上,與kseg0映射的物理地址一樣。但kseg1是非cache存取的。kseg1是唯一的在系統(tǒng)重啟時能正常工作的地址空間。這也是為什么重新啟動時的入口向量是0xBFC0 0000。這個向量相應(yīng)的物理地址是0x1FC00000。你將使用這段地址空間去存取你的初始化ROM。大多數(shù)人在這段空間使用I/O寄存器。如果你的硬件工程師要把這段地址空間映射到非低段512M空間,你得勸說他。
kseg2: 虛擬空間0xC000 0000 - 0xFFFF FFFF (1G): 這段地址空間只能在核心態(tài)下使用并且要經(jīng)過MMU的轉(zhuǎn)換。在MMU設(shè)置好之前,不能存取這段區(qū)域。除非你在寫一個真正的操作系統(tǒng),一般來說你不需要使用這段地址空間。
綜上可以看到,MIPS32 CPU下面的不經(jīng)過MMU轉(zhuǎn)換的內(nèi)存窗口只有kseg0和kseg1的512M的大小,而且這兩個內(nèi)存窗口映射到同一512M的物理地址空間。其余的3G虛擬地址空間需要經(jīng)過MMU轉(zhuǎn)換成物理地址,這個轉(zhuǎn)換規(guī)則是由CPU廠商實(shí)現(xiàn)的。還句話說,在MIPS32 CPU下面訪問高于512M的物理地址空間,必須通過MMU地址轉(zhuǎn)換。
在核心態(tài)下(CPU啟動時),CPU可以作任何事情。在用戶態(tài)下,2G之上的地址空間是非法的,任何存取將會導(dǎo)致系統(tǒng)異常處理。注意的是,如果一個CPU有MMU,這意味著所有的用戶地址在真正訪問到物理地址之前必須經(jīng)過MMU的轉(zhuǎn)換, 從而使得OS可以防止用戶程序隨便亂用。對於一個沒有內(nèi)存映射的OS,MIPSCPU的用戶態(tài)其實(shí)是多余的。在核心態(tài)下,CPU可以存取低段地址空間,這個存取也是通過MMU的轉(zhuǎn)換。
下面來談?wù)凪IPS64 CPU的虛擬地址空間。
64位CPU的地址空間的最低2G和最高2G區(qū)域是和32位情況下一樣的,64位擴(kuò)展的地址部分在這兩者之間。64位下那些大塊的不需要MMU轉(zhuǎn)換的窗口可以克服kseg0和kseg1 512M的局限,但是32位下我們可以通過對MMU編程來同樣達(dá)到這一點(diǎn)。
/***************************************************************************************/
MIPS CPU內(nèi)存管理與TLB(整理自《See MIPS Run》):
早期的MIPS CPU定位于支持運(yùn)行在UNIX工作站與服務(wù)器上的應(yīng)用程序,因此內(nèi)存管理硬件被構(gòu)想為一個最小化的能幫助BSDUNIX——一個經(jīng)過完善設(shè)計(jì)并擁有充分多虛擬存儲需求的操作系統(tǒng)的典型——提供內(nèi)存管理功能的硬件。我們將從MIPS的設(shè)計(jì)起點(diǎn)開始,面對著一個unix類型的操作系統(tǒng)以及它的虛存系統(tǒng)的眾多需求。我們將會展示一下MIPS的硬件是如何滿足這些需求的。結(jié)尾時,我們會討論一下在不能像通常一樣使用內(nèi)存管理硬件的嵌入式系統(tǒng)中,您可以采取的幾種使用方式。
UNIX內(nèi)存管理工作的本質(zhì)是為了能運(yùn)行眾多不同的任務(wù)(即multitasking——多進(jìn)程),并且每個任務(wù)各自擁有自己的內(nèi)存空間。如果這個工作圓滿完成,那么各任務(wù)的命運(yùn)將彼此獨(dú)立開來(操作系統(tǒng)自身也因此得以保護(hù)):一個任務(wù)自身崩潰或者錯誤的做某些事不會影響整個系統(tǒng)。顯然,對一個使用分布終端來運(yùn)行學(xué)生們程序的大學(xué)而言,這是一個很有用的特性;然而不僅如此,甚至是要求最嚴(yán)格的商業(yè)系統(tǒng)環(huán)境也需要能夠在運(yùn)行的同時支持實(shí)驗(yàn)軟件或原型軟件一并進(jìn)行調(diào)試和測試。
MMU并不僅僅為了建立巨大而完備的虛擬存儲系統(tǒng),小的嵌入式程序也能在重定位和更有效的內(nèi)存分配里受益。如果能把應(yīng)用程序觀念上的地址映射到任何可獲得的物理地址,系統(tǒng)在不同時刻運(yùn)行不同程序就會更加容易。
嵌入式應(yīng)用中常常會明確的運(yùn)用多進(jìn)程機(jī)制,但幾乎沒有多少嵌入式操作系統(tǒng)使用隔離的地址空間?;蛟S這歸咎于這種機(jī)制在嵌入式CPU以及它們上面的操作系統(tǒng)上用處不大并且?guī)聿环€(wěn)定性,因而顯得不那么重要。