在Linux中可以將一部分內(nèi)存mount為分區(qū)來使用,通常稱之為RamDisk,分為:
Ramdisk, ramfs, tmpfs。
?、?第一種就是傳統(tǒng)意義上的,可以格式化,然后加載。這在Linux內(nèi)核2.0/2.2就已經(jīng)支持,其不足之處是大小固定,之后不能改變。為了能夠使用 Ramdisk,我們在編譯內(nèi)核時須將block device中的Ramdisk支持選上,它下面還有兩個選項,一個是設定Ramdisk的大小,默認是4096k;另一個是initrd的支持。
如果對Ramdisk的支持已經(jīng)編譯進內(nèi)核,我們就可以使用它了:首先查看一下可用的RamDisk,使用 ls /dev/ram*;首先創(chuàng)建一個目錄,比如test,運行 mkdir /mnt/test;然后對/dev/ram0 創(chuàng)建文件系統(tǒng),運行 mke2fs /dev/ram0;最后掛載/dev/ram0,運行mount /dev/ram /mnt/test,就可以象對普通硬盤一樣對它進行操作了。
② 另兩種則是內(nèi)核2.4才支持的,通過Ramfs或者Tmpfs來實現(xiàn):它們不需經(jīng)過格式化,用起來靈活,其大小隨所需要的空間而增加或減少。
Ramfs顧名思義是內(nèi)存文件系統(tǒng),它處于虛擬文件系統(tǒng)(VFS)層,而不像ramdisk那樣基于虛擬在內(nèi)存中的其他文件系統(tǒng)(ex2fs)。因而,它無需格式化,可以創(chuàng)建多個,只要內(nèi)存足夠,在創(chuàng)建時可以指定其最大能使用的內(nèi)存大小。
如果你的Linux已經(jīng)將Ramfs編譯進內(nèi)核,你就可以很容易地使用Ramfs了。創(chuàng)建一個目錄,加載Ramfs到該目錄即可:
# mkdir /testRam # mount -t ramfs none /testRAM缺省情況下,Ramfs被限制最多可使用內(nèi)存大小的一半??梢酝ㄟ^maxsize(以kbyte為單位)選項來改變。
# mount -t ramfs none /testRAM -o maxsize=2000 (創(chuàng)建了一個限定最大使用內(nèi)存為2M的ramdisk)③ Tmpfs是一個虛擬內(nèi)存文件系統(tǒng),它不同于傳統(tǒng)的用塊設備形式來實現(xiàn)的Ramdisk,也不同于針對物理內(nèi)存的Ramfs。
Tmpfs 可以使用物理內(nèi)存,也可以使用交換分區(qū)。在Linux內(nèi)核中,虛擬內(nèi)存資源由物理內(nèi)存(RAM)和交換分區(qū)組成,這些資源是由內(nèi)核中的虛擬內(nèi)存子系統(tǒng)來負 責分配和管理。Tmpfs向虛擬內(nèi)存子系統(tǒng)請求頁來存儲文件,它同Linux的其它請求頁的部分一樣,不知道分配給自己的頁是在內(nèi)存中還是在交換分區(qū)中。同Ramfs一樣,其大小也不是固定的,而是隨著所需要的空間而動態(tài)的增減。
使用tmpfs,首先你編譯內(nèi)核時得選擇“虛擬內(nèi)存文件系統(tǒng)支持(Virtual memory filesystem support)”。然后就可以加載tmpfs文件系統(tǒng)了:
# mkdir -p /mnt/tmpfs
# mount tmpfs /mnt/tmpfs -t tmpfs
同樣可以在加載時指定tmpfs文件系統(tǒng)大小的最大限制:
# mount tmpfs /mnt/tmpfs -t tmpfs -o size=32m
FAT: bogus logical sector size 21072
具體的文件系統(tǒng)FAT格式。虛擬邏輯扇區(qū)大小為20K,linux-2.4.22/fs/fat/Inode.c。
在初始化MS-DOS文件系統(tǒng)時,讀MS-DOS文件系統(tǒng)的superblock,函數(shù)fat_read_super中輸出的上面的信息。
UMSDOS: msdos_read_super failed, mount aborted.
UMSDOS:一種文件系統(tǒng),特點容量大 但相對而言不大穩(wěn)定。是Linux 使用的擴展了的DOS文件系統(tǒng)。它在 DOS 文件系統(tǒng)下增加了長文件名、 UID/GID、POSIX 權限和特殊文件 (設備、命名管道等)功能,而不犧牲對 DOS 的兼容性。允許一個普通的msdos文件系統(tǒng)用于Linux,而且無須為它建立單獨的分區(qū),特別適合早期的硬盤空間不足的硬件條件。
VFS: Mounted root (romfs filesystem) readonly
虛擬文件系統(tǒng)VFS(Virtual Filesystem Switch)的輸出信息。
再 次強調(diào)一下一個概念。VFS 是一種軟件機制,也可稱它為 Linux 的文件系統(tǒng)管理者,它是用來管理實際文件系統(tǒng)的掛載點,目的是為了能支持多種文件系統(tǒng)。kernel會先在內(nèi)存中建立一顆 VFS 目錄樹,是內(nèi)存中的一個數(shù)據(jù)對象,然后在其下掛載rootfs文件系統(tǒng),還可以掛載其他類型的文件系統(tǒng)到某個子目錄上。
Mounted devfs on /dev
加載devfs設備管理文件系統(tǒng)到dev安裝點上。/dev是我們經(jīng)常會用到的一個目錄。在2.4的kernel中才有使用到。每次啟動時內(nèi)核會自動掛載devfs。
devfs 提供了訪問內(nèi)核設備的命名空間。它并不是建立或更改設備節(jié)點,devfs只是為你的特別文件系統(tǒng)進行維護。一般我們可以手工mknod創(chuàng)件設備節(jié)點。 /dev目錄最初是空的,里面特定的文件是在系統(tǒng)啟動時、或是加載模組后驅(qū)動程序載入時建立的。當模組和驅(qū)動程序卸載時,文件就消失了。
Freeing init memory: 72K
釋放1號用戶進程init所占用的內(nèi)存
*************************************************************
第三節(jié):加載linux內(nèi)核完畢,轉(zhuǎn)入cpu_idle進程
系統(tǒng)啟動過程中進程情況:
?、?init進程
一 般來說, 系統(tǒng)在跑完 kernel bootstrapping 內(nèi)核引導自舉后(被裝入內(nèi)存、已經(jīng)開始運行、已經(jīng)初始化了所有的設備驅(qū)動程序和數(shù)據(jù)結構等等), 就去運行 init『萬process之父』, 有了它, 才能開始跑其他的進程,因此,init進程,它是內(nèi)核啟動的第一個用戶級進程,它的進程號總是1。你可以用進程查看命令來驗證:
# ps aux
PID Uid VmSize Stat Command
1 0 SW init
2 0 SW [keventd]
3 0 SWN [ksoftirqd_CPU0]
4 0 SW [kswapd]
5 0 SW [bdflush]
6 0 SW [kupdated]
7 0 SW [rbwdg]
9 0 SW [mtdblockd]
10 0 SW [khubd]
80 0 SW [loop0]
另外 Linux 有兩個 kernel 類的 process 也開始跑了起來,一個是 kflushd/bdflush,另一個是 kswapd。只有這個init 是完全屬于 user 類的進程, 后兩者是 kernel假借 process 進程之名掛在進程上。
init 有許多很重要的任務,比如象啟動getty(用于用戶登錄)、實現(xiàn)運行級別、以及處理孤立進程。init 一開始就去讀 /etc/inittab (init初始化表),初始化表是按一定格式排列的關于進程運行時的有關信息的。init程序需要讀取/etc/inittab文件作為其行為指針。這個 inittab 中對于各個runlevel運行級別要跑哪些 rc 或 spawn 生出什么有很清楚的設定。
一 般, 在Linux中初始化腳本在/etc/inittab 文件(或稱初始化表)中可以找到關于不同運行級別的描述。inittab是以行為單位的描述性(非執(zhí)行性)文本,每一個指令行都是固定格式。 inittab中有respawn項,但如果一個命令運行時失敗了,為了避免重運行的頻率太高,init將追蹤一個命令重運行了多少次,并且如果重運行的 頻率太高,它將被延時五分鐘后再運行。
?、?kernel進程
A》 請注意init是1號進程,其他進程id分別是kflushd/ bdflush, kupdate, kpiod and kswapd。這里有一個要指出的:你會注意到虛擬占用(SIZE)和實際占用(RSS)列都是0,進程怎么會不使用內(nèi)存呢?
這些進程就是內(nèi)核守護進程。大部分內(nèi)核并不顯示在進程列表里。守護進程在init之后啟動,所以他們和其他進程一樣有進程ID,但是他們的代碼和數(shù)據(jù)都存放在內(nèi)核占有的內(nèi)存中。在列表中使用中括號來區(qū)別與其他進程。
B》 輸入和輸出是通過內(nèi)存中的緩沖來完成的,這讓事情變得更快,程序的寫入會存放在內(nèi)存緩沖中,然后再一起寫入硬盤。守護進程kflushd和kupdate 管理這些工作。kupdate間斷的工作(每5秒)來檢查是否有寫過的緩沖,如過有,就讓kflushd把它們寫入磁盤。
C》 進程有時候無事可做,當它運行時也不一定需要把其所有的代碼和數(shù)據(jù)都放在內(nèi)存中。這就意味著我們可以通過把運行中程序不用的內(nèi)容切換到交換分區(qū)來更好的是利用內(nèi)存。把這些進程數(shù)據(jù)移入/移出內(nèi)存通過進程IO管理守護進程kpiod和交換守護進程kswapd,大約每隔1秒,kswapd醒來并檢查內(nèi)存情 況。如果在硬盤的東西要讀入內(nèi)存,或者內(nèi)存可用空間不足,kpiod就會被調(diào)用來做移入/移出操作。
D》 bdflush - BUF_DIRTY, 將dirty緩存寫回到磁盤的核心守護進程。對于有許多臟的緩沖區(qū)(包含必須同時寫到磁盤的數(shù)據(jù)的緩沖區(qū))的系統(tǒng)提供了動態(tài)的響應。它在系統(tǒng)啟動的時候作為一個核心線程啟動,它叫自己為 “kflushd”,而這是你用ps顯示系統(tǒng)中的進程的時候你會看得的名字。即定期(5秒)將臟(dirty)緩沖區(qū)的內(nèi)容寫入磁盤,以騰出內(nèi)存;
E》 ksoftirqd_CPUx 是一個死循環(huán), 負責處理軟中斷的。它是用來對軟中斷隊列進行緩沖處理的進程。當發(fā)生軟中斷時,系統(tǒng)并不急于處理,只是將相應的cpu的中斷狀態(tài)結構中的active 的相應的位,置位,并將相應的處理函數(shù)掛到相應的隊列,然后等待調(diào)度時機來臨,再來處理。
ksoftirqd_CPUx是由 cpu_raise_softirq() 即cpu觸發(fā)中斷,喚醒的內(nèi)核線程,這涉及到軟中斷,ksoftirqd的代碼參見[kernel/softirq.c]。
F》 keventd,它的任務就是執(zhí)行 scheduler 調(diào)度器隊列中的任務,keventd 為它運行的任務提供了可預期的進程上下文。
G》 khubd, 是用來檢測USB hub設備的,當usb有動態(tài)插拔時,將交由此內(nèi)核進程來處理。在檢測到有hub事件時會有相應的動作(usb_hub_events())
H》 mtdblockd是用來對flash塊設備進行寫操作的守護進程。NAND類型的Flash需要MTD(Memory Technology Devices 內(nèi)存技術驅(qū)動程序)驅(qū)動的支持才能被linux所使用。NAND的特點是不能在芯片內(nèi)執(zhí)行(XIP,eXecute In Place),需要把代碼讀到系統(tǒng)RAM中再執(zhí)行,傳輸效率不是最高,最大擦寫次數(shù)量為一百萬次,但寫入和擦除的速度很快,擦除單元小,是高數(shù)據(jù)存儲密度 的最佳選擇。NAND需要I/O接口,因此使用時需要驅(qū)動程序。
I》 loop0 是負責處理loop塊設備的(回環(huán)設備)。loopback device指的就是拿文件來模擬塊設備, 在我們這里,loop設備主要用來處理需要mount到板上的文件系統(tǒng),類似mount /tmp/rootfs /mnt -o loop。。我們的實例有:mount -o loop -t cramfs /xxx.bin /xxx 也就是將xxx.bin這個文件mount到板上來模擬cramfs壓縮ram文件系統(tǒng)。loop0進程負責對loop設備進行操作。
loopback設備和其他的塊設備的使用方法相同。特別的是,可以在該設備上建立一個文件系統(tǒng),然后利用mount命令把該系統(tǒng)映射到某個目錄下以便訪問。這種整個建立在一個普通磁盤文件上的文件系統(tǒng),就是虛擬文件系統(tǒng) (virtual file system)。
總結
上面的內(nèi)容是本人為了在實際開發(fā)中更加清楚地了解uclinux的啟動過程而做的一個總結性的文章。在對uclinux的啟動過程做了一個詳細注釋后,大家 會對涉及到嵌入系統(tǒng)的各個概念有了一個更加明確的認識,并能對嵌入系統(tǒng)的軟硬件環(huán)境的有關設置更加清楚。當你自己動手結合linux源代碼來分析時,將會有一個清楚的全局觀。
=============================================================
1. 運行bootloader初始化程序
SRAM 、SDRAM等存儲設備屬于揮發(fā)性的存儲器,掉電以后其中的內(nèi)容就會全部丟失,所以必須把操作系統(tǒng)的內(nèi)核鏡像存放在Flash等不揮發(fā)性存儲介質(zhì)上。但是操作系統(tǒng)在運行時,需要動態(tài)的創(chuàng)建一些如數(shù)據(jù)段、堆棧、頁表(針對使用虛擬地址的操作系統(tǒng))等內(nèi)容,所以需要在RAM中運行操作系統(tǒng)。
因此,就需要一個引導程序把操作系統(tǒng)的內(nèi)核鏡像從Flash存儲器拷貝到RAM中,然后再從RAM中執(zhí)行操作系統(tǒng)的內(nèi)核。Bootloader就是可以完成這樣一種功能的程序。
從本質(zhì)上來講,bootloader不屬于操作系統(tǒng)內(nèi)核。它采用匯編語言編寫,因此針對不同的CPU體系結構,這一部分代碼不具有可移植性。在移植操作系統(tǒng)時,這部分代碼必須加以改寫
具體來講,bootloader在系統(tǒng)啟動時主要完成以下幾項工作:
?。?) 將操作系統(tǒng)內(nèi)核從Flash拷貝到SDRAM中,如果是壓縮格式的內(nèi)核,還要將之解壓縮。
(2) 改寫系統(tǒng)的memory map,原先flash起始地址映射為0地址,這時需要將RAM的起始地址映射為0。
?。?) 設置堆棧指針并將bss段清零。
將來執(zhí)行C語言程序和調(diào)用子函數(shù)時要用到
?。?) 改變pc值,使得CPU開始執(zhí)行真正的操作系統(tǒng)內(nèi)核。
2. 運行操作系統(tǒng)內(nèi)核
bootloader程序執(zhí)行完上述的各項工作后,通過一條跳轉(zhuǎn)指令,轉(zhuǎn)而執(zhí)行init目錄下C語言源文件main.c中的函數(shù)start_kernel()。
因為在此之前bootloader已經(jīng)創(chuàng)建好一個初始化環(huán)境,C函數(shù)可以開始執(zhí)行了。
整個操作系統(tǒng)內(nèi)核的初始化工作從這里才算是真正開始。這個函數(shù)的長度比較短,代碼如下:
void __init start_kernel(void)
{
char * command_line;
unsigned long mempages;
extern char saved_command_line[];
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
lock_kernel();
printk(linux_banner);
setup_arch(&command_line);
printk(“Kernel command line: %s/n”, saved_command_line);
parse_options(command_line);
trap_init();
init_IRQ();
sched_init();
softirq_init();
time_init();
/*
* HACK ALERT! This is early. We‘re enabling the console before
* we’ve done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init();
#ifdef CONFIG_MODULES
init_modules();
#endif
if (prof_shift) {
unsigned int size;
/* only text is profiled */
prof_len = (unsigned long) &_etext - (unsigned long) &_stext;
prof_len 》》= prof_shift;
size = prof_len * sizeof(unsigned int) + PAGE_SIZE-1;
prof_buffer = (unsigned int *) alloc_bootmem(size);
}
kmem_cache_init();
sti();
calibrate_delay();
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok && initrd_start 《 min_low_pfn 《《 PAGE_SHIFT)
{
printk(KERN_CRIT “initrd overwritten (0x%08lx 《 0x%08lx) - ”
“disabling it./n”,initrd_start,min_low_pfn 《《 PAGE_SHIFT);
initrd_start = 0;
}
#endif
mem_init();
kmem_cache_sizes_init();
pgtable_cache_init();
mempages = num_physpages;
fork_init(mempages);
proc_caches_init();
vfs_caches_init(mempages);
buffer_init(mempages);
page_cache_init(mempages);
#if defined(CONFIG_ARCH_S390)
ccwcache_init();
#endif
signals_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
#if defined(CONFIG_SYSVIPC)
ipc_init();
#endif
check_bugs();
printk(“POSIX conformance testing by UNIFIX/n”);
/*
* We count on the initial thread going ok
Like idlers init is an unlocked kernel thread,which will
* make syscalls (and thus be locked)。
*/
smp_init();
rest_init();
}
內(nèi)核啟動之后需要執(zhí)行的第一個函數(shù)是start_kernel()(在linux/init/main.c文件中)。
start_kernel()
完成下面一系列初始化的工作。
◆printk(1inux_banner),顯示Linux內(nèi)核的版本信息。
◆ setup_arch(&command_line),做與體系結構相關的初始化工作。
◆ parse_options(command_line),解釋系統(tǒng)參數(shù)。
◆ trap_init(),設置系統(tǒng)異常的入口點。
◆ init_IRQ(),初始化系統(tǒng)中斷服務。
◆ sched_init(),系統(tǒng)調(diào)度器的初始化。
◆ time_init(),時鐘、定時器初始化。
◆ softirq_init(),系統(tǒng)軟中斷的初始化。
◆console_init(),控制臺初始化。
◆kmem_cache_init(),內(nèi)核cache的初始化。
◆calibrate_delay(),校準時鐘。
◆mem_init(),內(nèi)存初始化。
◆kmem_cache_sizes_init(),創(chuàng)建及設置通用cache。
◆fork_init(mempages),建立uidcache,并且根據(jù)系統(tǒng)內(nèi)存大小來確定最大進程數(shù)目。
◆buffer_init(mempages),塊設備緩沖區(qū)的初始化。初始化一系列的cache。
◆check_bugs(),檢查體系結構漏洞。
◆kernel_thread(init NULL,CLONE_FS | CLONE_FILES | CLONE_SIGNAL),創(chuàng)建第一個核心進程,啟動init進程。
◆cpu_idle(),運行idle進程
接下去做的工作由init()函數(shù)來完成。init()首先要鎖定內(nèi)核,然后調(diào)用do_basic_setup( )來完成外部設備以及驅(qū)動程序的初始化。外設的初始化要根據(jù)內(nèi)核的配置來決定,一般需要做下面的初始化工作:
◆PCI總線初始化。
◆網(wǎng)絡初始化。
◆一系列其他設備的初始化。
◆start_context_thread()創(chuàng)建事件管理核心進程keventd。
◆通過do_initcalls()函數(shù)來啟動任何使用__initcall標識的函數(shù)。
◆文件系統(tǒng)初始化。
◆加載文件系統(tǒng)。
在do_basic_setup()調(diào)用完成之后,init()會釋放初始化函數(shù)所用的內(nèi)存,并且打開/dev/console設備重新定向控制臺,讓系統(tǒng)調(diào)用execve來執(zhí)行程序init。
到這里為止,Linux 內(nèi)核的初始化工作已經(jīng)完成。然后開始用戶態(tài)進程的初始化。
=============================================================
uClinux中內(nèi)存模塊的啟動初始化
arch/armnommu/kernel/entry_armv.S是一個匯編文件,他包含了一個kernel_entry的定義,這是整個內(nèi)核的進入點。在完成某些平臺相關的初始化工作之后,執(zhí)行流程跳轉(zhuǎn)到start_kerne()處。從這里開始考察uClinux的內(nèi)存模塊啟動初始化是如何實現(xiàn)的。
start_kernel()中與內(nèi)存模塊相關的函數(shù)調(diào)用流程如下:
setup_arch() paging_init() free_area_init() mem_init()
下面分別分析這些函數(shù)各自的功能以及uClinux對他們的改造。
?。?) setup_arch()
setup_arch()首先根據(jù)目前內(nèi)核所配置的平臺向某些特定地址寫入特殊字符序列,以完成對特定硬件的初始化,比如工作狀態(tài)發(fā)光二極管、錯誤和報警發(fā)光二極管。
存儲了可用物理內(nèi)存起始地址的變量memory_start的初始化是通過一個ld腳本中定義的變量_end進行的。ld腳本是用來控制GNU ld連接器在連接內(nèi)核各個目標文件部分的時候的配置動作,比如這樣一個腳本:
SECTION
{
。=0x10000;
.text:{*(.text)}
。=0x8000000;
.data:{*(.data)}
.bss :{*(.bss)}
}
用來配置ld連接目標文件的時候?qū)⑺械哪繕宋募械拇鎯Τ绦蛘牡?text段(section)連接到一起,并且映射到輸出文件的地址0x10000處,將所有目標文件中已初始化的數(shù)據(jù).data段連接到一起并放置到輸出可執(zhí)行文件的0x8000000地址處,而所有目標文件中還未初始化的數(shù)據(jù)段.bss連接起來后影射到輸出文件中緊跟在.data段之后的位置。
這個ld配置腳本文件對每個平臺都是不同的。如為MICETEK上所使用的uClinux版本使用的ld配置文件為arch/armnomm/vmLinux.lds??梢酝ㄟ^修改某個平臺上的ld腳本配置文件中的_end變量來達到配置其可用物理內(nèi)存起始地址的目的。
setup_arch()在完成對memory_start變量的初始化之后,通過某些特定手段檢測不同類型的內(nèi)存分布情況。比如為檢測某段地址范圍是否為RAM的方法是通過將某個地址的數(shù)據(jù)讀出來,將它加1后寫回內(nèi)存地址中,然后再讀出來和原始數(shù)據(jù)比較看看其值是否成功增加了1,這樣反復操作兩次,最后將數(shù)據(jù)恢復。如果是可讀可寫的RAM,那么這個測試的結果就是每次比較都是成功的,否則就不能將這個地址當作RAM。
在setup_arch()中還可能根據(jù)所用平臺進行對flash memory和ROM的測試。在這些平臺相關的工作完成之后,setup_arch()將對系統(tǒng)運行的第一個進程init_task的mm_struct結構中描述地址空間分布的變量start_code,end_code,end_data和brk進行初始化,start_code為0,其他三個數(shù)值分別為來自于ld腳本配置文件中定義的相關變量_etext、_edata和_end。
此后setup_arch()將根據(jù)Linux中為系統(tǒng)中的第一塊rom/flash memory card所分配的固定的主/從設備號(可以從Document/devices.txt中得到)來創(chuàng)建根文件系統(tǒng)的設備號,并存儲在后來將要用到的全局變量ROOT_DEV中。
setup_arch()最后完成對系統(tǒng)啟動參數(shù)的保存。
在調(diào)用setup_arch()返回之后,start_kernel()中得到了系統(tǒng)可用物理內(nèi)存的起始和結束地址,以及命令啟動時的命令行參數(shù)。
?。?) paging_init()
在Linux中,paging_init()的一項主要功能是建立頁目錄和頁表,而且將Linux移植到不同平臺的過程中非常重要的一個步驟就是修改這個函數(shù)來適應新的硬件平臺的虛擬內(nèi)存體系。但是由于在uClinux中不再使用虛擬內(nèi)存機制,也就不再需要維護頁目錄和頁表數(shù)據(jù)結構了,所以paging_init()在這里只是為系統(tǒng)啟動的時候保留一部分特殊用途的內(nèi)存區(qū)間。它返回后,從可以使用的內(nèi)存空間開始,依次是如下的數(shù)據(jù)結構:
empty_bad_page_table 占用1頁(4KB)
empty_bad_page 占用1頁(4KB)
empty_zero_page 占用1頁,并初始化為全0
mem_map
bitmap
paging_init()函數(shù)在返回前通過調(diào)用free_area_init(start_mem,end_mem)進行建立buddy system的映射位圖關系,以及建立空閑物理頁面鏈表的操作。
(3)free_area_init()
這個函數(shù)用于建立管理物理頁幀的數(shù)據(jù)結構mem_map,有多少物理頁幀就有多少mem_map_t類型的結構體與之相對應。每個頁面的mem_map_t結構中的flags被標明為PG_DMA和PG_reserved,并且頁幀號被賦給相應的數(shù)值。同時建立了管理空閑頁面的bitmap映射表,并且所有的位都被清零。
?。?) mem_init()
mem_init()函數(shù)遍歷整個可用物理內(nèi)存地址空間,將每個頁面相對應的struct page結構中flags的PG_reserved 標志位清除,標志用戶個數(shù)的count計數(shù)器置1,并同時統(tǒng)計可用物理頁面數(shù)量,然后打印系統(tǒng)的各個內(nèi)存參數(shù),如可用RAM和ROM的大小、內(nèi)核代碼段和數(shù)據(jù)段大小等。
======================================================
摘 要:本文采用三星公司的S3C44B0微處理器,對uClinux操作系統(tǒng)內(nèi)核的引導過程進行了剖析。
關鍵字:S3C44B0X;uClinux;嵌入式系統(tǒng);內(nèi)核引導
1 前言
伴隨著微電子的發(fā)展,用于嵌入式設備的處理器速度越來越快,功能也越來越強大。三星公司生產(chǎn)的S3C44B0微處理器,采用的是ARM7TDMI內(nèi)核。該內(nèi)核因為有著功耗小、成本低等特點,因此非常適合作為移動手持終端的處理器核心。Linux操作系統(tǒng)因為它的開放性,使得它不斷的被應用到各個領域。在嵌入式領域同樣也出現(xiàn)了各種各樣的Linux變體,最常用的是uClinux。也正是因為uClinux操作系統(tǒng)支持不帶MMU單元的ARM處理器,因此該系統(tǒng)可以對S3C44B0微處理器有很好的支持。
在嵌入式系統(tǒng)開發(fā)中,第一個部分便是系統(tǒng)的引導。而系統(tǒng)的引導過程是通過BootLoader來完成的。BootLoader程序是與硬件緊密相關的一段代碼,而且編寫的時候比較復雜,它主要的功能是初始化微處理器以及周邊的硬件資源,并且引導操作系統(tǒng)的啟動。下面我將以S3C44B0微處理器來作為例子,對uClinux操作系統(tǒng)內(nèi)核的引導過程進行一個剖析。
2 BootLoader程序概念
簡單的說Boot Lodaer就是在操作系統(tǒng)內(nèi)核運行之前運行的一段小程序,通過這段小程序,可以初始化硬件設備、建立系統(tǒng)的內(nèi)存空間映射圖,從而將系統(tǒng)的軟硬件環(huán)境設置成一個適合的狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核準備好正確的環(huán)境。最終,BootLoader把操作系統(tǒng)內(nèi)核映象加載到RAM中,并將系統(tǒng)控制權傳遞給它。
2.1 典型的BootLoader程序框架
操作系統(tǒng)角度來說,Boot Loader的總目標就是正確的調(diào)用內(nèi)核來執(zhí)行。
由于Boot Loader的實現(xiàn)依賴于CPU的體系結構,因此大多數(shù)Boot Loader都分為Stage1和Stage2兩大部分。依賴于CPU體系結構的代碼,例如設備初始化代碼等,通常都放在Stage1中,而且通常都用匯編語言來實現(xiàn),以達到短小精悍的目的。而Stage2通常用C語言來實現(xiàn),這樣可以實現(xiàn)更加復雜的功能,而且代碼會具有更好的可讀性和可移植性。
Boot Loader的Stage1通常包括如下步驟:
1) 硬件設備初始化
2) 為加載Boot Loader的Stage2準備RAM空間
3) 復制Boot Loader的Stage2到RAM空間中
4) 設置好堆棧
5) 跳轉(zhuǎn)到Stage2的C入口點
Boot Loader的Stage2通常包括如下步驟:
1) 始化本階段要使用的硬件設備
2) 檢測系統(tǒng)內(nèi)存映射(Memory Map)
3) 將Kernel映象和根文件系統(tǒng)映象從FLASH上讀取到RAM空間中
4) 為內(nèi)核設置啟動參數(shù)
5) 調(diào)用內(nèi)核
2.2 系統(tǒng)內(nèi)存組織
由于嵌入式設備具有很好的制定性,因此通常硬件環(huán)境會變的千差萬別。就算是用戶使用了相同的處理器芯片,但是也很有可能因為外圍設備電路設計的不同,而存在差異。對于BootLoader程序來說,存儲設備的與處理器的連接方式,與其息息相關。對于我們采用的S3C44B0微處理器來說,在系統(tǒng)加電之后,指令指針是指向0x00000000的,也就是說系統(tǒng)是從0x00000000開始之行。正是因為這個原因,通常這個地址空間我們會安排給FLASH存儲器。這樣我們可以將BootLoader啟動代碼以及我們之后將會要啟動的uClinux操作系統(tǒng)映像燒寫到Flash里。對于RAM地址空間,S3C44B0芯片將其設定為從0x0C000000到0x0FFFFFFF一共64MB的范圍里。我們可以通過設定存儲器控制寄存器來重新設定RAM的大小。例如我們試驗采用的存儲設備安排如下:
0x00000000 – 0x003FFFFF 4MB Flash
0x0C000000 – 0x0C7FFFFF 8MB RAM
通常來說對于系統(tǒng)的引導和操作系統(tǒng)的啟動,可以完全都在Flash中進行,但是Flash存儲器的速度相對于RAM來說會慢很多,因此出于速度上的考慮,我們通常會將啟動代碼和uClinux操作系統(tǒng)的內(nèi)核映像文件拷貝到RAM中之行。
下面我將對典型的BootLoader程序框架進行分析。
2.3 Stage1階段
該階段的主要工作是完成對系統(tǒng)中斷向量的設置,初始化微處理器內(nèi)部寄存器,初始化堆棧,初始化RAM地址空間,并且將Stage2部分的C代碼拷貝到RAM空間的指定地點,然后跳轉(zhuǎn)到C代碼入口點繼續(xù)執(zhí)行。對于這段代碼來說,做的都是一些準備工作,因此為了提高效率,這段代碼通常都是使用匯編語言來完成的。下面我將結合具體的代碼來分析一下Stage1的啟動過程。
1)設置中斷向量
設置S3C44B0處理器定義的8種系統(tǒng)中斷的中斷向量地址。這八種系統(tǒng)中斷分別是復位中斷、未定義指令中斷、軟件中斷、指令預取異常中斷、數(shù)據(jù)異常中斷、地址異常中斷、IRQ中斷和FIQ中斷。這8個中斷通常是通過無條件跳轉(zhuǎn)的方式來實現(xiàn)的。具體的代碼如下。
__entry :
b ResetHandler /* Reset vector */
b HandlerUndef /* Undefined instruction */
b HandlerSWI /* SWI */
b HandlerPabort /* Prefetch abort */
b HandlerDabort /* Data abort */
b 。 /* Address exception */
b HandlerIRQ /* IRQ */
b HandlerFIQ /* FIQ */
2)初始化微處理器內(nèi)部寄存器
這段代碼主要是要完成硬件部分的初始化,包括關閉中斷響應、初始化微處理器通用端口、設置CPU頻率等操作。不過需要注意的是,在進行硬件初始化之前需要將微處理器的運行狀態(tài)轉(zhuǎn)換到SVC模式下。
MRS a1,CPSR /*; Pickup current CPSR*/
BIC a1,a1,#MODE_MASK /*; Clear the mode bits*/
ORR a1,a1,#SUP_MODE /*; Set the supervisor mode bits*/
ORR a1,a1,#LOCKOUT /*; Insure IRQ and FIQ intr are locked out*/
MSR CPSR_cxsf,a1 /*; Setup the new CPSR*/
3)初始化系統(tǒng)RAM空間
這個部分的工作主要是為之后啟動代碼和內(nèi)核映像的拷貝操作做準備,并且也為之后的C代碼的執(zhí)行初始化堆棧。這部分的工作主要可以分成兩個部分來處理。首先,根據(jù)系統(tǒng)配置的存儲器特性來初始化相關的存儲器控制寄存器。在我們使用的S3C44B0處理器中,存儲空間被分成了BANK0-BANK7一共8個塊,分別由BANKCON0-BANKCON7控制各個塊存儲器的讀寫時鐘和片選時鐘等信號參數(shù)。具體代碼如下:
ldr r0,=rBANKCON0
ldr r1,=0x700
str r1,[r0]
ldr r0,=rBANKCON1
ldr r1,=0x700 /* 0x7ffc */
str r1,[r0]
ldr r0,=rBANKCON2
ldr r1,=0x700 /* 0x7ffc */
str r1,[r0]
ldr r0,=rBANKCON3
ldr r1,=0x7568
str r1,[r0]
ldr r0,=rBANKCON4
ldr r1,=0x700 /* 0x7ffc */
str r1,[r0]
ldr r0,=rBANKCON5
ldr r1,=0x700 /* 0x7ffc */
str r1,[r0]
ldr r0,=rBANKCON6
ldr r1,=0x18008
str r1,[r0]
ldr r0,=rBANKCON7
ldr r1,=0x18000
str r1,[r0]
ldr r0,=rREFRESH
ldr r1,=0xac03e1
str r1,[r0]
ldr r0,=rBANKSIZE
ldr r1,=0x16
str r1,[r0]
ldr r0,=rMRSRB6
ldr r1,=0x020
str r1,[r0]
ldr r0,=rMRSRB7
ldr r1,=0x020
str r1,[r0]
初始化RAM空間的第二個部分就是初始化連接腳本文件中指定的需要清0的地址空間,將該斷地址空間的內(nèi)容清0。該部分地址空間主要是用來存放C語言代碼中的全局變量等內(nèi)容的。實現(xiàn)代碼如下:
LDR a1,=Image_ZI_Base /* Pickup the start of the BSS area */
MOV a3,#0 /* Clear value in a3 */
LDR a2,=Image_ZI_Limit /* Pickup the end of the BSS area */
CMP a1,a2
BEQ move_data
clear_loop :
STR a3,[a1],#4 /* Clear a word, a1 += 4 */
CMP a1,a2 /* end of ZI ? */
BNE clear_loop
4)為Stage2的C語言代碼的執(zhí)行準備必要的堆棧
因為在Stage2階段一般都是采用C語言代碼來完成的,因此必須在使用C語言代碼之前先建立起必要的堆棧信息。通常為了避免堆棧數(shù)據(jù)被執(zhí)行代碼破壞,通常都是放在RAM的高端地址,并且使得堆棧指針的增長方向是向下增長的。
5)將初始化代碼拷貝到RAM中,并且跳轉(zhuǎn)到RAM中執(zhí)行。因為在我們采用的S3C44B0微處理器里對于FLASH和RAM地址空間是使用的統(tǒng)一編址的,因此我們可以直接使用一個簡單循環(huán)來完成拷貝。
ldr r3, =0x10000 /* 64K Bytes */
ldr r2, =0xc700000
ldr r1, =0
next :
ldr r0,[r1],#4
str r0,[r2],#4
cmp r1,r3
bne next
6)跳轉(zhuǎn)到C代碼執(zhí)行(即Stage2階段)
這個過程是直接給指令指針賦值于跳轉(zhuǎn)的C代碼的入口地址,在我們的試驗中該入口地址是Main。
LDR pc,=Main
2.4 Stage2階段
該階段的代碼主要使用C語言來實現(xiàn)的。該階段的工作主要是建立開發(fā)板與宿主機之間的通信,加載uClinux內(nèi)核映像文件和配置內(nèi)核啟動參數(shù),并且啟動內(nèi)核。
嵌入式設備與宿主機的通訊方式有多種,最常用的是使用串口方式進行數(shù)據(jù)交換。本試驗采用的S3C44B0微處理器提供了兩個UART口,因此我們可以任選其中一個來初始化并且使用它來與宿主機交互。對于串口的初始化主要是波特率、奇偶校驗、停止位、數(shù)據(jù)位等內(nèi)容。
對于串口的波特率和波特因子的計算采用如下公式
Iubrd =((int(mclk/16 / baud + 0.5) – 1)
mclk是頻率、baud為波特率
2.4.1 檢測內(nèi)存
該部分的功能主要是檢測系統(tǒng)在進行硬件初始化的時候是否發(fā)生了內(nèi)存映射錯誤,即是否物理地址是否被映射到不存在的地址空間。通常是使用讀寫方式來檢測的,即以內(nèi)存頁為單位,在每個頁頭進行讀寫操作,比較讀寫結果。因為S3C44B0處理器并不支持內(nèi)存映射,因此我們在Stage2過程中并沒有包含該部分功能函數(shù)。
2.4.2 加載uClinux內(nèi)核映像
該過程其實只是一個從Flash的指定位置(該位置是uClinux燒寫的起始地址)拷貝到RAM中指定的地址空間里。在拷貝之前必須要為uClinux的全局變量結構,即啟動參數(shù)、內(nèi)核頁表、RAM的頁目錄等信息預留一定的空間。如果我們將FLASH和RAM看成連在一起的線性地址,則系統(tǒng)的空間分配會如下圖:
。..。..
Boot
初始化代碼
uClinux
未用
中斷向量表
初始化映像代碼
啟動參數(shù)
內(nèi)核映像
未用
堆棧
2.4.3 配置內(nèi)核啟動參數(shù)
我們采用的uClinux是2.4.x內(nèi)核版本,該版本的內(nèi)核支持參數(shù)啟動過程。在嵌入式系統(tǒng)中,啟動參數(shù)的傳入主要是依靠bootloader程序向標記列表(tagged list)的相關域中填寫相應的值來完成的。
2.5 uClinux內(nèi)核引導
當我們初始化完畢uClinux的啟動參數(shù)之后,控制權就可以交給uClinux內(nèi)核了,uClinux系統(tǒng)調(diào)用內(nèi)核解壓函數(shù)(decompress_kernel)來對上一個階段拷貝的uClinux內(nèi)核在RAM空間里進行解壓(當然如果系統(tǒng)內(nèi)核在建立的時候沒有配置成壓縮格式,則解壓過程略去)。在解壓完畢后,跳轉(zhuǎn)到內(nèi)核調(diào)用函數(shù)(call_kernel),該函數(shù)實際上執(zhí)行的是start_kernel(),這個函數(shù)包含了有關處理器初始化、中斷初始化、進程初始化等操作。最后,將控制權完全的交與uClinux操作系統(tǒng)來執(zhí)行。
偽處理過程如下:
IF(啟動參數(shù)正確)
CALL decmporess_kernel()
CALL call_kernel()
ELSE
啟動失敗
decompress_kernel()
{
解壓內(nèi)核映像
}
call_kernel()
{
。..
start_kernel()
。..。
}
3 總結
本文是對S3C44B0的啟動過程進行了一次分析,啟動部分的代碼可以說是嵌入式設備開發(fā)比較重要的部分。而且該部分的處理工作往往又比較麻煩,因此在這里我只是想起到拋磚引玉的作用。因為成文時間比較倉促,難免有錯誤,請大家批評指正。
==========================================================
《ARM7 uClinux開發(fā)實驗與實踐》P130
Bootloader完成系統(tǒng)初始化工作后,將運行控制權交給uClinux內(nèi)核。根據(jù)內(nèi)核是否壓縮以及內(nèi)核是否在本地執(zhí)行,uClinux通常有以下兩種可選的啟動方式:
?。?)Flash本地運行方式。內(nèi)核中未經(jīng)壓縮的可執(zhí)行映像固化在Flash中,系統(tǒng)啟動時,內(nèi)核在Flash中開始逐句執(zhí)行。
?。?)壓縮內(nèi)核加載方式。內(nèi)核的壓縮映像固化在Flash上,系統(tǒng)啟動時,由附加在壓縮映像前的解壓復制程序讀取壓縮映像,并在內(nèi)存中解壓后執(zhí)行。這種方式相對復雜,但是運行速度更快。
首先介紹內(nèi)核的Flash本地運行方式。
本地運行時,內(nèi)核的啟動包括特定體系結構設置和uClinux系統(tǒng)初始化兩步,內(nèi)核啟動的入口文件是head-armv.s。
評論