在Linux中可以將一部分內(nèi)存mount為分區(qū)來使用,通常稱之為RamDisk,分為:
Ramdisk, ramfs, tmpfs。
① 第一種就是傳統(tǒng)意義上的,可以格式化,然后加載。這在Linux內(nèi)核2.0/2.2就已經(jīng)支持,其不足之處是大小固定,之后不能改變。為了能夠使用 Ramdisk,我們?cè)诰幾g內(nèi)核時(shí)須將block device中的Ramdisk支持選上,它下面還有兩個(gè)選項(xiàng),一個(gè)是設(shè)定Ramdisk的大小,默認(rèn)是4096k;另一個(gè)是initrd的支持。
如果對(duì)Ramdisk的支持已經(jīng)編譯進(jìn)內(nèi)核,我們就可以使用它了:首先查看一下可用的RamDisk,使用 ls /dev/ram*;首先創(chuàng)建一個(gè)目錄,比如test,運(yùn)行 mkdir /mnt/test;然后對(duì)/dev/ram0 創(chuàng)建文件系統(tǒng),運(yùn)行 mke2fs /dev/ram0;最后掛載/dev/ram0,運(yùn)行mount /dev/ram /mnt/test,就可以象對(duì)普通硬盤一樣對(duì)它進(jìn)行操作了。
?、?另兩種則是內(nèi)核2.4才支持的,通過Ramfs或者Tmpfs來實(shí)現(xiàn):它們不需經(jīng)過格式化,用起來靈活,其大小隨所需要的空間而增加或減少。
Ramfs顧名思義是內(nèi)存文件系統(tǒng),它處于虛擬文件系統(tǒng)(VFS)層,而不像ramdisk那樣基于虛擬在內(nèi)存中的其他文件系統(tǒng)(ex2fs)。因而,它無需格式化,可以創(chuàng)建多個(gè),只要內(nèi)存足夠,在創(chuàng)建時(shí)可以指定其最大能使用的內(nèi)存大小。
如果你的Linux已經(jīng)將Ramfs編譯進(jìn)內(nèi)核,你就可以很容易地使用Ramfs了。創(chuàng)建一個(gè)目錄,加載Ramfs到該目錄即可:
# mkdir /testRam # mount -t ramfs none /testRAM缺省情況下,Ramfs被限制最多可使用內(nèi)存大小的一半??梢酝ㄟ^maxsize(以kbyte為單位)選項(xiàng)來改變。
# mount -t ramfs none /testRAM -o maxsize=2000 (創(chuàng)建了一個(gè)限定最大使用內(nèi)存為2M的ramdisk)③ Tmpfs是一個(gè)虛擬內(nèi)存文件系統(tǒng),它不同于傳統(tǒng)的用塊設(shè)備形式來實(shí)現(xiàn)的Ramdisk,也不同于針對(duì)物理內(nèi)存的Ramfs。
Tmpfs 可以使用物理內(nèi)存,也可以使用交換分區(qū)。在Linux內(nèi)核中,虛擬內(nèi)存資源由物理內(nèi)存(RAM)和交換分區(qū)組成,這些資源是由內(nèi)核中的虛擬內(nèi)存子系統(tǒng)來負(fù) 責(zé)分配和管理。Tmpfs向虛擬內(nèi)存子系統(tǒng)請(qǐng)求頁來存儲(chǔ)文件,它同Linux的其它請(qǐng)求頁的部分一樣,不知道分配給自己的頁是在內(nèi)存中還是在交換分區(qū)中。同Ramfs一樣,其大小也不是固定的,而是隨著所需要的空間而動(dòng)態(tài)的增減。
使用tmpfs,首先你編譯內(nèi)核時(shí)得選擇“虛擬內(nèi)存文件系統(tǒng)支持(Virtual memory filesystem support)”。然后就可以加載tmpfs文件系統(tǒng)了:
# mkdir -p /mnt/tmpfs
# mount tmpfs /mnt/tmpfs -t tmpfs
同樣可以在加載時(shí)指定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)時(shí),讀MS-DOS文件系統(tǒng)的superblock,函數(shù)fat_read_super中輸出的上面的信息。
UMSDOS: msdos_read_super failed, mount aborted.
UMSDOS:一種文件系統(tǒng),特點(diǎn)容量大 但相對(duì)而言不大穩(wěn)定。是Linux 使用的擴(kuò)展了的DOS文件系統(tǒng)。它在 DOS 文件系統(tǒng)下增加了長文件名、 UID/GID、POSIX 權(quán)限和特殊文件 (設(shè)備、命名管道等)功能,而不犧牲對(duì) DOS 的兼容性。允許一個(gè)普通的msdos文件系統(tǒng)用于Linux,而且無須為它建立單獨(dú)的分區(qū),特別適合早期的硬盤空間不足的硬件條件。
VFS: Mounted root (romfs filesystem) readonly
虛擬文件系統(tǒng)VFS(Virtual Filesystem Switch)的輸出信息。
再 次強(qiáng)調(diào)一下一個(gè)概念。VFS 是一種軟件機(jī)制,也可稱它為 Linux 的文件系統(tǒng)管理者,它是用來管理實(shí)際文件系統(tǒng)的掛載點(diǎn),目的是為了能支持多種文件系統(tǒng)。kernel會(huì)先在內(nèi)存中建立一顆 VFS 目錄樹,是內(nèi)存中的一個(gè)數(shù)據(jù)對(duì)象,然后在其下掛載rootfs文件系統(tǒng),還可以掛載其他類型的文件系統(tǒng)到某個(gè)子目錄上。
Mounted devfs on /dev
加載devfs設(shè)備管理文件系統(tǒng)到dev安裝點(diǎn)上。/dev是我們經(jīng)常會(huì)用到的一個(gè)目錄。在2.4的kernel中才有使用到。每次啟動(dòng)時(shí)內(nèi)核會(huì)自動(dòng)掛載devfs。
devfs 提供了訪問內(nèi)核設(shè)備的命名空間。它并不是建立或更改設(shè)備節(jié)點(diǎn),devfs只是為你的特別文件系統(tǒng)進(jìn)行維護(hù)。一般我們可以手工mknod創(chuàng)件設(shè)備節(jié)點(diǎn)。 /dev目錄最初是空的,里面特定的文件是在系統(tǒng)啟動(dòng)時(shí)、或是加載模組后驅(qū)動(dòng)程序載入時(shí)建立的。當(dāng)模組和驅(qū)動(dòng)程序卸載時(shí),文件就消失了。
Freeing init memory: 72K
釋放1號(hào)用戶進(jìn)程init所占用的內(nèi)存
*************************************************************
第三節(jié):加載linux內(nèi)核完畢,轉(zhuǎn)入cpu_idle進(jìn)程
系統(tǒng)啟動(dòng)過程中進(jìn)程情況:
① init進(jìn)程
一 般來說, 系統(tǒng)在跑完 kernel bootstrapping 內(nèi)核引導(dǎo)自舉后(被裝入內(nèi)存、已經(jīng)開始運(yùn)行、已經(jīng)初始化了所有的設(shè)備驅(qū)動(dòng)程序和數(shù)據(jù)結(jié)構(gòu)等等), 就去運(yùn)行 init『萬process之父』, 有了它, 才能開始跑其他的進(jìn)程,因此,init進(jìn)程,它是內(nèi)核啟動(dòng)的第一個(gè)用戶級(jí)進(jìn)程,它的進(jìn)程號(hào)總是1。你可以用進(jìn)程查看命令來驗(yàn)證:
# 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 有兩個(gè) kernel 類的 process 也開始跑了起來,一個(gè)是 kflushd/bdflush,另一個(gè)是 kswapd。只有這個(gè)init 是完全屬于 user 類的進(jìn)程, 后兩者是 kernel假借 process 進(jìn)程之名掛在進(jìn)程上。
init 有許多很重要的任務(wù),比如象啟動(dòng)getty(用于用戶登錄)、實(shí)現(xiàn)運(yùn)行級(jí)別、以及處理孤立進(jìn)程。init 一開始就去讀 /etc/inittab (init初始化表),初始化表是按一定格式排列的關(guān)于進(jìn)程運(yùn)行時(shí)的有關(guān)信息的。init程序需要讀取/etc/inittab文件作為其行為指針。這個(gè) inittab 中對(duì)于各個(gè)runlevel運(yùn)行級(jí)別要跑哪些 rc 或 spawn 生出什么有很清楚的設(shè)定。
一 般, 在Linux中初始化腳本在/etc/inittab 文件(或稱初始化表)中可以找到關(guān)于不同運(yùn)行級(jí)別的描述。inittab是以行為單位的描述性(非執(zhí)行性)文本,每一個(gè)指令行都是固定格式。 inittab中有respawn項(xiàng),但如果一個(gè)命令運(yùn)行時(shí)失敗了,為了避免重運(yùn)行的頻率太高,init將追蹤一個(gè)命令重運(yùn)行了多少次,并且如果重運(yùn)行的 頻率太高,它將被延時(shí)五分鐘后再運(yùn)行。
② kernel進(jìn)程
A》 請(qǐng)注意init是1號(hào)進(jìn)程,其他進(jìn)程id分別是kflushd/ bdflush, kupdate, kpiod and kswapd。這里有一個(gè)要指出的:你會(huì)注意到虛擬占用(SIZE)和實(shí)際占用(RSS)列都是0,進(jìn)程怎么會(huì)不使用內(nèi)存呢?
這些進(jìn)程就是內(nèi)核守護(hù)進(jìn)程。大部分內(nèi)核并不顯示在進(jìn)程列表里。守護(hù)進(jìn)程在init之后啟動(dòng),所以他們和其他進(jìn)程一樣有進(jìn)程ID,但是他們的代碼和數(shù)據(jù)都存放在內(nèi)核占有的內(nèi)存中。在列表中使用中括號(hào)來區(qū)別與其他進(jìn)程。
B》 輸入和輸出是通過內(nèi)存中的緩沖來完成的,這讓事情變得更快,程序的寫入會(huì)存放在內(nèi)存緩沖中,然后再一起寫入硬盤。守護(hù)進(jìn)程kflushd和kupdate 管理這些工作。kupdate間斷的工作(每5秒)來檢查是否有寫過的緩沖,如過有,就讓kflushd把它們寫入磁盤。
C》 進(jìn)程有時(shí)候無事可做,當(dāng)它運(yùn)行時(shí)也不一定需要把其所有的代碼和數(shù)據(jù)都放在內(nèi)存中。這就意味著我們可以通過把運(yùn)行中程序不用的內(nèi)容切換到交換分區(qū)來更好的是利用內(nèi)存。把這些進(jìn)程數(shù)據(jù)移入/移出內(nèi)存通過進(jìn)程IO管理守護(hù)進(jìn)程kpiod和交換守護(hù)進(jìn)程kswapd,大約每隔1秒,kswapd醒來并檢查內(nèi)存情 況。如果在硬盤的東西要讀入內(nèi)存,或者內(nèi)存可用空間不足,kpiod就會(huì)被調(diào)用來做移入/移出操作。
D》 bdflush - BUF_DIRTY, 將dirty緩存寫回到磁盤的核心守護(hù)進(jìn)程。對(duì)于有許多臟的緩沖區(qū)(包含必須同時(shí)寫到磁盤的數(shù)據(jù)的緩沖區(qū))的系統(tǒng)提供了動(dòng)態(tài)的響應(yīng)。它在系統(tǒng)啟動(dòng)的時(shí)候作為一個(gè)核心線程啟動(dòng),它叫自己為 “kflushd”,而這是你用ps顯示系統(tǒng)中的進(jìn)程的時(shí)候你會(huì)看得的名字。即定期(5秒)將臟(dirty)緩沖區(qū)的內(nèi)容寫入磁盤,以騰出內(nèi)存;
E》 ksoftirqd_CPUx 是一個(gè)死循環(huán), 負(fù)責(zé)處理軟中斷的。它是用來對(duì)軟中斷隊(duì)列進(jìn)行緩沖處理的進(jìn)程。當(dāng)發(fā)生軟中斷時(shí),系統(tǒng)并不急于處理,只是將相應(yīng)的cpu的中斷狀態(tài)結(jié)構(gòu)中的active 的相應(yīng)的位,置位,并將相應(yīng)的處理函數(shù)掛到相應(yīng)的隊(duì)列,然后等待調(diào)度時(shí)機(jī)來臨,再來處理。
ksoftirqd_CPUx是由 cpu_raise_softirq() 即cpu觸發(fā)中斷,喚醒的內(nèi)核線程,這涉及到軟中斷,ksoftirqd的代碼參見[kernel/softirq.c]。
F》 keventd,它的任務(wù)就是執(zhí)行 scheduler 調(diào)度器隊(duì)列中的任務(wù),keventd 為它運(yùn)行的任務(wù)提供了可預(yù)期的進(jìn)程上下文。
G》 khubd, 是用來檢測USB hub設(shè)備的,當(dāng)usb有動(dòng)態(tài)插拔時(shí),將交由此內(nèi)核進(jìn)程來處理。在檢測到有hub事件時(shí)會(huì)有相應(yīng)的動(dòng)作(usb_hub_events())
H》 mtdblockd是用來對(duì)flash塊設(shè)備進(jìn)行寫操作的守護(hù)進(jìn)程。NAND類型的Flash需要MTD(Memory Technology Devices 內(nèi)存技術(shù)驅(qū)動(dòng)程序)驅(qū)動(dòng)的支持才能被linux所使用。NAND的特點(diǎn)是不能在芯片內(nèi)執(zhí)行(XIP,eXecute In Place),需要把代碼讀到系統(tǒng)RAM中再執(zhí)行,傳輸效率不是最高,最大擦寫次數(shù)量為一百萬次,但寫入和擦除的速度很快,擦除單元小,是高數(shù)據(jù)存儲(chǔ)密度 的最佳選擇。NAND需要I/O接口,因此使用時(shí)需要驅(qū)動(dòng)程序。
I》 loop0 是負(fù)責(zé)處理loop塊設(shè)備的(回環(huán)設(shè)備)。loopback device指的就是拿文件來模擬塊設(shè)備, 在我們這里,loop設(shè)備主要用來處理需要mount到板上的文件系統(tǒng),類似mount /tmp/rootfs /mnt -o loop。。我們的實(shí)例有:mount -o loop -t cramfs /xxx.bin /xxx 也就是將xxx.bin這個(gè)文件mount到板上來模擬cramfs壓縮ram文件系統(tǒng)。loop0進(jìn)程負(fù)責(zé)對(duì)loop設(shè)備進(jìn)行操作。
loopback設(shè)備和其他的塊設(shè)備的使用方法相同。特別的是,可以在該設(shè)備上建立一個(gè)文件系統(tǒng),然后利用mount命令把該系統(tǒng)映射到某個(gè)目錄下以便訪問。這種整個(gè)建立在一個(gè)普通磁盤文件上的文件系統(tǒng),就是虛擬文件系統(tǒng) (virtual file system)。
總結(jié)
上面的內(nèi)容是本人為了在實(shí)際開發(fā)中更加清楚地了解uclinux的啟動(dòng)過程而做的一個(gè)總結(jié)性的文章。在對(duì)uclinux的啟動(dòng)過程做了一個(gè)詳細(xì)注釋后,大家 會(huì)對(duì)涉及到嵌入系統(tǒng)的各個(gè)概念有了一個(gè)更加明確的認(rèn)識(shí),并能對(duì)嵌入系統(tǒng)的軟硬件環(huán)境的有關(guān)設(shè)置更加清楚。當(dāng)你自己動(dòng)手結(jié)合linux源代碼來分析時(shí),將會(huì)有一個(gè)清楚的全局觀。
=============================================================
1. 運(yùn)行bootloader初始化程序
SRAM 、SDRAM等存儲(chǔ)設(shè)備屬于揮發(fā)性的存儲(chǔ)器,掉電以后其中的內(nèi)容就會(huì)全部丟失,所以必須把操作系統(tǒng)的內(nèi)核鏡像存放在Flash等不揮發(fā)性存儲(chǔ)介質(zhì)上。但是操作系統(tǒng)在運(yùn)行時(shí),需要?jiǎng)討B(tài)的創(chuàng)建一些如數(shù)據(jù)段、堆棧、頁表(針對(duì)使用虛擬地址的操作系統(tǒng))等內(nèi)容,所以需要在RAM中運(yùn)行操作系統(tǒng)。
因此,就需要一個(gè)引導(dǎo)程序把操作系統(tǒng)的內(nèi)核鏡像從Flash存儲(chǔ)器拷貝到RAM中,然后再從RAM中執(zhí)行操作系統(tǒng)的內(nèi)核。Bootloader就是可以完成這樣一種功能的程序。
從本質(zhì)上來講,bootloader不屬于操作系統(tǒng)內(nèi)核。它采用匯編語言編寫,因此針對(duì)不同的CPU體系結(jié)構(gòu),這一部分代碼不具有可移植性。在移植操作系統(tǒng)時(shí),這部分代碼必須加以改寫
具體來講,bootloader在系統(tǒng)啟動(dòng)時(shí)主要完成以下幾項(xiàng)工作:
(1) 將操作系統(tǒng)內(nèi)核從Flash拷貝到SDRAM中,如果是壓縮格式的內(nèi)核,還要將之解壓縮。
?。?) 改寫系統(tǒng)的memory map,原先flash起始地址映射為0地址,這時(shí)需要將RAM的起始地址映射為0。
?。?) 設(shè)置堆棧指針并將bss段清零。
將來執(zhí)行C語言程序和調(diào)用子函數(shù)時(shí)要用到
(4) 改變pc值,使得CPU開始執(zhí)行真正的操作系統(tǒng)內(nèi)核。
2. 運(yùn)行操作系統(tǒng)內(nèi)核
bootloader程序執(zhí)行完上述的各項(xiàng)工作后,通過一條跳轉(zhuǎn)指令,轉(zhuǎn)而執(zhí)行init目錄下C語言源文件main.c中的函數(shù)start_kernel()。
因?yàn)樵诖酥癰ootloader已經(jīng)創(chuàng)建好一個(gè)初始化環(huán)境,C函數(shù)可以開始執(zhí)行了。
整個(gè)操作系統(tǒng)內(nèi)核的初始化工作從這里才算是真正開始。這個(gè)函數(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)核啟動(dòng)之后需要執(zhí)行的第一個(gè)函數(shù)是start_kernel()(在linux/init/main.c文件中)。
start_kernel()
完成下面一系列初始化的工作。
◆printk(1inux_banner),顯示Linux內(nèi)核的版本信息。
◆ setup_arch(&command_line),做與體系結(jié)構(gòu)相關(guān)的初始化工作。
◆ parse_options(command_line),解釋系統(tǒng)參數(shù)。
◆ trap_init(),設(shè)置系統(tǒng)異常的入口點(diǎn)。
◆ init_IRQ(),初始化系統(tǒng)中斷服務(wù)。
◆ sched_init(),系統(tǒng)調(diào)度器的初始化。
◆ time_init(),時(shí)鐘、定時(shí)器初始化。
◆ softirq_init(),系統(tǒng)軟中斷的初始化。
◆console_init(),控制臺(tái)初始化。
◆kmem_cache_init(),內(nèi)核cache的初始化。
◆calibrate_delay(),校準(zhǔn)時(shí)鐘。
◆mem_init(),內(nèi)存初始化。
◆kmem_cache_sizes_init(),創(chuàng)建及設(shè)置通用cache。
◆fork_init(mempages),建立uidcache,并且根據(jù)系統(tǒng)內(nèi)存大小來確定最大進(jìn)程數(shù)目。
◆buffer_init(mempages),塊設(shè)備緩沖區(qū)的初始化。初始化一系列的cache。
◆check_bugs(),檢查體系結(jié)構(gòu)漏洞。
◆kernel_thread(init NULL,CLONE_FS | CLONE_FILES | CLONE_SIGNAL),創(chuàng)建第一個(gè)核心進(jìn)程,啟動(dòng)init進(jìn)程。
◆cpu_idle(),運(yùn)行idle進(jìn)程
接下去做的工作由init()函數(shù)來完成。init()首先要鎖定內(nèi)核,然后調(diào)用do_basic_setup( )來完成外部設(shè)備以及驅(qū)動(dòng)程序的初始化。外設(shè)的初始化要根據(jù)內(nèi)核的配置來決定,一般需要做下面的初始化工作:
◆PCI總線初始化。
◆網(wǎng)絡(luò)初始化。
◆一系列其他設(shè)備的初始化。
◆start_context_thread()創(chuàng)建事件管理核心進(jìn)程keventd。
◆通過do_initcalls()函數(shù)來啟動(dòng)任何使用__initcall標(biāo)識(shí)的函數(shù)。
◆文件系統(tǒng)初始化。
◆加載文件系統(tǒng)。
在do_basic_setup()調(diào)用完成之后,init()會(huì)釋放初始化函數(shù)所用的內(nèi)存,并且打開/dev/console設(shè)備重新定向控制臺(tái),讓系統(tǒng)調(diào)用execve來執(zhí)行程序init。
到這里為止,Linux 內(nèi)核的初始化工作已經(jīng)完成。然后開始用戶態(tài)進(jìn)程的初始化。
=============================================================
uClinux中內(nèi)存模塊的啟動(dòng)初始化
arch/armnommu/kernel/entry_armv.S是一個(gè)匯編文件,他包含了一個(gè)kernel_entry的定義,這是整個(gè)內(nèi)核的進(jìn)入點(diǎn)。在完成某些平臺(tái)相關(guān)的初始化工作之后,執(zhí)行流程跳轉(zhuǎn)到start_kerne()處。從這里開始考察uClinux的內(nèi)存模塊啟動(dòng)初始化是如何實(shí)現(xiàn)的。
start_kernel()中與內(nèi)存模塊相關(guān)的函數(shù)調(diào)用流程如下:
setup_arch() paging_init() free_area_init() mem_init()
下面分別分析這些函數(shù)各自的功能以及uClinux對(duì)他們的改造。
?。?) setup_arch()
setup_arch()首先根據(jù)目前內(nèi)核所配置的平臺(tái)向某些特定地址寫入特殊字符序列,以完成對(duì)特定硬件的初始化,比如工作狀態(tài)發(fā)光二極管、錯(cuò)誤和報(bào)警發(fā)光二極管。
存儲(chǔ)了可用物理內(nèi)存起始地址的變量memory_start的初始化是通過一個(gè)ld腳本中定義的變量_end進(jìn)行的。ld腳本是用來控制GNU ld連接器在連接內(nèi)核各個(gè)目標(biāo)文件部分的時(shí)候的配置動(dòng)作,比如這樣一個(gè)腳本:
SECTION
{
。=0x10000;
.text:{*(.text)}
。=0x8000000;
.data:{*(.data)}
.bss :{*(.bss)}
}
用來配置ld連接目標(biāo)文件的時(shí)候?qū)⑺械哪繕?biāo)文件中的存儲(chǔ)程序正文的.text段(section)連接到一起,并且映射到輸出文件的地址0x10000處,將所有目標(biāo)文件中已初始化的數(shù)據(jù).data段連接到一起并放置到輸出可執(zhí)行文件的0x8000000地址處,而所有目標(biāo)文件中還未初始化的數(shù)據(jù)段.bss連接起來后影射到輸出文件中緊跟在.data段之后的位置。
這個(gè)ld配置腳本文件對(duì)每個(gè)平臺(tái)都是不同的。如為MICETEK上所使用的uClinux版本使用的ld配置文件為arch/armnomm/vmLinux.lds。可以通過修改某個(gè)平臺(tái)上的ld腳本配置文件中的_end變量來達(dá)到配置其可用物理內(nèi)存起始地址的目的。
setup_arch()在完成對(duì)memory_start變量的初始化之后,通過某些特定手段檢測不同類型的內(nèi)存分布情況。比如為檢測某段地址范圍是否為RAM的方法是通過將某個(gè)地址的數(shù)據(jù)讀出來,將它加1后寫回內(nèi)存地址中,然后再讀出來和原始數(shù)據(jù)比較看看其值是否成功增加了1,這樣反復(fù)操作兩次,最后將數(shù)據(jù)恢復(fù)。如果是可讀可寫的RAM,那么這個(gè)測試的結(jié)果就是每次比較都是成功的,否則就不能將這個(gè)地址當(dāng)作RAM。
在setup_arch()中還可能根據(jù)所用平臺(tái)進(jìn)行對(duì)flash memory和ROM的測試。在這些平臺(tái)相關(guān)的工作完成之后,setup_arch()將對(duì)系統(tǒng)運(yùn)行的第一個(gè)進(jìn)程init_task的mm_struct結(jié)構(gòu)中描述地址空間分布的變量start_code,end_code,end_data和brk進(jìn)行初始化,start_code為0,其他三個(gè)數(shù)值分別為來自于ld腳本配置文件中定義的相關(guān)變量_etext、_edata和_end。
此后setup_arch()將根據(jù)Linux中為系統(tǒng)中的第一塊rom/flash memory card所分配的固定的主/從設(shè)備號(hào)(可以從Document/devices.txt中得到)來創(chuàng)建根文件系統(tǒng)的設(shè)備號(hào),并存儲(chǔ)在后來將要用到的全局變量ROOT_DEV中。
setup_arch()最后完成對(duì)系統(tǒng)啟動(dòng)參數(shù)的保存。
在調(diào)用setup_arch()返回之后,start_kernel()中得到了系統(tǒng)可用物理內(nèi)存的起始和結(jié)束地址,以及命令啟動(dòng)時(shí)的命令行參數(shù)。
(2) paging_init()
在Linux中,paging_init()的一項(xiàng)主要功能是建立頁目錄和頁表,而且將Linux移植到不同平臺(tái)的過程中非常重要的一個(gè)步驟就是修改這個(gè)函數(shù)來適應(yīng)新的硬件平臺(tái)的虛擬內(nèi)存體系。但是由于在uClinux中不再使用虛擬內(nèi)存機(jī)制,也就不再需要維護(hù)頁目錄和頁表數(shù)據(jù)結(jié)構(gòu)了,所以paging_init()在這里只是為系統(tǒng)啟動(dòng)的時(shí)候保留一部分特殊用途的內(nèi)存區(qū)間。它返回后,從可以使用的內(nèi)存空間開始,依次是如下的數(shù)據(jù)結(jié)構(gòu):
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)進(jìn)行建立buddy system的映射位圖關(guān)系,以及建立空閑物理頁面鏈表的操作。
?。?)free_area_init()
這個(gè)函數(shù)用于建立管理物理頁幀的數(shù)據(jù)結(jié)構(gòu)mem_map,有多少物理頁幀就有多少mem_map_t類型的結(jié)構(gòu)體與之相對(duì)應(yīng)。每個(gè)頁面的mem_map_t結(jié)構(gòu)中的flags被標(biāo)明為PG_DMA和PG_reserved,并且頁幀號(hào)被賦給相應(yīng)的數(shù)值。同時(shí)建立了管理空閑頁面的bitmap映射表,并且所有的位都被清零。
?。?) mem_init()
mem_init()函數(shù)遍歷整個(gè)可用物理內(nèi)存地址空間,將每個(gè)頁面相對(duì)應(yīng)的struct page結(jié)構(gòu)中flags的PG_reserved 標(biāo)志位清除,標(biāo)志用戶個(gè)數(shù)的count計(jì)數(shù)器置1,并同時(shí)統(tǒng)計(jì)可用物理頁面數(shù)量,然后打印系統(tǒng)的各個(gè)內(nèi)存參數(shù),如可用RAM和ROM的大小、內(nèi)核代碼段和數(shù)據(jù)段大小等。
======================================================
摘 要:本文采用三星公司的S3C44B0微處理器,對(duì)uClinux操作系統(tǒng)內(nèi)核的引導(dǎo)過程進(jìn)行了剖析。
關(guān)鍵字:S3C44B0X;uClinux;嵌入式系統(tǒng);內(nèi)核引導(dǎo)
1 前言
伴隨著微電子的發(fā)展,用于嵌入式設(shè)備的處理器速度越來越快,功能也越來越強(qiáng)大。三星公司生產(chǎn)的S3C44B0微處理器,采用的是ARM7TDMI內(nèi)核。該內(nèi)核因?yàn)橛兄男?、成本低等特點(diǎn),因此非常適合作為移動(dòng)手持終端的處理器核心。Linux操作系統(tǒng)因?yàn)樗拈_放性,使得它不斷的被應(yīng)用到各個(gè)領(lǐng)域。在嵌入式領(lǐng)域同樣也出現(xiàn)了各種各樣的Linux變體,最常用的是uClinux。也正是因?yàn)閡Clinux操作系統(tǒng)支持不帶MMU單元的ARM處理器,因此該系統(tǒng)可以對(duì)S3C44B0微處理器有很好的支持。
在嵌入式系統(tǒng)開發(fā)中,第一個(gè)部分便是系統(tǒng)的引導(dǎo)。而系統(tǒng)的引導(dǎo)過程是通過BootLoader來完成的。BootLoader程序是與硬件緊密相關(guān)的一段代碼,而且編寫的時(shí)候比較復(fù)雜,它主要的功能是初始化微處理器以及周邊的硬件資源,并且引導(dǎo)操作系統(tǒng)的啟動(dòng)。下面我將以S3C44B0微處理器來作為例子,對(duì)uClinux操作系統(tǒng)內(nèi)核的引導(dǎo)過程進(jìn)行一個(gè)剖析。
2 BootLoader程序概念
簡單的說Boot Lodaer就是在操作系統(tǒng)內(nèi)核運(yùn)行之前運(yùn)行的一段小程序,通過這段小程序,可以初始化硬件設(shè)備、建立系統(tǒng)的內(nèi)存空間映射圖,從而將系統(tǒng)的軟硬件環(huán)境設(shè)置成一個(gè)適合的狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核準(zhǔn)備好正確的環(huán)境。最終,BootLoader把操作系統(tǒng)內(nèi)核映象加載到RAM中,并將系統(tǒng)控制權(quán)傳遞給它。
2.1 典型的BootLoader程序框架
操作系統(tǒng)角度來說,Boot Loader的總目標(biāo)就是正確的調(diào)用內(nèi)核來執(zhí)行。
由于Boot Loader的實(shí)現(xiàn)依賴于CPU的體系結(jié)構(gòu),因此大多數(shù)Boot Loader都分為Stage1和Stage2兩大部分。依賴于CPU體系結(jié)構(gòu)的代碼,例如設(shè)備初始化代碼等,通常都放在Stage1中,而且通常都用匯編語言來實(shí)現(xiàn),以達(dá)到短小精悍的目的。而Stage2通常用C語言來實(shí)現(xiàn),這樣可以實(shí)現(xiàn)更加復(fù)雜的功能,而且代碼會(huì)具有更好的可讀性和可移植性。
Boot Loader的Stage1通常包括如下步驟:
1) 硬件設(shè)備初始化
2) 為加載Boot Loader的Stage2準(zhǔn)備RAM空間
3) 復(fù)制Boot Loader的Stage2到RAM空間中
4) 設(shè)置好堆棧
5) 跳轉(zhuǎn)到Stage2的C入口點(diǎn)
Boot Loader的Stage2通常包括如下步驟:
1) 始化本階段要使用的硬件設(shè)備
2) 檢測系統(tǒng)內(nèi)存映射(Memory Map)
3) 將Kernel映象和根文件系統(tǒng)映象從FLASH上讀取到RAM空間中
4) 為內(nèi)核設(shè)置啟動(dòng)參數(shù)
5) 調(diào)用內(nèi)核
2.2 系統(tǒng)內(nèi)存組織
由于嵌入式設(shè)備具有很好的制定性,因此通常硬件環(huán)境會(huì)變的千差萬別。就算是用戶使用了相同的處理器芯片,但是也很有可能因?yàn)橥鈬O(shè)備電路設(shè)計(jì)的不同,而存在差異。對(duì)于BootLoader程序來說,存儲(chǔ)設(shè)備的與處理器的連接方式,與其息息相關(guān)。對(duì)于我們采用的S3C44B0微處理器來說,在系統(tǒng)加電之后,指令指針是指向0x00000000的,也就是說系統(tǒng)是從0x00000000開始之行。正是因?yàn)檫@個(gè)原因,通常這個(gè)地址空間我們會(huì)安排給FLASH存儲(chǔ)器。這樣我們可以將BootLoader啟動(dòng)代碼以及我們之后將會(huì)要啟動(dòng)的uClinux操作系統(tǒng)映像燒寫到Flash里。對(duì)于RAM地址空間,S3C44B0芯片將其設(shè)定為從0x0C000000到0x0FFFFFFF一共64MB的范圍里。我們可以通過設(shè)定存儲(chǔ)器控制寄存器來重新設(shè)定RAM的大小。例如我們?cè)囼?yàn)采用的存儲(chǔ)設(shè)備安排如下:
0x00000000 – 0x003FFFFF 4MB Flash
0x0C000000 – 0x0C7FFFFF 8MB RAM
通常來說對(duì)于系統(tǒng)的引導(dǎo)和操作系統(tǒng)的啟動(dòng),可以完全都在Flash中進(jìn)行,但是Flash存儲(chǔ)器的速度相對(duì)于RAM來說會(huì)慢很多,因此出于速度上的考慮,我們通常會(huì)將啟動(dòng)代碼和uClinux操作系統(tǒng)的內(nèi)核映像文件拷貝到RAM中之行。
下面我將對(duì)典型的BootLoader程序框架進(jìn)行分析。
2.3 Stage1階段
該階段的主要工作是完成對(duì)系統(tǒng)中斷向量的設(shè)置,初始化微處理器內(nèi)部寄存器,初始化堆棧,初始化RAM地址空間,并且將Stage2部分的C代碼拷貝到RAM空間的指定地點(diǎn),然后跳轉(zhuǎn)到C代碼入口點(diǎn)繼續(xù)執(zhí)行。對(duì)于這段代碼來說,做的都是一些準(zhǔn)備工作,因此為了提高效率,這段代碼通常都是使用匯編語言來完成的。下面我將結(jié)合具體的代碼來分析一下Stage1的啟動(dòng)過程。
1)設(shè)置中斷向量
設(shè)置S3C44B0處理器定義的8種系統(tǒng)中斷的中斷向量地址。這八種系統(tǒng)中斷分別是復(fù)位中斷、未定義指令中斷、軟件中斷、指令預(yù)取異常中斷、數(shù)據(jù)異常中斷、地址異常中斷、IRQ中斷和FIQ中斷。這8個(gè)中斷通常是通過無條件跳轉(zhuǎn)的方式來實(shí)現(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)部寄存器
這段代碼主要是要完成硬件部分的初始化,包括關(guān)閉中斷響應(yīng)、初始化微處理器通用端口、設(shè)置CPU頻率等操作。不過需要注意的是,在進(jìn)行硬件初始化之前需要將微處理器的運(yùn)行狀態(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空間
這個(gè)部分的工作主要是為之后啟動(dòng)代碼和內(nèi)核映像的拷貝操作做準(zhǔn)備,并且也為之后的C代碼的執(zhí)行初始化堆棧。這部分的工作主要可以分成兩個(gè)部分來處理。首先,根據(jù)系統(tǒng)配置的存儲(chǔ)器特性來初始化相關(guān)的存儲(chǔ)器控制寄存器。在我們使用的S3C44B0處理器中,存儲(chǔ)空間被分成了BANK0-BANK7一共8個(gè)塊,分別由BANKCON0-BANKCON7控制各個(gè)塊存儲(chǔ)器的讀寫時(shí)鐘和片選時(shí)鐘等信號(hào)參數(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空間的第二個(gè)部分就是初始化連接腳本文件中指定的需要清0的地址空間,將該斷地址空間的內(nèi)容清0。該部分地址空間主要是用來存放C語言代碼中的全局變量等內(nèi)容的。實(shí)現(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í)行準(zhǔn)備必要的堆棧
因?yàn)樵赟tage2階段一般都是采用C語言代碼來完成的,因此必須在使用C語言代碼之前先建立起必要的堆棧信息。通常為了避免堆棧數(shù)據(jù)被執(zhí)行代碼破壞,通常都是放在RAM的高端地址,并且使得堆棧指針的增長方向是向下增長的。
5)將初始化代碼拷貝到RAM中,并且跳轉(zhuǎn)到RAM中執(zhí)行。因?yàn)樵谖覀儾捎玫腟3C44B0微處理器里對(duì)于FLASH和RAM地址空間是使用的統(tǒng)一編址的,因此我們可以直接使用一個(gè)簡單循環(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階段)
這個(gè)過程是直接給指令指針賦值于跳轉(zhuǎn)的C代碼的入口地址,在我們的試驗(yàn)中該入口地址是Main。
LDR pc,=Main
2.4 Stage2階段
該階段的代碼主要使用C語言來實(shí)現(xiàn)的。該階段的工作主要是建立開發(fā)板與宿主機(jī)之間的通信,加載uClinux內(nèi)核映像文件和配置內(nèi)核啟動(dòng)參數(shù),并且啟動(dòng)內(nèi)核。
嵌入式設(shè)備與宿主機(jī)的通訊方式有多種,最常用的是使用串口方式進(jìn)行數(shù)據(jù)交換。本試驗(yàn)采用的S3C44B0微處理器提供了兩個(gè)UART口,因此我們可以任選其中一個(gè)來初始化并且使用它來與宿主機(jī)交互。對(duì)于串口的初始化主要是波特率、奇偶校驗(yàn)、停止位、數(shù)據(jù)位等內(nèi)容。
對(duì)于串口的波特率和波特因子的計(jì)算采用如下公式
Iubrd =((int(mclk/16 / baud + 0.5) – 1)
mclk是頻率、baud為波特率
2.4.1 檢測內(nèi)存
該部分的功能主要是檢測系統(tǒng)在進(jìn)行硬件初始化的時(shí)候是否發(fā)生了內(nèi)存映射錯(cuò)誤,即是否物理地址是否被映射到不存在的地址空間。通常是使用讀寫方式來檢測的,即以內(nèi)存頁為單位,在每個(gè)頁頭進(jìn)行讀寫操作,比較讀寫結(jié)果。因?yàn)镾3C44B0處理器并不支持內(nèi)存映射,因此我們?cè)赟tage2過程中并沒有包含該部分功能函數(shù)。
2.4.2 加載uClinux內(nèi)核映像
該過程其實(shí)只是一個(gè)從Flash的指定位置(該位置是uClinux燒寫的起始地址)拷貝到RAM中指定的地址空間里。在拷貝之前必須要為uClinux的全局變量結(jié)構(gòu),即啟動(dòng)參數(shù)、內(nèi)核頁表、RAM的頁目錄等信息預(yù)留一定的空間。如果我們將FLASH和RAM看成連在一起的線性地址,則系統(tǒng)的空間分配會(huì)如下圖:
。..。..
Boot
初始化代碼
uClinux
未用
中斷向量表
初始化映像代碼
啟動(dòng)參數(shù)
內(nèi)核映像
未用
堆棧
2.4.3 配置內(nèi)核啟動(dòng)參數(shù)
我們采用的uClinux是2.4.x內(nèi)核版本,該版本的內(nèi)核支持參數(shù)啟動(dòng)過程。在嵌入式系統(tǒng)中,啟動(dòng)參數(shù)的傳入主要是依靠bootloader程序向標(biāo)記列表(tagged list)的相關(guān)域中填寫相應(yīng)的值來完成的。
2.5 uClinux內(nèi)核引導(dǎo)
當(dāng)我們初始化完畢uClinux的啟動(dòng)參數(shù)之后,控制權(quán)就可以交給uClinux內(nèi)核了,uClinux系統(tǒng)調(diào)用內(nèi)核解壓函數(shù)(decompress_kernel)來對(duì)上一個(gè)階段拷貝的uClinux內(nèi)核在RAM空間里進(jìn)行解壓(當(dāng)然如果系統(tǒng)內(nèi)核在建立的時(shí)候沒有配置成壓縮格式,則解壓過程略去)。在解壓完畢后,跳轉(zhuǎn)到內(nèi)核調(diào)用函數(shù)(call_kernel),該函數(shù)實(shí)際上執(zhí)行的是start_kernel(),這個(gè)函數(shù)包含了有關(guān)處理器初始化、中斷初始化、進(jìn)程初始化等操作。最后,將控制權(quán)完全的交與uClinux操作系統(tǒng)來執(zhí)行。
偽處理過程如下:
IF(啟動(dòng)參數(shù)正確)
CALL decmporess_kernel()
CALL call_kernel()
ELSE
啟動(dòng)失敗
decompress_kernel()
{
解壓內(nèi)核映像
}
call_kernel()
{
。..
start_kernel()
。..。
}
3 總結(jié)
本文是對(duì)S3C44B0的啟動(dòng)過程進(jìn)行了一次分析,啟動(dòng)部分的代碼可以說是嵌入式設(shè)備開發(fā)比較重要的部分。而且該部分的處理工作往往又比較麻煩,因此在這里我只是想起到拋磚引玉的作用。因?yàn)槌晌臅r(shí)間比較倉促,難免有錯(cuò)誤,請(qǐng)大家批評(píng)指正。
==========================================================
《ARM7 uClinux開發(fā)實(shí)驗(yàn)與實(shí)踐》P130
Bootloader完成系統(tǒng)初始化工作后,將運(yùn)行控制權(quán)交給uClinux內(nèi)核。根據(jù)內(nèi)核是否壓縮以及內(nèi)核是否在本地執(zhí)行,uClinux通常有以下兩種可選的啟動(dòng)方式:
?。?)Flash本地運(yùn)行方式。內(nèi)核中未經(jīng)壓縮的可執(zhí)行映像固化在Flash中,系統(tǒng)啟動(dòng)時(shí),內(nèi)核在Flash中開始逐句執(zhí)行。
?。?)壓縮內(nèi)核加載方式。內(nèi)核的壓縮映像固化在Flash上,系統(tǒng)啟動(dòng)時(shí),由附加在壓縮映像前的解壓復(fù)制程序讀取壓縮映像,并在內(nèi)存中解壓后執(zhí)行。這種方式相對(duì)復(fù)雜,但是運(yùn)行速度更快。
首先介紹內(nèi)核的Flash本地運(yùn)行方式。
本地運(yùn)行時(shí),內(nèi)核的啟動(dòng)包括特定體系結(jié)構(gòu)設(shè)置和uClinux系統(tǒng)初始化兩步,內(nèi)核啟動(dòng)的入口文件是head-armv.s。
評(píng)論
查看更多