uclinux表示micro-control linux.即“微控制器領(lǐng)域中的Linux系統(tǒng)”,是Lineo公司的主打產(chǎn)品,同時(shí)也是開放源碼的嵌入式Linux的典范之作。uCLinux主要是針對目標(biāo)處理器沒有存儲管理單元MMU(Memory Management Unit)的嵌入式系統(tǒng)而設(shè)計(jì)的。它已經(jīng)被成功地移植到了很多平臺上。由于沒有MMU,其多任務(wù)的實(shí)現(xiàn)需要一定技巧。
uClinux啟動過程
uCinux的啟動主要經(jīng)歷三個(gè)階段。首先,必須完成CPU和存儲器的硬件初始化,在系統(tǒng)RAM中建立程序堆棧和數(shù)據(jù)段,建立程序的運(yùn)行時(shí)的環(huán)境。初始化完成之后,uClinux內(nèi)核就取得了CPU的控制權(quán),開始操作系統(tǒng)自身的初始化,這包括建立RAM中斷矢量表、加載設(shè)備驅(qū)動程序、內(nèi)存管理模塊等等。這一切完成后,uClinux啟動一個(gè)最初的init線程,進(jìn)入到第三階段,這時(shí)內(nèi)核已經(jīng)正常運(yùn)行,外圍模塊也都就緒,開始執(zhí)行一些腳本文件(如/etc/rc腳本文件)。
一.kernel代碼段之前的系統(tǒng)初始化
1. uClinux-dist/linux-2.4.x/arch/armnommu/boot/compressed/head.S
開發(fā)板從上電開始,最開始執(zhí)行的程序放在uClinux-dist/linux-2.4.x/arch/armnommu/boot/compressed/head.S中。
(1) 切換模式,關(guān)閉中斷。 (line 96 )
(2) 首先程序要先給SYSCFG,EXTDBWTH,ROMCON0等一系列系統(tǒng)控制寄存器賦值,此時(shí)flash地址在 0X0,DRAM地址在0X1000000.(line 141 )
(3) 點(diǎn)亮I/O口的指示燈。 (line 152 )
(4) 把在flash上的image復(fù)制到DRAM上。(line 161 )
(5) 執(zhí)行remap,把flash地址映射為0X1000000,DRAM地址映射為0.(line 172 )
(6) 打開cache和write buffer.(line 196 )
(7) 設(shè)置好64K堆棧。(line 204 )
(8) 跳轉(zhuǎn)到decompress_kernel函數(shù)(line 217 ),此處的跳轉(zhuǎn)為帶返回的跳轉(zhuǎn),以便于執(zhí)行完此函數(shù)跳轉(zhuǎn)回來。
2. uClinux-dist/linux-2.4.x/arch/armnommu/boot/compressed/misc.c
此時(shí)的函數(shù)decompress_kernel是用C語言寫的,line 297 。
(1) makecrc();進(jìn)行crc校驗(yàn)。
(2) puts(“Uncompressing Linux.。.”); 輸出linux起動后的第一句話。
(3) gunzip();解壓縮kernel.
(4) puts(“ done, booting the kernel./n”);
3. uClinux-dist/linux-2.4.x/arch/armnommu/boot/compressed/head.S
執(zhí)行完decompress_kernel函數(shù)后,kernel又跳轉(zhuǎn)回head.S中,因?yàn)榇藭r(shí)我們還要檢驗(yàn)解壓縮之后的kernel起始地址是否緊接著kernel image,如果是,beq call_kernel(line 220),執(zhí)行解壓后的kernel.
如果解壓縮之后的kernel起始地址不是緊接著kernel image,執(zhí)行relocate(line 236),將其拷貝到緊接著kernel image的地方,然后跳轉(zhuǎn),執(zhí)行解壓后的kernel.
二.kernel執(zhí)行
1.uClinux-dist/linux-2.4.x/init/main.c中的start_kernel() (line 352)
系統(tǒng)啟動過程到此,轉(zhuǎn)入體系結(jié)構(gòu)無關(guān)的通用C代碼中,start_kernel() 中調(diào)用了一系列初始化函數(shù),以完成kernel本身的設(shè)置。這些動作有的是公共的,有的則是需要配置的才會執(zhí)行的。
(1) 輸出Linux版本信息(printk(linux_banner))
(2) 設(shè)置與體系結(jié)構(gòu)相關(guān)的環(huán)境(setup_arch())
(3) parse_options(command_line);解析command_line,將其轉(zhuǎn)化為環(huán)境變量。
(4) 初始化系統(tǒng)IRQ(init_IRQ())
(5) 核心進(jìn)程調(diào)度器初始化(sched_init())
(6) 軟中段初始化softirq_init();
(7) 時(shí)間、定時(shí)器初始化(包括估測主頻、初始化定時(shí)器中斷等,time_init())
(8) 控制臺初始化console_init();
(9) 核心CACHE初始化kmem_cache_init();
(10)延遲校準(zhǔn)calibrate_delay();
(11)內(nèi)存初始化(設(shè)置內(nèi)存上下界和頁表項(xiàng)初始值,mem_init())
(12)文件,目錄,塊設(shè)備讀寫緩沖區(qū)初始化
(13)檢查體系結(jié)構(gòu)漏洞(check_bugs())
(14)啟動init過程(創(chuàng)建第一個(gè)核心線程,調(diào)用init()函數(shù),原執(zhí)行序列調(diào)用cpu_idle() 等待調(diào)度,init())
至此start_kernel()結(jié)束,基本的核心環(huán)境已經(jīng)建立起來了。
2.uClinux-dist/linux-2.4.x/init/main.c中的init() (line 548)
現(xiàn)在我們進(jìn)入內(nèi)核引導(dǎo)第二部分,init()函數(shù)作為核心線程,首先鎖定內(nèi)核(僅對SMP機(jī)器有效,我們?yōu)榭蘸瘮?shù)),然后調(diào)用 do_basic_setup() (line 551)完成外設(shè)及其驅(qū)動程序的加載初始化。
過程如下:
* 網(wǎng)絡(luò)初始化(初始化網(wǎng)絡(luò)數(shù)據(jù)結(jié)構(gòu),包括sk_init()、skb_init()和proto_init()三部分,在proto_init()中,將調(diào)用protocols結(jié)構(gòu)中包含的所有協(xié)議的初始化過程,sock_init())
* 創(chuàng)建事件管理核心線程(start_context_thread()函數(shù),這是系統(tǒng)創(chuàng)建的第二個(gè)內(nèi)核線程,名叫“keventd”。其代碼context_thread()也在kernel/context.c中,)
啟動任何使用__initcall標(biāo)識的函數(shù)(方便核心開發(fā)者添加啟動函數(shù),此時(shí)由do_initcalls()函數(shù)啟動)。
此時(shí)系統(tǒng)開始加載外部設(shè)備的初始化程序,如:在linux-2.4.x/driver/block/genhd.c中的device_init()函數(shù),在genhd.c中由__initcall(device_init)標(biāo)識在此時(shí)調(diào)用,device_init()函數(shù)是所有外部設(shè)備初始化的總?cè)肟冢藟K設(shè)備的初始化blk_dev_init,網(wǎng)絡(luò)設(shè)備的初始化net_dev_init()和atmdev_init()等。
至此do_basic_setup()函數(shù)返回init(),在釋放啟動內(nèi)存段(free_initmem())并給內(nèi)核解鎖以后,init()打開/dev/console設(shè)備,重定向stdin、stdout和stderr到控制臺,最后,搜索文件系統(tǒng)中的init程序(或者由init=命令行參數(shù)指定的程序),并使用 execve()系統(tǒng)調(diào)用加載執(zhí)行init程序。(line 576) 。
init()函數(shù)到此結(jié)束,內(nèi)核的引導(dǎo)部分也到此結(jié)束了,
3. uClinux-dist/linux-2.4.x/init/main.c中的execve(“/etc/init”,argv_init,envp_init); (line 579)
init進(jìn)程是系統(tǒng)所有進(jìn)程的起點(diǎn),內(nèi)核在完成核內(nèi)引導(dǎo)以后,即在本線程(進(jìn)程)空間內(nèi)加載init程序,它的進(jìn)程號是1。
init程序需要讀取/vendors/SAMSUNG/4510B/inittab文件作為其行為指針,然后執(zhí)行。
4.系統(tǒng)執(zhí)行rc腳本。
hostname Samsung
/bin/expand /etc/ramfs.img /dev/ram0
/bin/expand /etc/ramfs2048.img /dev/ram1
mount -t proc proc /proc
mount -t ext2 /dev/ram0 /var
mount -t ext2 /dev/ram1 /ramdisk
chmod 777 /ramdisk
mkdir /var/config
mkdir /var/tmp
mkdir /var/log
mkdir /var/run
mkdir /var/lock
ifconfig lo 127.0.0.1
route add -net 127.0.0.0 netmask 255.255.255.0 lo
dhcpcd &
cat /etc/motd
rc程序執(zhí)行完畢后,系統(tǒng)環(huán)境已經(jīng)設(shè)置好了,下面就該用戶登錄系統(tǒng)了。
5.運(yùn)行Sash command shell
uclinux啟動的詳細(xì)過程有著諸多的信息可以給我們巨大的啟發(fā),我們在這里討論的就是要對這些信息做一個(gè)具體細(xì)致的分析,通過我們的討論,大家會對uclinux啟動過程中出現(xiàn)的、以前感覺熟悉的、但卻又似是而非的東西有一個(gè)確切的了解,并且能了解到這些輸出信息的來龍去脈。
uclinux的啟動過程,它是一幅縮影圖,對它有了一個(gè)詳細(xì)的了解后,有助于指導(dǎo)我們更加深入地了解uclinux的核心。
大家對uclinux的啟動應(yīng)該都比較熟悉,作為一名嵌入系統(tǒng)開發(fā)者,你一定遇到過下面的情景:在某論壇上看到一篇帖子,上面貼著uclinux開發(fā)板啟動時(shí)的一堆信息,然后大家在帖子里討論著這個(gè)啟動過程中出現(xiàn)的問題,隨機(jī)舉例如下:
Linux version 2.4.20-uc1 (root@Local) (gcc version 2.95.3
20010315 (release)(ColdFire patches - 20010318 from http://f
(uClinux XIP and shared lib patches from http://www.snapgear.com/)) #20 三 6月 1
8 00:58:31 CST 2003
Processor: Samsung S3C4510B revision 6
Architecture: SNDS100
On node 0 totalpages: 4096
zone(0): 0 pages.
zone(1): 4096 pages.
zone(2): 0 pages.
Kernel command line: root=/dev/rom0
Calibrating delay loop.。. 49.76 BogoMIPS
Memory: 16MB = 16MB total
Memory: 14348KB available (1615K code, 156K data, 40K init)
Dentry cache hash table entries: 2048 (order: 2, 16384 bytes)
Inode cache hash table entries: 1024 (order: 1,
Mount-cache hash table entries: 512 (order: 0, 4096 bytes)
Buffer-cache hash table entries: 1024 (order: 0, 4096 bytes)
Page-cache hash table entries: 4096 (order: 2, 16384 bytes)
POSIX conformance testing by UNIFIX
Linux NET4.0 for Linux 2.4
Based upon Swansea University Computer Society NET3.039
Initializing RT netlink socket
Starting kswapd
Samsung S3C4510 Serial driver version 0.9 (2001-12-27) with no serial options en
abled
ttyS00 at 0x3ffd000 (irq = 5) is a S3C4510B
ttyS01 at 0x3ffe000 (irq = 7) is a S3C451
Blkmem copyright 1998,1999 D. Jeff Dionne
Blkmem copyright 1998 Kenneth Albanowski
Blkmem 1 disk images:
0: BE558-1A5D57 [VIRTUAL BE558-1A5D57] (RO)
RAMDISK driver initialized: 16 RAM disks of 1024K size 1024 blocksize
Samsung S3C4510 Ethernet driver version 0.1 (2002-02-20) 《mac@os.nctu.edu.tw》
eth0: 00:40:95:36:35:34
NET4: Linux TCP/IP 1.0 for NET4.0
IP Protocols: ICMP, UDP, TCP
IP: routing cache hash table of 512 buckets, 4Kbytes
TCP: Hash tables configured (established 1024 bind 1024)
VFS: Mounted root (romfs
Freeing init memory: 40K
上面的這些輸出信息,也可能包括你自己正在做的uclinux開發(fā)板的輸出信息,其中的每一行,每一個(gè)字的含義,你是否深究過,或者說大部分的含義你能確切地知道的?本人想在這里結(jié)合本人在實(shí)踐中一些體會來和廣大uclinux的開發(fā)者一起讀懂這些信息。
我們在這里將以一個(gè)真實(shí)的uclinux系統(tǒng)的啟動過程為例,來分析這些輸出信息。啟動信息的原始內(nèi)容將用標(biāo)記標(biāo)出,以區(qū)別與注釋。
uclinux的啟動主要分為兩個(gè)階段:
① 第一部分bootloader啟動階段
② ② 第二部分linux 內(nèi)核初始化和啟動階段
第一節(jié):start_kernel
第二節(jié):用戶模式( user_mode )開始,start_kernel結(jié)束
第三節(jié):加載linux內(nèi)核完畢,轉(zhuǎn)入cpu_idle進(jìn)程
第一部分 : bootloader啟動
圖 1:uclinux啟動狀態(tài)轉(zhuǎn)移示意圖
*****************************************************
Boot loader v0.12NOTE: this boot loader is designed to boot kernels made with the2.4.xx releasesbootloader for XVBuilt at Nov 20 2005 10:12:35
Bootloader頭信息,版本,編譯時(shí)間等,這個(gè)因不同的bootloader的設(shè)計(jì)而有所不同,由此你能看出bootloader的版本信息,有很多使用的是通用的bootloader,如u-boot,redboot等。
Loaded to 0x90060000
將bootloader加載到內(nèi)存ram中的0x90060000處,即將bootloader加載到內(nèi)存的高端地址處。
Linux內(nèi)核將被bootloader加載到0x90090000處。
Found boot configuration
查找到了啟動boot的配置信息。
Booted from parallel flash
從flash中啟動代碼,此處的flash為并行閃存。
注意:任何flash器件的寫入操作只能在空或已擦除的單元內(nèi)進(jìn)行,所以大多數(shù)情況下,在進(jìn)行寫入操作之前必須先執(zhí)行擦除。NAND器件執(zhí)行擦除操作是十分簡單的,而NOR則要求在進(jìn)行擦除前先要將目標(biāo)塊內(nèi)所有的位都寫為0。
從上面的信息,我們可以對flash類型特點(diǎn)有個(gè)比較明確的了解。
CPU clock rate: 200 MHz
開發(fā)板上所使用的CPU的主頻為200MHZ。
DRAM size is 128MB (128MB/0MB)
動態(tài)內(nèi)存ram大小為128M。
在嵌入式系統(tǒng)中使用DRAM內(nèi)存的設(shè)計(jì)比較廣泛。
在uclinux的系統(tǒng)中,系統(tǒng)運(yùn)行時(shí)間較長后,會出現(xiàn)內(nèi)存碎片的問題,導(dǎo)致再分配大塊內(nèi)存時(shí)會失敗。這是在uclinux系統(tǒng)中經(jīng)常遇到的問題,解決的辦法通常有使用靜態(tài)內(nèi)存、應(yīng)用程序啟動時(shí)預(yù)先分配大內(nèi)存、使用內(nèi)存池等。
地址輔助說明:
先說明一下內(nèi)存地址數(shù)字情況,主要是為了方便記憶。
可以訪問的內(nèi)存為4G。0x40000000是1GB處;0x00040000是256K處,0x00020000是128K處,0x90000000是2GB多的地方。1M-》0x00100000, 2M-》0x00200000,8M-》0x00800000,16M-》0x01000000, 32M-》0x02000000,256M-》0x10000000,64K-》0x00010000,注意:rootfs并不是一個(gè)具體的文件系統(tǒng)類型,如jffs。它只是一個(gè)理論上的概念。在具體的嵌入系統(tǒng)實(shí)例中,可以將某種具體的文件系統(tǒng)設(shè)置為根文件系統(tǒng)rootfs,如我們可以設(shè)置romfs為根文件系統(tǒng),也可以設(shè)置jffs為根文件系統(tǒng)。
這里的ROMFS只讀文件系統(tǒng)只是一種具體的文件系統(tǒng)類型,也是在嵌入系統(tǒng)中經(jīng)常使用到的類型。
看完了上面的內(nèi)容,以后你對出現(xiàn)的類似“kernel Panic:VFS:Unable to mount root fs on 0:00”的含義應(yīng)該已經(jīng)了解了。其中“VFS:”就是虛擬文件系統(tǒng)管理器操作時(shí)的輸出信息了。
File linux.bin.gz found
linux kernel內(nèi)核文件名,它是在只讀文件系統(tǒng)romfs上的一個(gè)組成部分。
Unzipping image from 0x4639DE60 to 0x90090000, size = 1316021
將romfs中的linux kernel解壓縮到0x90090000,之后會從這個(gè)內(nèi)存地址啟動內(nèi)核。romfs為壓縮格式文件,使用壓縮的只讀文件系統(tǒng),是為了保持制作出來的整 個(gè)系統(tǒng)所占用的flash空間減小。這個(gè)內(nèi)核的大小為1.3M左右,這也是目前大多數(shù)嵌入系統(tǒng)所使用的方法。
Inptr = 0x00000014(20)Inflating……
釋放……
Outcnt = 0x0030e7c8(3205064)Final Inptr = 0x001414ad(1316013)Original CRC = 0xcbd73adbComputed CRC = 0xcbd73adb
做釋放后的CRC檢查。
Boot kernel at 0x90090000 with ROMFS at 0x46040000
kernel已經(jīng)被從romfs中釋放到內(nèi)存地址0x90090000處,可以跳轉(zhuǎn)到此處啟動kernel了,這里是指定的kernel的起始地址。
Press ‘enter’ to boot
系統(tǒng)等待啟動,后面將看到linux kernel的啟動過程了。
4K-》0x00001000這個(gè)是個(gè)快速記憶的方法,你可以根據(jù)地址中1的位置和其后0的個(gè)數(shù)來快速知道換算后的地址是在多少兆的地方。比如,1的后面5個(gè)0,代表1M的大小,6個(gè)0,代表16M,以此類推。
ROMFS found at 0x46040000, Volume name = rom 43f291aa
romfs,只讀文件系統(tǒng)所在的地址為:0x46040000 (flash映射后的第3分區(qū))。卷名為rom。
romfs 和rootfs概念上有所區(qū)別。flash在內(nèi)存中的的起始地址為0x46000000,而ROMFS在flash分區(qū)上的起始位置為0x00040000,所以ROMFS在內(nèi)存地址中的位置就為0x46040000。這個(gè)細(xì)節(jié)的部分可以參考flash分區(qū)時(shí)的地方,Creating 3 MTD partitions。
romfs中包括kernel和app應(yīng)用,不包括bootloader和firmware信息頭。romfs只讀文件系統(tǒng)里的內(nèi)容有很多種分類方法,我們可以將kernel和app同時(shí)放里面,作為根文件系統(tǒng)下的一個(gè)文件,也可以在flash上另外劃分區(qū)域來分別存放。
*****************************************************************************
第一節(jié):start_kernel
Linux的源代碼可以從 www.kernel.org 得到,或者你可以查看linux代碼交叉引用網(wǎng)站:http://lxr.linux.no/ 進(jìn)行在線的代碼查看,這是一個(gè)很好的工具網(wǎng)站。
在start_kernel中將調(diào)用到大量的init函數(shù),來完成內(nèi)核的各種初始化。如:
圖 2:kernel start up初始化過程
具體內(nèi)容可以參考[http://lxr.linux.no/source/init/main.c]
Linux version 2.4.22-uc0 (root@local) (gcc version 2.95.3 20010315 (release)) #33 。?1…… 20 12:09:106
上面的代碼輸出信息,是跟蹤linux代碼分析后得到的,進(jìn)入init目錄下的main.c的start_kernel啟動函數(shù)。
uclinux使用的是linux內(nèi)核版本為2.4.22。linux source code代碼中start_kernel中輸出的linux_banner信息。這個(gè)信息是每個(gè)linux kernel都會打印一下的信息,如果你沒有把這句去掉的話。
Found bootloader memory map at 0x10000fc0.
bootloader經(jīng)過內(nèi)存映射后的地址為:0x10000fc0, 按上面的地址換算方法,1后面有7個(gè)0,那么虛擬地址256M左右處。
Processor: ARM pt110 revision 0
pT110是ARM微處理器arm核的一種,另一種為pT100。此處為顯示ARM的類型。
On node 0 totalpages: 20480
zone(0): 20480 pages.
zone(0): Set minimum memory threshold to 12288KB
Warning: wrong zone alignment (0x90080000, 0x0000000c, 0x00001000)
zone(1): 0 pages.
zone(2): 0 pages.
預(yù)留內(nèi)存大小,在節(jié)點(diǎn)0上總共20頁, zone(0) 設(shè)置最小內(nèi)存為12MB, zone(1)和zone(2)為0頁。警告:對齊不正確。
Kernel command line: root=/dev/mtdblock3
Kernel 啟動命令設(shè)為:/dev/mtdblock3(在后面的說明中會看到mtdblock3是指的flash上的romfs分區(qū)。),用來指定根文件系統(tǒng)所在的位置,kernel會將塊設(shè)備mtdblock3當(dāng)作文件系統(tǒng)來處理。也就是說,內(nèi)核會根據(jù)上面的kernel命令行,知道只讀文件系統(tǒng)romfs將是 根文件系統(tǒng)rootfs。
start_kernel(void) 中輸出的上面的這句信息。這行命令是在linux內(nèi)核啟動過程中都會輸出的一句。
Console: colour dummy device 80x30
代碼中console_init()的輸出信息, 顯示控制臺屬性:一般使用VGA text console,標(biāo)準(zhǔn)是80 X 25行列的文本控制臺,這里是對屬性進(jìn)行了設(shè)置。
serial_xx: setup_console @ 115
串口設(shè)置值為115200,此為波特率輸出信息。對串口設(shè)置的信息做一個(gè)打印的動作,在調(diào)試時(shí)會非常有用。
Calibrating delay loop…… 82.94 BogoMIPS Calibrate:校準(zhǔn), 進(jìn)入時(shí)延校準(zhǔn)循環(huán)。檢查CPU的MIPS(每秒百萬條指令),Bogo是Bogus(偽)的意思。這里是對CPU進(jìn)行一個(gè)實(shí)時(shí)測試,來得到一個(gè)大體的MIPS數(shù)值。
上面這個(gè)輸出,在所有的linux系統(tǒng)啟動中都會打印出來。
進(jìn)入內(nèi)存初始化:mem_init(void), [arch/i386/mm/init.c]
Memory: 80MB = 80MB totalMemory: 76592KB available (1724K code, 2565K data, 72K init)
當(dāng)前內(nèi)存使用情況,將列出總的內(nèi)存大小, 及分配給內(nèi)核的內(nèi)存大小:包括代碼部分,數(shù)據(jù)部分,初始化部分,總共剛好4M。請留意此處的內(nèi)核的內(nèi)存大小的各個(gè)值。
進(jìn)入虛擬文件系統(tǒng)VFS初始化:vfs_caches_init()
Dentry cache hash table entries: 16384 (order: 5, 131072 bytes)
Inode cache hash table entries: 8192 (order: 4, 65536 bytes)
Mount cache hash table entries: 512 (order: 0, 4096 bytes)
Buffer cache hash table entries: 4096 (order: 2, 16384 bytes)
Page-cache hash table entries: 32768 (order: 5, 131072 bytes)
在內(nèi)存中建立各個(gè)緩沖hash表,為kernel對文件系統(tǒng)的訪問做準(zhǔn)備。
VFS(virtual filesystem switch)虛擬文件切換目錄樹有用到類似這樣的結(jié)構(gòu)表。
上面的輸出信息,在一般的linux啟動過程中都會看到。
POSIX conformance testing by UNIFIX
conformance:順應(yīng), 一致。即POSIX適應(yīng)性檢測。UNIFIX是一家德國的技術(shù)公司,Linux 原本要基于 POSIX.1 的, 但是POSIX 不是免費(fèi)的, 而且 POSIX.1 證書相當(dāng)昂貴。 這使得 Linux 基于 POSIX 開發(fā)相當(dāng)困難。 Unifix公司(Braunschweig, 德國) 開發(fā)了一個(gè)獲得了 FIPS 151-2 證書的 Linux 系統(tǒng)。 這種技術(shù)用于 Unifix 的發(fā)行版 Unifix Linux 2.0 和 Lasermoon的 Linux-FT。
在2.6的內(nèi)核中就將上面的這句輸出給拿掉了。
*************************************************************************
第二節(jié):用戶模式( user_mode )開始,start_kernel結(jié)束
圖 3:用戶模式初始化
PCI: bus0: Fast back to back transfers disabled
PCI: Configured XX as a PCI slave with 128MB PCI memory
PCI: Each Region size is 16384KB
PCI: Reserved memory from 0x10080000 to 0x15080000 for DMA and mapped to 0x12000000
設(shè)備的初始化 init()——》do_basic_init()——》pci_init(),初始化PCI,檢測系統(tǒng)的PCI設(shè)備。
Linux NET4.0 for Linux 2.4Based upon Swansea University Computer Society NET3.039
英國威爾士,斯旺西大學(xué)的NET3.039, TCP/IP 協(xié)議棧。
此信息,在linux啟動過程中都會出現(xiàn)。
Initializing RT netlink socket
對Socket的初始化, socket_init(),Netlink 一種路由器管理協(xié)議(linux-2.4.22/net/core/Rtnetlink.c,Routing netlink socket interface: protocol independent part。 其中RT是route路由的意思。這句輸出是在create產(chǎn)生rtnetlink的socket套接字時(shí)的一個(gè)調(diào)試輸出。)
此信息,在linux啟動過程中都會出現(xiàn)。
Starting kswapd
啟動交換守護(hù)進(jìn)程kswapd,進(jìn)程IO操作例程kpiod。
kswapd 可以配合kpiod運(yùn)行。進(jìn)程有時(shí)候無事可做,當(dāng)它運(yùn)行時(shí)也不一定需要把其所有的代碼和數(shù)據(jù)都放在內(nèi)存中。這就意味著我們可以通過把運(yùn)行中程序不用的內(nèi)容切換到交換分區(qū)來更好的是利用內(nèi)存。大約每隔1秒,kswapd醒來并檢查內(nèi)存情況。如果在硬盤的東西要讀入內(nèi)存,或者內(nèi)存可用空間不足,kpiod就會 被調(diào)用來做移入/移出操作。kswapd負(fù)責(zé)檢查,kpiod負(fù)責(zé)移動。
Journalled Block Device driver loaded
加載日志塊設(shè)備驅(qū)動。
日志塊設(shè)備是用來對文件系統(tǒng)進(jìn)行日志記錄的一個(gè)塊設(shè)備。日志文件系統(tǒng)是在傳統(tǒng)文件系統(tǒng)的基礎(chǔ)上,加入文件系統(tǒng)更改的日志記錄。
它 的設(shè)計(jì)思想是:跟蹤記錄文件系統(tǒng)的變化,并將變化內(nèi)容記錄入日志。日志文件系統(tǒng)在磁盤分區(qū)中保存有日志記錄,寫操作首先是對記錄文件進(jìn)行操作,若整個(gè)寫操作由于某種原因(如系統(tǒng)掉電)而中斷,系統(tǒng)重啟時(shí),會根據(jù)日志記錄來恢復(fù)中斷前的寫操作。在日志文件系統(tǒng)中,所有的文件系統(tǒng)的變化都被記錄到日志,每隔一定時(shí)間,文件系統(tǒng)會將更新后的元數(shù)據(jù)及文件內(nèi)容寫入磁盤。在對元數(shù)據(jù)做任何改變以前,文件系統(tǒng)驅(qū)動程序會向日志中寫入一個(gè)條目,這個(gè)條目描述了它將要做些 什么,然后它修改元數(shù)據(jù)。
devfs: v1.12c (20020818) Richard Gooch (rgooch@atnf.csiro.au)devfs: boot_options: 0x1
Devfs模塊的輸出信息。設(shè)備文件系統(tǒng)devfs,版本1.12c
pty: 256 Unix98 ptys configured
Pty模塊的輸出信息,與控制臺操作有關(guān)的設(shè)置。
將通過 devpts 文件系統(tǒng)使用 Unix98 PTYs,(Pseudo-ttys (telnet etc) device是偽ttys設(shè)備的縮寫。
① TTY(/dev/tty)是TeleTYpe的一個(gè)老縮寫,為用戶輸入提供不同控制臺的設(shè)備驅(qū)動程序。它的名字來源于實(shí)際掛接到UNIX系統(tǒng)的、被稱為電傳打字機(jī)(teletype)的終端。在Linux下,這些文件提供對虛擬控制臺的支持,可以通過按<Alt-F1>到<Alt -F6>鍵來訪問這些虛擬控制臺。這些虛擬控制臺提供獨(dú)立的、同時(shí)進(jìn)行的本地登錄對話過程② ttys(/dev/ttys)是計(jì)算機(jī)終端的串行接口。/dev/ttyS0對應(yīng)MS-DOS下的 COM1。
使 用 make dev腳本MAKEDEV來建立pty文件。這樣系統(tǒng)內(nèi)核就支持Unix98風(fēng)格的pty了。在進(jìn)行Telnet登錄時(shí)將要用到/dev/pty設(shè)備。 pty是偽終端設(shè)備,在遠(yuǎn)程登錄等需要以終端方式進(jìn)行連接,但又并非真實(shí)終端的應(yīng)用程序中必須使用這種設(shè)備,如telnet或xterm等程序。 Linux 2.2以后增添了UNIX98風(fēng)格的Pty設(shè)備,它使用一個(gè)新的文件系統(tǒng)(devpts針對偽終端的文件系統(tǒng))和一個(gè)克隆的設(shè)備cloning device來實(shí)現(xiàn)其功能。
linux-2.4.22/drivers/char/Pty.c, 在devfs_mk_dir (NULL, “pts”, NULL);時(shí)會輸出上面的信息。
loop: loaded (max 8 devices)
加載返還塊設(shè)備驅(qū)動,最多支持8個(gè)設(shè)備。
8139too Fast Ethernet driver 0.9.27
eth0: RealTek RTL8139 at 0x60112000, 00:10:0d:42:a0:03, IRQ 14
eth0: Identified 8139 chip type ‘RTL-8100B/8139D’
網(wǎng)卡驅(qū)動,基地址為:0x60112000, MAC地址:00:10:0d:42:a0:03, 中斷號:14
從 2.2 版內(nèi)核升級到 2.4 版時(shí), RTL-8139 支持模塊已不再叫 rtl8139,而叫它 8139too,現(xiàn)在你再看到8139too就不會不明白它的來由了吧。
SCSI subsystem driver Revision: 1.00
USB設(shè)備信息,USB會被當(dāng)做SCSI來處理。
mumk_register_tasklet: (1) tasklet 0x905bf9c0 status @0x9025e974
軟中斷信息輸出。Tasklet是在2.4中才出現(xiàn),它是為了更好地利用多CPU。
Probing XX Flash Memory
探測 XX的閃存(Flash Memory),“NOR NAND Flash Memory Technology”。
Amd/Fujitsu Extended Query Table v1.3 at 0x0040
number of CFI chips: 1
*************************************************************************
AMD與富士通合資設(shè)立的Flash供貨 商Spansion。AMD因獲利不佳,已經(jīng)退出Flash市場,后續(xù)由Spansion合資公司經(jīng)營。主要生產(chǎn)NOR類型的flash,特點(diǎn)是容量小,速度快。Spansion商標(biāo)的flash,在我們開發(fā)中會經(jīng)常看到。以后大家看到Spansion的芯片,就能了解到它和AMD還有富士通的來龍去脈 了。
Common flash Interface (CFI)是指一個(gè)統(tǒng)一的flash訪問接口,表示這種flash是這種接口類型的。
Using buffer write method
使用flash寫緩沖方式。
flash 提供了寫B(tài)UFFER的命令來加快對flash上塊的操作。對Flash擦除和寫數(shù)據(jù)是很慢的。如果用寫B(tài)UFFER的命令會快一點(diǎn)。據(jù)手冊上說,會快 20倍。Buffer Size :5 bytes的buffer緩沖不是每個(gè)塊都有,是整個(gè)flash只有一個(gè)5 bytes的buffer,用寫B(tài)UFFER命令對所有的塊進(jìn)行寫操作,都要用同一個(gè)buffer,寫B(tài)uffer是主要檢查buffer是否 available,其實(shí)buffer起緩沖作用,來提高工作效率。
比如某flash有128個(gè)128K字節(jié)塊。允許用戶對任意塊進(jìn)行字節(jié)編程和寫緩沖器字節(jié)編程操作,每字節(jié)編程時(shí)間為210μs;若采用寫緩沖器字節(jié)編程方式,32字節(jié)編程共需218μs,每字 節(jié)編程時(shí)間僅為6.8μs。芯片的塊擦除時(shí)間為1s,允許在編程或塊擦除操作的同時(shí)進(jìn)行懸掛中斷去進(jìn)行讀操作,待讀操作完成后,寫入懸掛恢復(fù)命令,再繼續(xù)編程或塊擦除。
Creating 3 MTD partitions on “XX mapped flash”:
0x00000000-0x00020000 : “BootLoader”
0x00020000-0x00040000 : “Config”
0x00040000-0x01000000 : “Romfs”
此處為重要信息部分,需要特別留意。在內(nèi)存中映射過的flash,創(chuàng)建三個(gè)MTD分區(qū):
flash上的內(nèi)容將被映射到內(nèi)存中的對應(yīng)地址
前128K為BootLoader——》0x00000000-0x00020000接著的128K為系統(tǒng)配置信息Config存放的位置——》0x00020000-0x00040000再后面的 16M - 2X128K 為romfs的存放處。——》0x00040000-0x01000000上面的內(nèi)容,大家可以根據(jù)前面的換算公式得到。
A》 編譯的bootloader一般大小約50K左右;
B》 在此處就知道了配置信息config是放在第2分區(qū)中的;
C》 制作的romfs的大小,一般為8M或10M左右,所以能放得下;
NET4: Linux TCP/IP 1.0 for NET4.0
調(diào)用inet_init [ linux-2.4.22/net/ipv4/Af_inet.c ]時(shí)的輸出信息, 在啟動過程中被socket.c調(diào)用到。
IP Protocols: ICMP, UDP, TCP, IGMP
列出可以支持的IP協(xié)議,此處為kernel源代碼inet_add_protocol(p);的輸出。在linux啟動過程中,都會看到這句的輸出。
IP: routing cache hash table of 512 buckets, 4Kbytes
IP路由代碼的輸出信息。
ip_rt_init [ linux-2.4.22/net/ipv4\Route.c ],設(shè)置 IP module,路由緩沖hash表
TCP: Hash tables configured (established 8192 bind 8192)
TCP協(xié)議初始化輸出信息。tcp_init [ linux-2.4.22/net/ipv4/Tcp.c ],
NET4: Unix domain sockets 1.0/SMP for Linux NET4.0.
UNIX網(wǎng)絡(luò)協(xié)議信息。
af_unix_init[ linux-2.4.22/net/unix/Af_unix.c ], 多種連接的一種(IPv4, UNIX domain sockets, IPv6和IrDA)。 SMP 對稱多處理器—Symmetrical Multi Processing,這里主要是指UNIX的一些網(wǎng)絡(luò)協(xié)議。
上面的關(guān)于網(wǎng)絡(luò)的輸出信息是在linux啟動信息中都會出現(xiàn)的。
cramfs: wrong magic
加載各種文件系統(tǒng)。
會出現(xiàn)“cramfs: wrong magic”,別擔(dān)心這沒有什么害處,這個(gè)是kernel的書寫bug,在2.6中有修改之,它是一個(gè)警告信息,用來檢查cramfs的superblock超級塊的。superblock也是VFS要用到的數(shù)據(jù)結(jié)構(gòu)。
代碼linux-2.4.22/fs/cramfs/Inode.c:
2.4
cramfs_read_super(。。。)
/* Do sanity checks on the superblock */
if (super.magic != CRAMFS_MAGIC) {
/* check at 512 byte offset */
memcpy(&super, cramfs_read(sb, 512, sizeof(super)), sizeof(super));
if (super.magic != CRAMFS_MAGIC) {
printk(KERN_ERR “cramfs: wrong magic/n”);
goto out;
}
}
2.6
if (super.magic != CRAMFS_MAGIC) {
if (!silent)
printk(KERN_ERR “cramfs: wrong magic/n”);
goto out;
}
超級塊是文件系統(tǒng)的“頭部”。它包含文件 系統(tǒng)的狀態(tài)、尺寸和空閑磁盤塊等信息。如果損壞了一個(gè)文件系統(tǒng)的超級塊(例如不小心直接將數(shù)據(jù)寫到了文件系統(tǒng)的超級塊分區(qū)中),那么系統(tǒng)可能會完全不識別該文件系統(tǒng),這樣也就不能安裝它了,即使采用e2fsck 命令也不能處理這個(gè)問題。
**************************************************************************
RamDisk有三種實(shí)現(xiàn)方式。
在Linux中可以將一部分內(nèi)存mount為分區(qū)來使用,通常稱之為RamDisk,分為:
Ramdisk, ramfs, tmpfs。
① 第一種就是傳統(tǒng)意義上的,可以格式化,然后加載。這在Linux內(nèi)核2.0/2.2就已經(jīng)支持,其不足之處是大小固定,之后不能改變。為了能夠使用 Ramdisk,我們在編譯內(nèi)核時(shí)須將block device中的Ramdisk支持選上,它下面還有兩個(gè)選項(xiàng),一個(gè)是設(shè)定Ramdisk的大小,默認(rèn)是4096k;另一個(gè)是initrd的支持。
如果對Ramdisk的支持已經(jīng)編譯進(jìn)內(nèi)核,我們就可以使用它了:首先查看一下可用的RamDisk,使用 ls /dev/ram*;首先創(chuàng)建一個(gè)目錄,比如test,運(yùn)行 mkdir /mnt/test;然后對/dev/ram0 創(chuàng)建文件系統(tǒng),運(yùn)行 mke2fs /dev/ram0;最后掛載/dev/ram0,運(yùn)行mount /dev/ram /mnt/test,就可以象對普通硬盤一樣對它進(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,也不同于針對物理內(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)請求頁來存儲文件,它同Linux的其它請求頁的部分一樣,不知道分配給自己的頁是在內(nèi)存中還是在交換分區(qū)中。同Ramfs一樣,其大小也不是固定的,而是隨著所需要的空間而動態(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)容量大 但相對而言不大穩(wěn)定。是Linux 使用的擴(kuò)展了的DOS文件系統(tǒng)。它在 DOS 文件系統(tǒng)下增加了長文件名、 UID/GID、POSIX 權(quán)限和特殊文件 (設(shè)備、命名管道等)功能,而不犧牲對 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會先在內(nèi)存中建立一顆 VFS 目錄樹,是內(nèi)存中的一個(gè)數(shù)據(jù)對象,然后在其下掛載rootfs文件系統(tǒng),還可以掛載其他類型的文件系統(tǒng)到某個(gè)子目錄上。
Mounted devfs on /dev
加載devfs設(shè)備管理文件系統(tǒng)到dev安裝點(diǎn)上。/dev是我們經(jīng)常會用到的一個(gè)目錄。在2.4的kernel中才有使用到。每次啟動時(shí)內(nèi)核會自動掛載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)啟動時(shí)、或是加載模組后驅(qū)動程序載入時(shí)建立的。當(dāng)模組和驅(qū)動程序卸載時(shí),文件就消失了。
Freeing init memory: 72K
釋放1號用戶進(jìn)程init所占用的內(nèi)存
*************************************************************
第三節(jié):加載linux內(nèi)核完畢,轉(zhuǎn)入cpu_idle進(jìn)程
系統(tǒng)啟動過程中進(jìn)程情況:
① init進(jìn)程
一 般來說, 系統(tǒng)在跑完 kernel bootstrapping 內(nèi)核引導(dǎo)自舉后(被裝入內(nèi)存、已經(jīng)開始運(yùn)行、已經(jīng)初始化了所有的設(shè)備驅(qū)動程序和數(shù)據(jù)結(jié)構(gòu)等等), 就去運(yùn)行 init『萬process之父』, 有了它, 才能開始跑其他的進(jìn)程,因此,init進(jìn)程,它是內(nèi)核啟動的第一個(gè)用戶級進(jìn)程,它的進(jìn)程號總是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ù),比如象啟動getty(用于用戶登錄)、實(shí)現(xiàn)運(yùn)行級別、以及處理孤立進(jìn)程。init 一開始就去讀 /etc/inittab (init初始化表),初始化表是按一定格式排列的關(guān)于進(jìn)程運(yùn)行時(shí)的有關(guān)信息的。init程序需要讀取/etc/inittab文件作為其行為指針。這個(gè) inittab 中對于各個(gè)runlevel運(yùn)行級別要跑哪些 rc 或 spawn 生出什么有很清楚的設(shè)定。
一 般, 在Linux中初始化腳本在/etc/inittab 文件(或稱初始化表)中可以找到關(guān)于不同運(yùn)行級別的描述。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》 請注意init是1號進(jìn)程,其他進(jìn)程id分別是kflushd/ bdflush, kupdate, kpiod and kswapd。這里有一個(gè)要指出的:你會注意到虛擬占用(SIZE)和實(shí)際占用(RSS)列都是0,進(jìn)程怎么會不使用內(nèi)存呢?
這些進(jìn)程就是內(nèi)核守護(hù)進(jìn)程。大部分內(nèi)核并不顯示在進(jìn)程列表里。守護(hù)進(jìn)程在init之后啟動,所以他們和其他進(jìn)程一樣有進(jìn)程ID,但是他們的代碼和數(shù)據(jù)都存放在內(nèi)核占有的內(nèi)存中。在列表中使用中括號來區(qū)別與其他進(jìn)程。
B》 輸入和輸出是通過內(nèi)存中的緩沖來完成的,這讓事情變得更快,程序的寫入會存放在內(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就會被調(diào)用來做移入/移出操作。
D》 bdflush - BUF_DIRTY, 將dirty緩存寫回到磁盤的核心守護(hù)進(jìn)程。對于有許多臟的緩沖區(qū)(包含必須同時(shí)寫到磁盤的數(shù)據(jù)的緩沖區(qū))的系統(tǒng)提供了動態(tài)的響應(yīng)。它在系統(tǒng)啟動的時(shí)候作為一個(gè)核心線程啟動,它叫自己為 “kflushd”,而這是你用ps顯示系統(tǒng)中的進(jìn)程的時(shí)候你會看得的名字。即定期(5秒)將臟(dirty)緩沖區(qū)的內(nèi)容寫入磁盤,以騰出內(nèi)存;
E》 ksoftirqd_CPUx 是一個(gè)死循環(huán), 負(fù)責(zé)處理軟中斷的。它是用來對軟中斷隊(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有動態(tài)插拔時(shí),將交由此內(nèi)核進(jìn)程來處理。在檢測到有hub事件時(shí)會有相應(yīng)的動作(usb_hub_events())
H》 mtdblockd是用來對flash塊設(shè)備進(jìn)行寫操作的守護(hù)進(jìn)程。NAND類型的Flash需要MTD(Memory Technology Devices 內(nèi)存技術(shù)驅(qū)動程序)驅(qū)動的支持才能被linux所使用。NAND的特點(diǎn)是不能在芯片內(nèi)執(zhí)行(XIP,eXecute In Place),需要把代碼讀到系統(tǒng)RAM中再執(zhí)行,傳輸效率不是最高,最大擦寫次數(shù)量為一百萬次,但寫入和擦除的速度很快,擦除單元小,是高數(shù)據(jù)存儲密度 的最佳選擇。NAND需要I/O接口,因此使用時(shí)需要驅(qū)動程序。
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é)對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的啟動過程而做的一個(gè)總結(jié)性的文章。在對uclinux的啟動過程做了一個(gè)詳細(xì)注釋后,大家 會對涉及到嵌入系統(tǒng)的各個(gè)概念有了一個(gè)更加明確的認(rèn)識,并能對嵌入系統(tǒng)的軟硬件環(huán)境的有關(guān)設(shè)置更加清楚。當(dāng)你自己動手結(jié)合linux源代碼來分析時(shí),將會有一個(gè)清楚的全局觀。
=============================================================
1. 運(yùn)行bootloader初始化程序
SRAM 、SDRAM等存儲設(shè)備屬于揮發(fā)性的存儲器,掉電以后其中的內(nèi)容就會全部丟失,所以必須把操作系統(tǒng)的內(nèi)核鏡像存放在Flash等不揮發(fā)性存儲介質(zhì)上。但是操作系統(tǒng)在運(yùn)行時(shí),需要動態(tài)的創(chuàng)建一些如數(shù)據(jù)段、堆棧、頁表(針對使用虛擬地址的操作系統(tǒng))等內(nèi)容,所以需要在RAM中運(yùn)行操作系統(tǒng)。
因此,就需要一個(gè)引導(dǎo)程序把操作系統(tǒng)的內(nèi)核鏡像從Flash存儲器拷貝到RAM中,然后再從RAM中執(zhí)行操作系統(tǒng)的內(nèi)核。Bootloader就是可以完成這樣一種功能的程序。
從本質(zhì)上來講,bootloader不屬于操作系統(tǒng)內(nèi)核。它采用匯編語言編寫,因此針對不同的CPU體系結(jié)構(gòu),這一部分代碼不具有可移植性。在移植操作系統(tǒng)時(shí),這部分代碼必須加以改寫
具體來講,bootloader在系統(tǒng)啟動時(shí)主要完成以下幾項(xiàng)工作:
(1) 將操作系統(tǒng)內(nèi)核從Flash拷貝到SDRAM中,如果是壓縮格式的內(nèi)核,還要將之解壓縮。
(2) 改寫系統(tǒng)的memory map,原先flash起始地址映射為0地址,這時(shí)需要將RAM的起始地址映射為0。
(3) 設(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)核啟動之后需要執(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(),控制臺初始化。
◆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)程,啟動init進(jìn)程。
◆cpu_idle(),運(yùn)行idle進(jìn)程
接下去做的工作由init()函數(shù)來完成。init()首先要鎖定內(nèi)核,然后調(diào)用do_basic_setup( )來完成外部設(shè)備以及驅(qū)動程序的初始化。外設(shè)的初始化要根據(jù)內(nèi)核的配置來決定,一般需要做下面的初始化工作:
◆PCI總線初始化。
◆網(wǎng)絡(luò)初始化。
◆一系列其他設(shè)備的初始化。
◆start_context_thread()創(chuàng)建事件管理核心進(jìn)程keventd。
◆通過do_initcalls()函數(shù)來啟動任何使用__initcall標(biāo)識的函數(shù)。
◆文件系統(tǒng)初始化。
◆加載文件系統(tǒng)。
在do_basic_setup()調(diào)用完成之后,init()會釋放初始化函數(shù)所用的內(nèi)存,并且打開/dev/console設(shè)備重新定向控制臺,讓系統(tǒng)調(diào)用execve來執(zhí)行程序init。
到這里為止,Linux 內(nèi)核的初始化工作已經(jīng)完成。然后開始用戶態(tài)進(jìn)程的初始化。
=============================================================
uClinux中內(nèi)存模塊的啟動初始化
arch/armnommu/kernel/entry_armv.S是一個(gè)匯編文件,他包含了一個(gè)kernel_entry的定義,這是整個(gè)內(nèi)核的進(jìn)入點(diǎn)。在完成某些平臺相關(guān)的初始化工作之后,執(zhí)行流程跳轉(zhuǎn)到start_kerne()處。從這里開始考察uClinux的內(nèi)存模塊啟動初始化是如何實(shí)現(xiàn)的。
start_kernel()中與內(nèi)存模塊相關(guān)的函數(shù)調(diào)用流程如下:
setup_arch() paging_init() free_area_init() mem_init()
下面分別分析這些函數(shù)各自的功能以及uClinux對他們的改造。
(1) setup_arch()
setup_arch()首先根據(jù)目前內(nèi)核所配置的平臺向某些特定地址寫入特殊字符序列,以完成對特定硬件的初始化,比如工作狀態(tài)發(fā)光二極管、錯(cuò)誤和報(bào)警發(fā)光二極管。
存儲了可用物理內(nèi)存起始地址的變量memory_start的初始化是通過一個(gè)ld腳本中定義的變量_end進(jìn)行的。ld腳本是用來控制GNU ld連接器在連接內(nèi)核各個(gè)目標(biāo)文件部分的時(shí)候的配置動作,比如這樣一個(gè)腳本:
SECTION
{
。=0x10000;
.text:{*(.text)}
。=0x8000000;
.data:{*(.data)}
.bss :{*(.bss)}
}
用來配置ld連接目標(biāo)文件的時(shí)候?qū)⑺械哪繕?biāo)文件中的存儲程序正文的.text段(section)連接到一起,并且映射到輸出文件的地址0x10000處,將所有目標(biāo)文件中已初始化的數(shù)據(jù).data段連接到一起并放置到輸出可執(zhí)行文件的0x8000000地址處,而所有目標(biāo)文件中還未初始化的數(shù)據(jù)段.bss連接起來后影射到輸出文件中緊跟在.data段之后的位置。
這個(gè)ld配置腳本文件對每個(gè)平臺都是不同的。如為MICETEK上所使用的uClinux版本使用的ld配置文件為arch/armnomm/vmLinux.lds。可以通過修改某個(gè)平臺上的ld腳本配置文件中的_end變量來達(dá)到配置其可用物理內(nèi)存起始地址的目的。
setup_arch()在完成對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ù)所用平臺進(jìn)行對flash memory和ROM的測試。在這些平臺相關(guān)的工作完成之后,setup_arch()將對系統(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è)備號(可以從Document/devices.txt中得到)來創(chuàng)建根文件系統(tǒng)的設(shè)備號,并存儲在后來將要用到的全局變量ROOT_DEV中。
setup_arch()最后完成對系統(tǒng)啟動參數(shù)的保存。
在調(diào)用setup_arch()返回之后,start_kernel()中得到了系統(tǒng)可用物理內(nèi)存的起始和結(jié)束地址,以及命令啟動時(shí)的命令行參數(shù)。
(2) paging_init()
在Linux中,paging_init()的一項(xiàng)主要功能是建立頁目錄和頁表,而且將Linux移植到不同平臺的過程中非常重要的一個(gè)步驟就是修改這個(gè)函數(shù)來適應(yīng)新的硬件平臺的虛擬內(nèi)存體系。但是由于在uClinux中不再使用虛擬內(nèi)存機(jī)制,也就不再需要維護(hù)頁目錄和頁表數(shù)據(jù)結(jié)構(gòu)了,所以paging_init()在這里只是為系統(tǒ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)系,以及建立空閑物理頁面鏈表的操作。
(3)free_area_init()
這個(gè)函數(shù)用于建立管理物理頁幀的數(shù)據(jù)結(jié)構(gòu)mem_map,有多少物理頁幀就有多少mem_map_t類型的結(jié)構(gòu)體與之相對應(yīng)。每個(gè)頁面的mem_map_t結(jié)構(gòu)中的flags被標(biāo)明為PG_DMA和PG_reserved,并且頁幀號被賦給相應(yīng)的數(shù)值。同時(shí)建立了管理空閑頁面的bitmap映射表,并且所有的位都被清零。
(3) mem_init()
mem_init()函數(shù)遍歷整個(gè)可用物理內(nèi)存地址空間,將每個(gè)頁面相對應(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微處理器,對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)橛兄男 ⒊杀镜偷忍攸c(diǎn),因此非常適合作為移動手持終端的處理器核心。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)可以對S3C44B0微處理器有很好的支持。
在嵌入式系統(tǒng)開發(fā)中,第一個(gè)部分便是系統(tǒng)的引導(dǎo)。而系統(tǒng)的引導(dǎo)過程是通過BootLoader來完成的。BootLoader程序是與硬件緊密相關(guān)的一段代碼,而且編寫的時(shí)候比較復(fù)雜,它主要的功能是初始化微處理器以及周邊的硬件資源,并且引導(dǎo)操作系統(tǒng)的啟動。下面我將以S3C44B0微處理器來作為例子,對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ù)雜的功能,而且代碼會具有更好的可讀性和可移植性。
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è)置啟動參數(shù)
5) 調(diào)用內(nèi)核
2.2 系統(tǒng)內(nèi)存組織
由于嵌入式設(shè)備具有很好的制定性,因此通常硬件環(huán)境會變的千差萬別。就算是用戶使用了相同的處理器芯片,但是也很有可能因?yàn)橥鈬O(shè)備電路設(shè)計(jì)的不同,而存在差異。對于BootLoader程序來說,存儲設(shè)備的與處理器的連接方式,與其息息相關(guān)。對于我們采用的S3C44B0微處理器來說,在系統(tǒng)加電之后,指令指針是指向0x00000000的,也就是說系統(tǒng)是從0x00000000開始之行。正是因?yàn)檫@個(gè)原因,通常這個(gè)地址空間我們會安排給FLASH存儲器。這樣我們可以將BootLoader啟動代碼以及我們之后將會要啟動的uClinux操作系統(tǒng)映像燒寫到Flash里。對于RAM地址空間,S3C44B0芯片將其設(shè)定為從0x0C000000到0x0FFFFFFF一共64MB的范圍里。我們可以通過設(shè)定存儲器控制寄存器來重新設(shè)定RAM的大小。例如我們試驗(yàn)采用的存儲設(shè)備安排如下:
0x00000000 – 0x003FFFFF 4MB Flash
0x0C000000 – 0x0C7FFFFF 8MB RAM
通常來說對于系統(tǒng)的引導(dǎo)和操作系統(tǒng)的啟動,可以完全都在Flash中進(jìn)行,但是Flash存儲器的速度相對于RAM來說會慢很多,因此出于速度上的考慮,我們通常會將啟動代碼和uClinux操作系統(tǒng)的內(nèi)核映像文件拷貝到RAM中之行。
下面我將對典型的BootLoader程序框架進(jìn)行分析。
2.3 Stage1階段
該階段的主要工作是完成對系統(tǒng)中斷向量的設(shè)置,初始化微處理器內(nèi)部寄存器,初始化堆棧,初始化RAM地址空間,并且將Stage2部分的C代碼拷貝到RAM空間的指定地點(diǎn),然后跳轉(zhuǎn)到C代碼入口點(diǎn)繼續(xù)執(zhí)行。對于這段代碼來說,做的都是一些準(zhǔn)備工作,因此為了提高效率,這段代碼通常都是使用匯編語言來完成的。下面我將結(jié)合具體的代碼來分析一下Stage1的啟動過程。
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è)部分的工作主要是為之后啟動代碼和內(nèi)核映像的拷貝操作做準(zhǔn)備,并且也為之后的C代碼的執(zhí)行初始化堆棧。這部分的工作主要可以分成兩個(gè)部分來處理。首先,根據(jù)系統(tǒng)配置的存儲器特性來初始化相關(guān)的存儲器控制寄存器。在我們使用的S3C44B0處理器中,存儲空間被分成了BANK0-BANK7一共8個(gè)塊,分別由BANKCON0-BANKCON7控制各個(gè)塊存儲器的讀寫時(shí)鐘和片選時(shí)鐘等信號參數(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微處理器里對于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)核啟動參數(shù),并且啟動內(nèi)核。
嵌入式設(shè)備與宿主機(jī)的通訊方式有多種,最常用的是使用串口方式進(jìn)行數(shù)據(jù)交換。本試驗(yàn)采用的S3C44B0微處理器提供了兩個(gè)UART口,因此我們可以任選其中一個(gè)來初始化并且使用它來與宿主機(jī)交互。對于串口的初始化主要是波特率、奇偶校驗(yàn)、停止位、數(shù)據(jù)位等內(nèi)容。
對于串口的波特率和波特因子的計(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)存映射,因此我們在Stage2過程中并沒有包含該部分功能函數(shù)。
2.4.2 加載uClinux內(nèi)核映像
該過程其實(shí)只是一個(gè)從Flash的指定位置(該位置是uClinux燒寫的起始地址)拷貝到RAM中指定的地址空間里。在拷貝之前必須要為uClinux的全局變量結(jié)構(gòu),即啟動參數(shù)、內(nèi)核頁表、RAM的頁目錄等信息預(yù)留一定的空間。如果我們將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程序向標(biāo)記列表(tagged list)的相關(guān)域中填寫相應(yīng)的值來完成的。
2.5 uClinux內(nèi)核引導(dǎo)
當(dāng)我們初始化完畢uClinux的啟動參數(shù)之后,控制權(quán)就可以交給uClinux內(nèi)核了,uClinux系統(tǒng)調(diào)用內(nèi)核解壓函數(shù)(decompress_kernel)來對上一個(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(啟動參數(shù)正確)
CALL decmporess_kernel()
CALL call_kernel()
ELSE
啟動失敗
decompress_kernel()
{
解壓內(nèi)核映像
}
call_kernel()
{
。..
start_kernel()
。..。
}
3 總結(jié)
本文是對S3C44B0的啟動過程進(jìn)行了一次分析,啟動部分的代碼可以說是嵌入式設(shè)備開發(fā)比較重要的部分。而且該部分的處理工作往往又比較麻煩,因此在這里我只是想起到拋磚引玉的作用。因?yàn)槌晌臅r(shí)間比較倉促,難免有錯(cuò)誤,請大家批評指正。
==========================================================
《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通常有以下兩種可選的啟動方式:
(1)Flash本地運(yùn)行方式。內(nèi)核中未經(jīng)壓縮的可執(zhí)行映像固化在Flash中,系統(tǒng)啟動時(shí),內(nèi)核在Flash中開始逐句執(zhí)行。
(2)壓縮內(nèi)核加載方式。內(nèi)核的壓縮映像固化在Flash上,系統(tǒng)啟動時(shí),由附加在壓縮映像前的解壓復(fù)制程序讀取壓縮映像,并在內(nèi)存中解壓后執(zhí)行。這種方式相對復(fù)雜,但是運(yùn)行速度更快。
首先介紹內(nèi)核的Flash本地運(yùn)行方式。
本地運(yùn)行時(shí),內(nèi)核的啟動包括特定體系結(jié)構(gòu)設(shè)置和uClinux系統(tǒng)初始化兩步,內(nèi)核啟動的入口文件是head-armv.s。
評論
查看更多