uclinux表示micro-control linux.即“微控制器領域中的Linux系統”,是Lineo公司的主打產品,同時也是開放源碼的嵌入式Linux的典范之作。uCLinux主要是針對目標處理器沒有存儲管理單元MMU(Memory Management Unit)的嵌入式系統而設計的。它已經被成功地移植到了很多平臺上。由于沒有MMU,其多任務的實現需要一定技巧。
uClinux啟動過程
uCinux的啟動主要經歷三個階段。首先,必須完成CPU和存儲器的硬件初始化,在系統RAM中建立程序堆棧和數據段,建立程序的運行時的環境。初始化完成之后,uClinux內核就取得了CPU的控制權,開始操作系統自身的初始化,這包括建立RAM中斷矢量表、加載設備驅動程序、內存管理模塊等等。這一切完成后,uClinux啟動一個最初的init線程,進入到第三階段,這時內核已經正常運行,外圍模塊也都就緒,開始執行一些腳本文件(如/etc/rc腳本文件)。
一.kernel代碼段之前的系統初始化
1. uClinux-dist/linux-2.4.x/arch/armnommu/boot/compressed/head.S
開發板從上電開始,最開始執行的程序放在uClinux-dist/linux-2.4.x/arch/armnommu/boot/compressed/head.S中。
(1) 切換模式,關閉中斷。 (line 96 )
(2) 首先程序要先給SYSCFG,EXTDBWTH,ROMCON0等一系列系統控制寄存器賦值,此時flash地址在 0X0,DRAM地址在0X1000000.(line 141 )
(3) 點亮I/O口的指示燈。 (line 152 )
(4) 把在flash上的image復制到DRAM上。(line 161 )
(5) 執行remap,把flash地址映射為0X1000000,DRAM地址映射為0.(line 172 )
(6) 打開cache和write buffer.(line 196 )
(7) 設置好64K堆棧。(line 204 )
(8) 跳轉到decompress_kernel函數(line 217 ),此處的跳轉為帶返回的跳轉,以便于執行完此函數跳轉回來。
2. uClinux-dist/linux-2.4.x/arch/armnommu/boot/compressed/misc.c
此時的函數decompress_kernel是用C語言寫的,line 297 。
(1) makecrc();進行crc校驗。
(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
執行完decompress_kernel函數后,kernel又跳轉回head.S中,因為此時我們還要檢驗解壓縮之后的kernel起始地址是否緊接著kernel image,如果是,beq call_kernel(line 220),執行解壓后的kernel.
如果解壓縮之后的kernel起始地址不是緊接著kernel image,執行relocate(line 236),將其拷貝到緊接著kernel image的地方,然后跳轉,執行解壓后的kernel.
二.kernel執行
1.uClinux-dist/linux-2.4.x/init/main.c中的start_kernel() (line 352)
系統啟動過程到此,轉入體系結構無關的通用C代碼中,start_kernel() 中調用了一系列初始化函數,以完成kernel本身的設置。這些動作有的是公共的,有的則是需要配置的才會執行的。
(1) 輸出Linux版本信息(printk(linux_banner))
(2) 設置與體系結構相關的環境(setup_arch())
(3) parse_options(command_line);解析command_line,將其轉化為環境變量。
(4) 初始化系統IRQ(init_IRQ())
(5) 核心進程調度器初始化(sched_init())
(6) 軟中段初始化softirq_init();
(7) 時間、定時器初始化(包括估測主頻、初始化定時器中斷等,time_init())
(8) 控制臺初始化console_init();
(9) 核心CACHE初始化kmem_cache_init();
(10)延遲校準calibrate_delay();
(11)內存初始化(設置內存上下界和頁表項初始值,mem_init())
(12)文件,目錄,塊設備讀寫緩沖區初始化
(13)檢查體系結構漏洞(check_bugs())
(14)啟動init過程(創建第一個核心線程,調用init()函數,原執行序列調用cpu_idle() 等待調度,init())
至此start_kernel()結束,基本的核心環境已經建立起來了。
2.uClinux-dist/linux-2.4.x/init/main.c中的init() (line 548)
現在我們進入內核引導第二部分,init()函數作為核心線程,首先鎖定內核(僅對SMP機器有效,我們為空函數),然后調用 do_basic_setup() (line 551)完成外設及其驅動程序的加載初始化。
過程如下:
* 網絡初始化(初始化網絡數據結構,包括sk_init()、skb_init()和proto_init()三部分,在proto_init()中,將調用protocols結構中包含的所有協議的初始化過程,sock_init())
* 創建事件管理核心線程(start_context_thread()函數,這是系統創建的第二個內核線程,名叫“keventd”。其代碼context_thread()也在kernel/context.c中,)
啟動任何使用__initcall標識的函數(方便核心開發者添加啟動函數,此時由do_initcalls()函數啟動)。
此時系統開始加載外部設備的初始化程序,如:在linux-2.4.x/driver/block/genhd.c中的device_init()函數,在genhd.c中由__initcall(device_init)標識在此時調用,device_init()函數是所有外部設備初始化的總入口,包括了塊設備的初始化blk_dev_init,網絡設備的初始化net_dev_init()和atmdev_init()等。
至此do_basic_setup()函數返回init(),在釋放啟動內存段(free_initmem())并給內核解鎖以后,init()打開/dev/console設備,重定向stdin、stdout和stderr到控制臺,最后,搜索文件系統中的init程序(或者由init=命令行參數指定的程序),并使用 execve()系統調用加載執行init程序。(line 576) 。
init()函數到此結束,內核的引導部分也到此結束了,
3. uClinux-dist/linux-2.4.x/init/main.c中的execve(“/etc/init”,argv_init,envp_init); (line 579)
init進程是系統所有進程的起點,內核在完成核內引導以后,即在本線程(進程)空間內加載init程序,它的進程號是1。
init程序需要讀取/vendors/SAMSUNG/4510B/inittab文件作為其行為指針,然后執行。
4.系統執行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程序執行完畢后,系統環境已經設置好了,下面就該用戶登錄系統了。
5.運行Sash command shell
uclinux啟動的詳細過程有著諸多的信息可以給我們巨大的啟發,我們在這里討論的就是要對這些信息做一個具體細致的分析,通過我們的討論,大家會對uclinux啟動過程中出現的、以前感覺熟悉的、但卻又似是而非的東西有一個確切的了解,并且能了解到這些輸出信息的來龍去脈。
uclinux的啟動過程,它是一幅縮影圖,對它有了一個詳細的了解后,有助于指導我們更加深入地了解uclinux的核心。
大家對uclinux的啟動應該都比較熟悉,作為一名嵌入系統開發者,你一定遇到過下面的情景:在某論壇上看到一篇帖子,上面貼著uclinux開發板啟動時的一堆信息,然后大家在帖子里討論著這個啟動過程中出現的問題,隨機舉例如下:
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開發板的輸出信息,其中的每一行,每一個字的含義,你是否深究過,或者說大部分的含義你能確切地知道的?本人想在這里結合本人在實踐中一些體會來和廣大uclinux的開發者一起讀懂這些信息。
我們在這里將以一個真實的uclinux系統的啟動過程為例,來分析這些輸出信息。啟動信息的原始內容將用標記標出,以區別與注釋。
uclinux的啟動主要分為兩個階段:
① 第一部分bootloader啟動階段
② ② 第二部分linux 內核初始化和啟動階段
第一節:start_kernel
第二節:用戶模式( user_mode )開始,start_kernel結束
第三節:加載linux內核完畢,轉入cpu_idle進程
第一部分 : bootloader啟動
圖 1:uclinux啟動狀態轉移示意圖
*****************************************************
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頭信息,版本,編譯時間等,這個因不同的bootloader的設計而有所不同,由此你能看出bootloader的版本信息,有很多使用的是通用的bootloader,如u-boot,redboot等。
Loaded to 0x90060000
將bootloader加載到內存ram中的0x90060000處,即將bootloader加載到內存的高端地址處。
Linux內核將被bootloader加載到0x90090000處。
Found boot configuration
查找到了啟動boot的配置信息。
Booted from parallel flash
從flash中啟動代碼,此處的flash為并行閃存。
注意:任何flash器件的寫入操作只能在空或已擦除的單元內進行,所以大多數情況下,在進行寫入操作之前必須先執行擦除。NAND器件執行擦除操作是十分簡單的,而NOR則要求在進行擦除前先要將目標塊內所有的位都寫為0。
從上面的信息,我們可以對flash類型特點有個比較明確的了解。
CPU clock rate: 200 MHz
開發板上所使用的CPU的主頻為200MHZ。
DRAM size is 128MB (128MB/0MB)
動態內存ram大小為128M。
在嵌入式系統中使用DRAM內存的設計比較廣泛。
在uclinux的系統中,系統運行時間較長后,會出現內存碎片的問題,導致再分配大塊內存時會失敗。這是在uclinux系統中經常遇到的問題,解決的辦法通常有使用靜態內存、應用程序啟動時預先分配大內存、使用內存池等。
地址輔助說明:
先說明一下內存地址數字情況,主要是為了方便記憶。
可以訪問的內存為4G。0x40000000是1GB處;0x00040000是256K處,0x00020000是128K處,0x90000000是2GB多的地方。1M-》0x00100000, 2M-》0x00200000,8M-》0x00800000,16M-》0x01000000, 32M-》0x02000000,256M-》0x10000000,64K-》0x00010000,注意:rootfs并不是一個具體的文件系統類型,如jffs。它只是一個理論上的概念。在具體的嵌入系統實例中,可以將某種具體的文件系統設置為根文件系統rootfs,如我們可以設置romfs為根文件系統,也可以設置jffs為根文件系統。
這里的ROMFS只讀文件系統只是一種具體的文件系統類型,也是在嵌入系統中經常使用到的類型。
看完了上面的內容,以后你對出現的類似“kernel Panic:VFS:Unable to mount root fs on 0:00”的含義應該已經了解了。其中“VFS:”就是虛擬文件系統管理器操作時的輸出信息了。
File linux.bin.gz found
linux kernel內核文件名,它是在只讀文件系統romfs上的一個組成部分。
Unzipping image from 0x4639DE60 to 0x90090000, size = 1316021
將romfs中的linux kernel解壓縮到0x90090000,之后會從這個內存地址啟動內核。romfs為壓縮格式文件,使用壓縮的只讀文件系統,是為了保持制作出來的整 個系統所占用的flash空間減小。這個內核的大小為1.3M左右,這也是目前大多數嵌入系統所使用的方法。
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已經被從romfs中釋放到內存地址0x90090000處,可以跳轉到此處啟動kernel了,這里是指定的kernel的起始地址。
Press ‘enter’ to boot
系統等待啟動,后面將看到linux kernel的啟動過程了。
4K-》0x00001000這個是個快速記憶的方法,你可以根據地址中1的位置和其后0的個數來快速知道換算后的地址是在多少兆的地方。比如,1的后面5個0,代表1M的大小,6個0,代表16M,以此類推。
ROMFS found at 0x46040000, Volume name = rom 43f291aa
romfs,只讀文件系統所在的地址為:0x46040000 (flash映射后的第3分區)。卷名為rom。
romfs 和rootfs概念上有所區別。flash在內存中的的起始地址為0x46000000,而ROMFS在flash分區上的起始位置為0x00040000,所以ROMFS在內存地址中的位置就為0x46040000。這個細節的部分可以參考flash分區時的地方,Creating 3 MTD partitions。
romfs中包括kernel和app應用,不包括bootloader和firmware信息頭。romfs只讀文件系統里的內容有很多種分類方法,我們可以將kernel和app同時放里面,作為根文件系統下的一個文件,也可以在flash上另外劃分區域來分別存放。
*****************************************************************************
第一節:start_kernel
Linux的源代碼可以從 www.kernel.org 得到,或者你可以查看linux代碼交叉引用網站:http://lxr.linux.no/ 進行在線的代碼查看,這是一個很好的工具網站。
在start_kernel中將調用到大量的init函數,來完成內核的各種初始化。如:
圖 2:kernel start up初始化過程
具體內容可以參考[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代碼分析后得到的,進入init目錄下的main.c的start_kernel啟動函數。
uclinux使用的是linux內核版本為2.4.22。linux source code代碼中start_kernel中輸出的linux_banner信息。這個信息是每個linux kernel都會打印一下的信息,如果你沒有把這句去掉的話。
Found bootloader memory map at 0x10000fc0.
bootloader經過內存映射后的地址為:0x10000fc0, 按上面的地址換算方法,1后面有7個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.
預留內存大小,在節點0上總共20頁, zone(0) 設置最小內存為12MB, zone(1)和zone(2)為0頁。警告:對齊不正確。
Kernel command line: root=/dev/mtdblock3
Kernel 啟動命令設為:/dev/mtdblock3(在后面的說明中會看到mtdblock3是指的flash上的romfs分區。),用來指定根文件系統所在的位置,kernel會將塊設備mtdblock3當作文件系統來處理。也就是說,內核會根據上面的kernel命令行,知道只讀文件系統romfs將是 根文件系統rootfs。
start_kernel(void) 中輸出的上面的這句信息。這行命令是在linux內核啟動過程中都會輸出的一句。
Console: colour dummy device 80x30
代碼中console_init()的輸出信息, 顯示控制臺屬性:一般使用VGA text console,標準是80 X 25行列的文本控制臺,這里是對屬性進行了設置。
serial_xx: setup_console @ 115
串口設置值為115200,此為波特率輸出信息。對串口設置的信息做一個打印的動作,在調試時會非常有用。
Calibrating delay loop…… 82.94 BogoMIPS Calibrate:校準, 進入時延校準循環。檢查CPU的MIPS(每秒百萬條指令),Bogo是Bogus(偽)的意思。這里是對CPU進行一個實時測試,來得到一個大體的MIPS數值。
上面這個輸出,在所有的linux系統啟動中都會打印出來。
進入內存初始化:mem_init(void), [arch/i386/mm/init.c]
Memory: 80MB = 80MB totalMemory: 76592KB available (1724K code, 2565K data, 72K init)
當前內存使用情況,將列出總的內存大小, 及分配給內核的內存大小:包括代碼部分,數據部分,初始化部分,總共剛好4M。請留意此處的內核的內存大小的各個值。
進入虛擬文件系統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)
在內存中建立各個緩沖hash表,為kernel對文件系統的訪問做準備。
VFS(virtual filesystem switch)虛擬文件切換目錄樹有用到類似這樣的結構表。
上面的輸出信息,在一般的linux啟動過程中都會看到。
POSIX conformance testing by UNIFIX
conformance:順應, 一致。即POSIX適應性檢測。UNIFIX是一家德國的技術公司,Linux 原本要基于 POSIX.1 的, 但是POSIX 不是免費的, 而且 POSIX.1 證書相當昂貴。 這使得 Linux 基于 POSIX 開發相當困難。 Unifix公司(Braunschweig, 德國) 開發了一個獲得了 FIPS 151-2 證書的 Linux 系統。 這種技術用于 Unifix 的發行版 Unifix Linux 2.0 和 Lasermoon的 Linux-FT。
在2.6的內核中就將上面的這句輸出給拿掉了。
*************************************************************************
第二節:用戶模式( user_mode )開始,start_kernel結束
圖 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
設備的初始化 init()——》do_basic_init()——》pci_init(),初始化PCI,檢測系統的PCI設備。
Linux NET4.0 for Linux 2.4Based upon Swansea University Computer Society NET3.039
英國威爾士,斯旺西大學的NET3.039, TCP/IP 協議棧。
此信息,在linux啟動過程中都會出現。
Initializing RT netlink socket
對Socket的初始化, socket_init(),Netlink 一種路由器管理協議(linux-2.4.22/net/core/Rtnetlink.c,Routing netlink socket interface: protocol independent part。 其中RT是route路由的意思。這句輸出是在create產生rtnetlink的socket套接字時的一個調試輸出。)
此信息,在linux啟動過程中都會出現。
Starting kswapd
啟動交換守護進程kswapd,進程IO操作例程kpiod。
kswapd 可以配合kpiod運行。進程有時候無事可做,當它運行時也不一定需要把其所有的代碼和數據都放在內存中。這就意味著我們可以通過把運行中程序不用的內容切換到交換分區來更好的是利用內存。大約每隔1秒,kswapd醒來并檢查內存情況。如果在硬盤的東西要讀入內存,或者內存可用空間不足,kpiod就會 被調用來做移入/移出操作。kswapd負責檢查,kpiod負責移動。
Journalled Block Device driver loaded
加載日志塊設備驅動。
日志塊設備是用來對文件系統進行日志記錄的一個塊設備。日志文件系統是在傳統文件系統的基礎上,加入文件系統更改的日志記錄。
它 的設計思想是:跟蹤記錄文件系統的變化,并將變化內容記錄入日志。日志文件系統在磁盤分區中保存有日志記錄,寫操作首先是對記錄文件進行操作,若整個寫操作由于某種原因(如系統掉電)而中斷,系統重啟時,會根據日志記錄來恢復中斷前的寫操作。在日志文件系統中,所有的文件系統的變化都被記錄到日志,每隔一定時間,文件系統會將更新后的元數據及文件內容寫入磁盤。在對元數據做任何改變以前,文件系統驅動程序會向日志中寫入一個條目,這個條目描述了它將要做些 什么,然后它修改元數據。
devfs: v1.12c (20020818) Richard Gooch (rgooch@atnf.csiro.au)devfs: boot_options: 0x1
Devfs模塊的輸出信息。設備文件系統devfs,版本1.12c
pty: 256 Unix98 ptys configured
Pty模塊的輸出信息,與控制臺操作有關的設置。
將通過 devpts 文件系統使用 Unix98 PTYs,(Pseudo-ttys (telnet etc) device是偽ttys設備的縮寫。
① TTY(/dev/tty)是TeleTYpe的一個老縮寫,為用戶輸入提供不同控制臺的設備驅動程序。它的名字來源于實際掛接到UNIX系統的、被稱為電傳打字機(teletype)的終端。在Linux下,這些文件提供對虛擬控制臺的支持,可以通過按<Alt-F1>到<Alt -F6>鍵來訪問這些虛擬控制臺。這些虛擬控制臺提供獨立的、同時進行的本地登錄對話過程② ttys(/dev/ttys)是計算機終端的串行接口。/dev/ttyS0對應MS-DOS下的 COM1。
使 用 make dev腳本MAKEDEV來建立pty文件。這樣系統內核就支持Unix98風格的pty了。在進行Telnet登錄時將要用到/dev/pty設備。 pty是偽終端設備,在遠程登錄等需要以終端方式進行連接,但又并非真實終端的應用程序中必須使用這種設備,如telnet或xterm等程序。 Linux 2.2以后增添了UNIX98風格的Pty設備,它使用一個新的文件系統(devpts針對偽終端的文件系統)和一個克隆的設備cloning device來實現其功能。
linux-2.4.22/drivers/char/Pty.c, 在devfs_mk_dir (NULL, “pts”, NULL);時會輸出上面的信息。
loop: loaded (max 8 devices)
加載返還塊設備驅動,最多支持8個設備。
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’
網卡驅動,基地址為:0x60112000, MAC地址:00:10:0d:42:a0:03, 中斷號:14
從 2.2 版內核升級到 2.4 版時, RTL-8139 支持模塊已不再叫 rtl8139,而叫它 8139too,現在你再看到8139too就不會不明白它的來由了吧。
SCSI subsystem driver Revision: 1.00
USB設備信息,USB會被當做SCSI來處理。
mumk_register_tasklet: (1) tasklet 0x905bf9c0 status @0x9025e974
軟中斷信息輸出。Tasklet是在2.4中才出現,它是為了更好地利用多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與富士通合資設立的Flash供貨 商Spansion。AMD因獲利不佳,已經退出Flash市場,后續由Spansion合資公司經營。主要生產NOR類型的flash,特點是容量小,速度快。Spansion商標的flash,在我們開發中會經常看到。以后大家看到Spansion的芯片,就能了解到它和AMD還有富士通的來龍去脈 了。
Common flash Interface (CFI)是指一個統一的flash訪問接口,表示這種flash是這種接口類型的。
Using buffer write method
使用flash寫緩沖方式。
flash 提供了寫BUFFER的命令來加快對flash上塊的操作。對Flash擦除和寫數據是很慢的。如果用寫BUFFER的命令會快一點。據手冊上說,會快 20倍。Buffer Size :5 bytes的buffer緩沖不是每個塊都有,是整個flash只有一個5 bytes的buffer,用寫BUFFER命令對所有的塊進行寫操作,都要用同一個buffer,寫Buffer是主要檢查buffer是否 available,其實buffer起緩沖作用,來提高工作效率。
比如某flash有128個128K字節塊。允許用戶對任意塊進行字節編程和寫緩沖器字節編程操作,每字節編程時間為210μs;若采用寫緩沖器字節編程方式,32字節編程共需218μs,每字 節編程時間僅為6.8μs。芯片的塊擦除時間為1s,允許在編程或塊擦除操作的同時進行懸掛中斷去進行讀操作,待讀操作完成后,寫入懸掛恢復命令,再繼續編程或塊擦除。
Creating 3 MTD partitions on “XX mapped flash”:
0x00000000-0x00020000 : “BootLoader”
0x00020000-0x00040000 : “Config”
0x00040000-0x01000000 : “Romfs”
此處為重要信息部分,需要特別留意。在內存中映射過的flash,創建三個MTD分區:
flash上的內容將被映射到內存中的對應地址
前128K為BootLoader——》0x00000000-0x00020000接著的128K為系統配置信息Config存放的位置——》0x00020000-0x00040000再后面的 16M - 2X128K 為romfs的存放處。——》0x00040000-0x01000000上面的內容,大家可以根據前面的換算公式得到。
A》 編譯的bootloader一般大小約50K左右;
B》 在此處就知道了配置信息config是放在第2分區中的;
C》 制作的romfs的大小,一般為8M或10M左右,所以能放得下;
NET4: Linux TCP/IP 1.0 for NET4.0
調用inet_init [ linux-2.4.22/net/ipv4/Af_inet.c ]時的輸出信息, 在啟動過程中被socket.c調用到。
IP Protocols: ICMP, UDP, TCP, IGMP
列出可以支持的IP協議,此處為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 ],設置 IP module,路由緩沖hash表
TCP: Hash tables configured (established 8192 bind 8192)
TCP協議初始化輸出信息。tcp_init [ linux-2.4.22/net/ipv4/Tcp.c ],
NET4: Unix domain sockets 1.0/SMP for Linux NET4.0.
UNIX網絡協議信息。
af_unix_init[ linux-2.4.22/net/unix/Af_unix.c ], 多種連接的一種(IPv4, UNIX domain sockets, IPv6和IrDA)。 SMP 對稱多處理器—Symmetrical Multi Processing,這里主要是指UNIX的一些網絡協議。
上面的關于網絡的輸出信息是在linux啟動信息中都會出現的。
cramfs: wrong magic
加載各種文件系統。
會出現“cramfs: wrong magic”,別擔心這沒有什么害處,這個是kernel的書寫bug,在2.6中有修改之,它是一個警告信息,用來檢查cramfs的superblock超級塊的。superblock也是VFS要用到的數據結構。
代碼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;
}
超級塊是文件系統的“頭部”。它包含文件 系統的狀態、尺寸和空閑磁盤塊等信息。如果損壞了一個文件系統的超級塊(例如不小心直接將數據寫到了文件系統的超級塊分區中),那么系統可能會完全不識別該文件系統,這樣也就不能安裝它了,即使采用e2fsck 命令也不能處理這個問題。
**************************************************************************
RamDisk有三種實現方式。
在Linux中可以將一部分內存mount為分區來使用,通常稱之為RamDisk,分為:
Ramdisk, ramfs, tmpfs。
① 第一種就是傳統意義上的,可以格式化,然后加載。這在Linux內核2.0/2.2就已經支持,其不足之處是大小固定,之后不能改變。為了能夠使用 Ramdisk,我們在編譯內核時須將block device中的Ramdisk支持選上,它下面還有兩個選項,一個是設定Ramdisk的大小,默認是4096k;另一個是initrd的支持。
如果對Ramdisk的支持已經編譯進內核,我們就可以使用它了:首先查看一下可用的RamDisk,使用 ls /dev/ram*;首先創建一個目錄,比如test,運行 mkdir /mnt/test;然后對/dev/ram0 創建文件系統,運行 mke2fs /dev/ram0;最后掛載/dev/ram0,運行mount /dev/ram /mnt/test,就可以象對普通硬盤一樣對它進行操作了。
② 另兩種則是內核2.4才支持的,通過Ramfs或者Tmpfs來實現:它們不需經過格式化,用起來靈活,其大小隨所需要的空間而增加或減少。
Ramfs顧名思義是內存文件系統,它處于虛擬文件系統(VFS)層,而不像ramdisk那樣基于虛擬在內存中的其他文件系統(ex2fs)。因而,它無需格式化,可以創建多個,只要內存足夠,在創建時可以指定其最大能使用的內存大小。
如果你的Linux已經將Ramfs編譯進內核,你就可以很容易地使用Ramfs了。創建一個目錄,加載Ramfs到該目錄即可:
# mkdir /testRam # mount -t ramfs none /testRAM缺省情況下,Ramfs被限制最多可使用內存大小的一半。可以通過maxsize(以kbyte為單位)選項來改變。
# mount -t ramfs none /testRAM -o maxsize=2000 (創建了一個限定最大使用內存為2M的ramdisk)③ Tmpfs是一個虛擬內存文件系統,它不同于傳統的用塊設備形式來實現的Ramdisk,也不同于針對物理內存的Ramfs。
Tmpfs 可以使用物理內存,也可以使用交換分區。在Linux內核中,虛擬內存資源由物理內存(RAM)和交換分區組成,這些資源是由內核中的虛擬內存子系統來負 責分配和管理。Tmpfs向虛擬內存子系統請求頁來存儲文件,它同Linux的其它請求頁的部分一樣,不知道分配給自己的頁是在內存中還是在交換分區中。同Ramfs一樣,其大小也不是固定的,而是隨著所需要的空間而動態的增減。
使用tmpfs,首先你編譯內核時得選擇“虛擬內存文件系統支持(Virtual memory filesystem support)”。然后就可以加載tmpfs文件系統了:
# mkdir -p /mnt/tmpfs
# mount tmpfs /mnt/tmpfs -t tmpfs
同樣可以在加載時指定tmpfs文件系統大小的最大限制:
# mount tmpfs /mnt/tmpfs -t tmpfs -o size=32m
FAT: bogus logical sector size 21072
具體的文件系統FAT格式。虛擬邏輯扇區大小為20K,linux-2.4.22/fs/fat/Inode.c。
在初始化MS-DOS文件系統時,讀MS-DOS文件系統的superblock,函數fat_read_super中輸出的上面的信息。
UMSDOS: msdos_read_super failed, mount aborted.
UMSDOS:一種文件系統,特點容量大 但相對而言不大穩定。是Linux 使用的擴展了的DOS文件系統。它在 DOS 文件系統下增加了長文件名、 UID/GID、POSIX 權限和特殊文件 (設備、命名管道等)功能,而不犧牲對 DOS 的兼容性。允許一個普通的msdos文件系統用于Linux,而且無須為它建立單獨的分區,特別適合早期的硬盤空間不足的硬件條件。
VFS: Mounted root (romfs filesystem) readonly
虛擬文件系統VFS(Virtual Filesystem Switch)的輸出信息。
再 次強調一下一個概念。VFS 是一種軟件機制,也可稱它為 Linux 的文件系統管理者,它是用來管理實際文件系統的掛載點,目的是為了能支持多種文件系統。kernel會先在內存中建立一顆 VFS 目錄樹,是內存中的一個數據對象,然后在其下掛載rootfs文件系統,還可以掛載其他類型的文件系統到某個子目錄上。
Mounted devfs on /dev
加載devfs設備管理文件系統到dev安裝點上。/dev是我們經常會用到的一個目錄。在2.4的kernel中才有使用到。每次啟動時內核會自動掛載devfs。
devfs 提供了訪問內核設備的命名空間。它并不是建立或更改設備節點,devfs只是為你的特別文件系統進行維護。一般我們可以手工mknod創件設備節點。 /dev目錄最初是空的,里面特定的文件是在系統啟動時、或是加載模組后驅動程序載入時建立的。當模組和驅動程序卸載時,文件就消失了。
Freeing init memory: 72K
釋放1號用戶進程init所占用的內存
*************************************************************
第三節:加載linux內核完畢,轉入cpu_idle進程
系統啟動過程中進程情況:
① init進程
一 般來說, 系統在跑完 kernel bootstrapping 內核引導自舉后(被裝入內存、已經開始運行、已經初始化了所有的設備驅動程序和數據結構等等), 就去運行 init『萬process之父』, 有了它, 才能開始跑其他的進程,因此,init進程,它是內核啟動的第一個用戶級進程,它的進程號總是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(用于用戶登錄)、實現運行級別、以及處理孤立進程。init 一開始就去讀 /etc/inittab (init初始化表),初始化表是按一定格式排列的關于進程運行時的有關信息的。init程序需要讀取/etc/inittab文件作為其行為指針。這個 inittab 中對于各個runlevel運行級別要跑哪些 rc 或 spawn 生出什么有很清楚的設定。
一 般, 在Linux中初始化腳本在/etc/inittab 文件(或稱初始化表)中可以找到關于不同運行級別的描述。inittab是以行為單位的描述性(非執行性)文本,每一個指令行都是固定格式。 inittab中有respawn項,但如果一個命令運行時失敗了,為了避免重運行的頻率太高,init將追蹤一個命令重運行了多少次,并且如果重運行的 頻率太高,它將被延時五分鐘后再運行。
② kernel進程
A》 請注意init是1號進程,其他進程id分別是kflushd/ bdflush, kupdate, kpiod and kswapd。這里有一個要指出的:你會注意到虛擬占用(SIZE)和實際占用(RSS)列都是0,進程怎么會不使用內存呢?
這些進程就是內核守護進程。大部分內核并不顯示在進程列表里。守護進程在init之后啟動,所以他們和其他進程一樣有進程ID,但是他們的代碼和數據都存放在內核占有的內存中。在列表中使用中括號來區別與其他進程。
B》 輸入和輸出是通過內存中的緩沖來完成的,這讓事情變得更快,程序的寫入會存放在內存緩沖中,然后再一起寫入硬盤。守護進程kflushd和kupdate 管理這些工作。kupdate間斷的工作(每5秒)來檢查是否有寫過的緩沖,如過有,就讓kflushd把它們寫入磁盤。
C》 進程有時候無事可做,當它運行時也不一定需要把其所有的代碼和數據都放在內存中。這就意味著我們可以通過把運行中程序不用的內容切換到交換分區來更好的是利用內存。把這些進程數據移入/移出內存通過進程IO管理守護進程kpiod和交換守護進程kswapd,大約每隔1秒,kswapd醒來并檢查內存情 況。如果在硬盤的東西要讀入內存,或者內存可用空間不足,kpiod就會被調用來做移入/移出操作。
D》 bdflush - BUF_DIRTY, 將dirty緩存寫回到磁盤的核心守護進程。對于有許多臟的緩沖區(包含必須同時寫到磁盤的數據的緩沖區)的系統提供了動態的響應。它在系統啟動的時候作為一個核心線程啟動,它叫自己為 “kflushd”,而這是你用ps顯示系統中的進程的時候你會看得的名字。即定期(5秒)將臟(dirty)緩沖區的內容寫入磁盤,以騰出內存;
E》 ksoftirqd_CPUx 是一個死循環, 負責處理軟中斷的。它是用來對軟中斷隊列進行緩沖處理的進程。當發生軟中斷時,系統并不急于處理,只是將相應的cpu的中斷狀態結構中的active 的相應的位,置位,并將相應的處理函數掛到相應的隊列,然后等待調度時機來臨,再來處理。
ksoftirqd_CPUx是由 cpu_raise_softirq() 即cpu觸發中斷,喚醒的內核線程,這涉及到軟中斷,ksoftirqd的代碼參見[kernel/softirq.c]。
F》 keventd,它的任務就是執行 scheduler 調度器隊列中的任務,keventd 為它運行的任務提供了可預期的進程上下文。
G》 khubd, 是用來檢測USB hub設備的,當usb有動態插拔時,將交由此內核進程來處理。在檢測到有hub事件時會有相應的動作(usb_hub_events())
H》 mtdblockd是用來對flash塊設備進行寫操作的守護進程。NAND類型的Flash需要MTD(Memory Technology Devices 內存技術驅動程序)驅動的支持才能被linux所使用。NAND的特點是不能在芯片內執行(XIP,eXecute In Place),需要把代碼讀到系統RAM中再執行,傳輸效率不是最高,最大擦寫次數量為一百萬次,但寫入和擦除的速度很快,擦除單元小,是高數據存儲密度 的最佳選擇。NAND需要I/O接口,因此使用時需要驅動程序。
I》 loop0 是負責處理loop塊設備的(回環設備)。loopback device指的就是拿文件來模擬塊設備, 在我們這里,loop設備主要用來處理需要mount到板上的文件系統,類似mount /tmp/rootfs /mnt -o loop。。我們的實例有:mount -o loop -t cramfs /xxx.bin /xxx 也就是將xxx.bin這個文件mount到板上來模擬cramfs壓縮ram文件系統。loop0進程負責對loop設備進行操作。
loopback設備和其他的塊設備的使用方法相同。特別的是,可以在該設備上建立一個文件系統,然后利用mount命令把該系統映射到某個目錄下以便訪問。這種整個建立在一個普通磁盤文件上的文件系統,就是虛擬文件系統 (virtual file system)。
總結
上面的內容是本人為了在實際開發中更加清楚地了解uclinux的啟動過程而做的一個總結性的文章。在對uclinux的啟動過程做了一個詳細注釋后,大家 會對涉及到嵌入系統的各個概念有了一個更加明確的認識,并能對嵌入系統的軟硬件環境的有關設置更加清楚。當你自己動手結合linux源代碼來分析時,將會有一個清楚的全局觀。
=============================================================
1. 運行bootloader初始化程序
SRAM 、SDRAM等存儲設備屬于揮發性的存儲器,掉電以后其中的內容就會全部丟失,所以必須把操作系統的內核鏡像存放在Flash等不揮發性存儲介質上。但是操作系統在運行時,需要動態的創建一些如數據段、堆棧、頁表(針對使用虛擬地址的操作系統)等內容,所以需要在RAM中運行操作系統。
因此,就需要一個引導程序把操作系統的內核鏡像從Flash存儲器拷貝到RAM中,然后再從RAM中執行操作系統的內核。Bootloader就是可以完成這樣一種功能的程序。
從本質上來講,bootloader不屬于操作系統內核。它采用匯編語言編寫,因此針對不同的CPU體系結構,這一部分代碼不具有可移植性。在移植操作系統時,這部分代碼必須加以改寫
具體來講,bootloader在系統啟動時主要完成以下幾項工作:
(1) 將操作系統內核從Flash拷貝到SDRAM中,如果是壓縮格式的內核,還要將之解壓縮。
(2) 改寫系統的memory map,原先flash起始地址映射為0地址,這時需要將RAM的起始地址映射為0。
(3) 設置堆棧指針并將bss段清零。
將來執行C語言程序和調用子函數時要用到
(4) 改變pc值,使得CPU開始執行真正的操作系統內核。
2. 運行操作系統內核
bootloader程序執行完上述的各項工作后,通過一條跳轉指令,轉而執行init目錄下C語言源文件main.c中的函數start_kernel()。
因為在此之前bootloader已經創建好一個初始化環境,C函數可以開始執行了。
整個操作系統內核的初始化工作從這里才算是真正開始。這個函數的長度比較短,代碼如下:
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();
}
內核啟動之后需要執行的第一個函數是start_kernel()(在linux/init/main.c文件中)。
start_kernel()
完成下面一系列初始化的工作。
◆printk(1inux_banner),顯示Linux內核的版本信息。
◆ setup_arch(&command_line),做與體系結構相關的初始化工作。
◆ parse_options(command_line),解釋系統參數。
◆ trap_init(),設置系統異常的入口點。
◆ init_IRQ(),初始化系統中斷服務。
◆ sched_init(),系統調度器的初始化。
◆ time_init(),時鐘、定時器初始化。
◆ softirq_init(),系統軟中斷的初始化。
◆console_init(),控制臺初始化。
◆kmem_cache_init(),內核cache的初始化。
◆calibrate_delay(),校準時鐘。
◆mem_init(),內存初始化。
◆kmem_cache_sizes_init(),創建及設置通用cache。
◆fork_init(mempages),建立uidcache,并且根據系統內存大小來確定最大進程數目。
◆buffer_init(mempages),塊設備緩沖區的初始化。初始化一系列的cache。
◆check_bugs(),檢查體系結構漏洞。
◆kernel_thread(init NULL,CLONE_FS | CLONE_FILES | CLONE_SIGNAL),創建第一個核心進程,啟動init進程。
◆cpu_idle(),運行idle進程
接下去做的工作由init()函數來完成。init()首先要鎖定內核,然后調用do_basic_setup( )來完成外部設備以及驅動程序的初始化。外設的初始化要根據內核的配置來決定,一般需要做下面的初始化工作:
◆PCI總線初始化。
◆網絡初始化。
◆一系列其他設備的初始化。
◆start_context_thread()創建事件管理核心進程keventd。
◆通過do_initcalls()函數來啟動任何使用__initcall標識的函數。
◆文件系統初始化。
◆加載文件系統。
在do_basic_setup()調用完成之后,init()會釋放初始化函數所用的內存,并且打開/dev/console設備重新定向控制臺,讓系統調用execve來執行程序init。
到這里為止,Linux 內核的初始化工作已經完成。然后開始用戶態進程的初始化。
=============================================================
uClinux中內存模塊的啟動初始化
arch/armnommu/kernel/entry_armv.S是一個匯編文件,他包含了一個kernel_entry的定義,這是整個內核的進入點。在完成某些平臺相關的初始化工作之后,執行流程跳轉到start_kerne()處。從這里開始考察uClinux的內存模塊啟動初始化是如何實現的。
start_kernel()中與內存模塊相關的函數調用流程如下:
setup_arch() paging_init() free_area_init() mem_init()
下面分別分析這些函數各自的功能以及uClinux對他們的改造。
(1) setup_arch()
setup_arch()首先根據目前內核所配置的平臺向某些特定地址寫入特殊字符序列,以完成對特定硬件的初始化,比如工作狀態發光二極管、錯誤和報警發光二極管。
存儲了可用物理內存起始地址的變量memory_start的初始化是通過一個ld腳本中定義的變量_end進行的。ld腳本是用來控制GNU ld連接器在連接內核各個目標文件部分的時候的配置動作,比如這樣一個腳本:
SECTION
{
。=0x10000;
.text:{*(.text)}
。=0x8000000;
.data:{*(.data)}
.bss :{*(.bss)}
}
用來配置ld連接目標文件的時候將所有的目標文件中的存儲程序正文的.text段(section)連接到一起,并且映射到輸出文件的地址0x10000處,將所有目標文件中已初始化的數據.data段連接到一起并放置到輸出可執行文件的0x8000000地址處,而所有目標文件中還未初始化的數據段.bss連接起來后影射到輸出文件中緊跟在.data段之后的位置。
這個ld配置腳本文件對每個平臺都是不同的。如為MICETEK上所使用的uClinux版本使用的ld配置文件為arch/armnomm/vmLinux.lds。可以通過修改某個平臺上的ld腳本配置文件中的_end變量來達到配置其可用物理內存起始地址的目的。
setup_arch()在完成對memory_start變量的初始化之后,通過某些特定手段檢測不同類型的內存分布情況。比如為檢測某段地址范圍是否為RAM的方法是通過將某個地址的數據讀出來,將它加1后寫回內存地址中,然后再讀出來和原始數據比較看看其值是否成功增加了1,這樣反復操作兩次,最后將數據恢復。如果是可讀可寫的RAM,那么這個測試的結果就是每次比較都是成功的,否則就不能將這個地址當作RAM。
在setup_arch()中還可能根據所用平臺進行對flash memory和ROM的測試。在這些平臺相關的工作完成之后,setup_arch()將對系統運行的第一個進程init_task的mm_struct結構中描述地址空間分布的變量start_code,end_code,end_data和brk進行初始化,start_code為0,其他三個數值分別為來自于ld腳本配置文件中定義的相關變量_etext、_edata和_end。
此后setup_arch()將根據Linux中為系統中的第一塊rom/flash memory card所分配的固定的主/從設備號(可以從Document/devices.txt中得到)來創建根文件系統的設備號,并存儲在后來將要用到的全局變量ROOT_DEV中。
setup_arch()最后完成對系統啟動參數的保存。
在調用setup_arch()返回之后,start_kernel()中得到了系統可用物理內存的起始和結束地址,以及命令啟動時的命令行參數。
(2) paging_init()
在Linux中,paging_init()的一項主要功能是建立頁目錄和頁表,而且將Linux移植到不同平臺的過程中非常重要的一個步驟就是修改這個函數來適應新的硬件平臺的虛擬內存體系。但是由于在uClinux中不再使用虛擬內存機制,也就不再需要維護頁目錄和頁表數據結構了,所以paging_init()在這里只是為系統啟動的時候保留一部分特殊用途的內存區間。它返回后,從可以使用的內存空間開始,依次是如下的數據結構:
empty_bad_page_table 占用1頁(4KB)
empty_bad_page 占用1頁(4KB)
empty_zero_page 占用1頁,并初始化為全0
mem_map
bitmap
paging_init()函數在返回前通過調用free_area_init(start_mem,end_mem)進行建立buddy system的映射位圖關系,以及建立空閑物理頁面鏈表的操作。
(3)free_area_init()
這個函數用于建立管理物理頁幀的數據結構mem_map,有多少物理頁幀就有多少mem_map_t類型的結構體與之相對應。每個頁面的mem_map_t結構中的flags被標明為PG_DMA和PG_reserved,并且頁幀號被賦給相應的數值。同時建立了管理空閑頁面的bitmap映射表,并且所有的位都被清零。
(3) mem_init()
mem_init()函數遍歷整個可用物理內存地址空間,將每個頁面相對應的struct page結構中flags的PG_reserved 標志位清除,標志用戶個數的count計數器置1,并同時統計可用物理頁面數量,然后打印系統的各個內存參數,如可用RAM和ROM的大小、內核代碼段和數據段大小等。
======================================================
摘 要:本文采用三星公司的S3C44B0微處理器,對uClinux操作系統內核的引導過程進行了剖析。
關鍵字:S3C44B0X;uClinux;嵌入式系統;內核引導
1 前言
伴隨著微電子的發展,用于嵌入式設備的處理器速度越來越快,功能也越來越強大。三星公司生產的S3C44B0微處理器,采用的是ARM7TDMI內核。該內核因為有著功耗小、成本低等特點,因此非常適合作為移動手持終端的處理器核心。Linux操作系統因為它的開放性,使得它不斷的被應用到各個領域。在嵌入式領域同樣也出現了各種各樣的Linux變體,最常用的是uClinux。也正是因為uClinux操作系統支持不帶MMU單元的ARM處理器,因此該系統可以對S3C44B0微處理器有很好的支持。
在嵌入式系統開發中,第一個部分便是系統的引導。而系統的引導過程是通過BootLoader來完成的。BootLoader程序是與硬件緊密相關的一段代碼,而且編寫的時候比較復雜,它主要的功能是初始化微處理器以及周邊的硬件資源,并且引導操作系統的啟動。下面我將以S3C44B0微處理器來作為例子,對uClinux操作系統內核的引導過程進行一個剖析。
2 BootLoader程序概念
簡單的說Boot Lodaer就是在操作系統內核運行之前運行的一段小程序,通過這段小程序,可以初始化硬件設備、建立系統的內存空間映射圖,從而將系統的軟硬件環境設置成一個適合的狀態,以便為最終調用操作系統內核準備好正確的環境。最終,BootLoader把操作系統內核映象加載到RAM中,并將系統控制權傳遞給它。
2.1 典型的BootLoader程序框架
操作系統角度來說,Boot Loader的總目標就是正確的調用內核來執行。
由于Boot Loader的實現依賴于CPU的體系結構,因此大多數Boot Loader都分為Stage1和Stage2兩大部分。依賴于CPU體系結構的代碼,例如設備初始化代碼等,通常都放在Stage1中,而且通常都用匯編語言來實現,以達到短小精悍的目的。而Stage2通常用C語言來實現,這樣可以實現更加復雜的功能,而且代碼會具有更好的可讀性和可移植性。
Boot Loader的Stage1通常包括如下步驟:
1) 硬件設備初始化
2) 為加載Boot Loader的Stage2準備RAM空間
3) 復制Boot Loader的Stage2到RAM空間中
4) 設置好堆棧
5) 跳轉到Stage2的C入口點
Boot Loader的Stage2通常包括如下步驟:
1) 始化本階段要使用的硬件設備
2) 檢測系統內存映射(Memory Map)
3) 將Kernel映象和根文件系統映象從FLASH上讀取到RAM空間中
4) 為內核設置啟動參數
5) 調用內核
2.2 系統內存組織
由于嵌入式設備具有很好的制定性,因此通常硬件環境會變的千差萬別。就算是用戶使用了相同的處理器芯片,但是也很有可能因為外圍設備電路設計的不同,而存在差異。對于BootLoader程序來說,存儲設備的與處理器的連接方式,與其息息相關。對于我們采用的S3C44B0微處理器來說,在系統加電之后,指令指針是指向0x00000000的,也就是說系統是從0x00000000開始之行。正是因為這個原因,通常這個地址空間我們會安排給FLASH存儲器。這樣我們可以將BootLoader啟動代碼以及我們之后將會要啟動的uClinux操作系統映像燒寫到Flash里。對于RAM地址空間,S3C44B0芯片將其設定為從0x0C000000到0x0FFFFFFF一共64MB的范圍里。我們可以通過設定存儲器控制寄存器來重新設定RAM的大小。例如我們試驗采用的存儲設備安排如下:
0x00000000 – 0x003FFFFF 4MB Flash
0x0C000000 – 0x0C7FFFFF 8MB RAM
通常來說對于系統的引導和操作系統的啟動,可以完全都在Flash中進行,但是Flash存儲器的速度相對于RAM來說會慢很多,因此出于速度上的考慮,我們通常會將啟動代碼和uClinux操作系統的內核映像文件拷貝到RAM中之行。
下面我將對典型的BootLoader程序框架進行分析。
2.3 Stage1階段
該階段的主要工作是完成對系統中斷向量的設置,初始化微處理器內部寄存器,初始化堆棧,初始化RAM地址空間,并且將Stage2部分的C代碼拷貝到RAM空間的指定地點,然后跳轉到C代碼入口點繼續執行。對于這段代碼來說,做的都是一些準備工作,因此為了提高效率,這段代碼通常都是使用匯編語言來完成的。下面我將結合具體的代碼來分析一下Stage1的啟動過程。
1)設置中斷向量
設置S3C44B0處理器定義的8種系統中斷的中斷向量地址。這八種系統中斷分別是復位中斷、未定義指令中斷、軟件中斷、指令預取異常中斷、數據異常中斷、地址異常中斷、IRQ中斷和FIQ中斷。這8個中斷通常是通過無條件跳轉的方式來實現的。具體的代碼如下。
__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)初始化微處理器內部寄存器
這段代碼主要是要完成硬件部分的初始化,包括關閉中斷響應、初始化微處理器通用端口、設置CPU頻率等操作。不過需要注意的是,在進行硬件初始化之前需要將微處理器的運行狀態轉換到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)初始化系統RAM空間
這個部分的工作主要是為之后啟動代碼和內核映像的拷貝操作做準備,并且也為之后的C代碼的執行初始化堆棧。這部分的工作主要可以分成兩個部分來處理。首先,根據系統配置的存儲器特性來初始化相關的存儲器控制寄存器。在我們使用的S3C44B0處理器中,存儲空間被分成了BANK0-BANK7一共8個塊,分別由BANKCON0-BANKCON7控制各個塊存儲器的讀寫時鐘和片選時鐘等信號參數。具體代碼如下:
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的地址空間,將該斷地址空間的內容清0。該部分地址空間主要是用來存放C語言代碼中的全局變量等內容的。實現代碼如下:
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語言代碼的執行準備必要的堆棧
因為在Stage2階段一般都是采用C語言代碼來完成的,因此必須在使用C語言代碼之前先建立起必要的堆棧信息。通常為了避免堆棧數據被執行代碼破壞,通常都是放在RAM的高端地址,并且使得堆棧指針的增長方向是向下增長的。
5)將初始化代碼拷貝到RAM中,并且跳轉到RAM中執行。因為在我們采用的S3C44B0微處理器里對于FLASH和RAM地址空間是使用的統一編址的,因此我們可以直接使用一個簡單循環來完成拷貝。
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)跳轉到C代碼執行(即Stage2階段)
這個過程是直接給指令指針賦值于跳轉的C代碼的入口地址,在我們的試驗中該入口地址是Main。
LDR pc,=Main
2.4 Stage2階段
該階段的代碼主要使用C語言來實現的。該階段的工作主要是建立開發板與宿主機之間的通信,加載uClinux內核映像文件和配置內核啟動參數,并且啟動內核。
嵌入式設備與宿主機的通訊方式有多種,最常用的是使用串口方式進行數據交換。本試驗采用的S3C44B0微處理器提供了兩個UART口,因此我們可以任選其中一個來初始化并且使用它來與宿主機交互。對于串口的初始化主要是波特率、奇偶校驗、停止位、數據位等內容。
對于串口的波特率和波特因子的計算采用如下公式
Iubrd =((int(mclk/16 / baud + 0.5) – 1)
mclk是頻率、baud為波特率
2.4.1 檢測內存
該部分的功能主要是檢測系統在進行硬件初始化的時候是否發生了內存映射錯誤,即是否物理地址是否被映射到不存在的地址空間。通常是使用讀寫方式來檢測的,即以內存頁為單位,在每個頁頭進行讀寫操作,比較讀寫結果。因為S3C44B0處理器并不支持內存映射,因此我們在Stage2過程中并沒有包含該部分功能函數。
2.4.2 加載uClinux內核映像
該過程其實只是一個從Flash的指定位置(該位置是uClinux燒寫的起始地址)拷貝到RAM中指定的地址空間里。在拷貝之前必須要為uClinux的全局變量結構,即啟動參數、內核頁表、RAM的頁目錄等信息預留一定的空間。如果我們將FLASH和RAM看成連在一起的線性地址,則系統的空間分配會如下圖:
。..。..
Boot
初始化代碼
uClinux
未用
中斷向量表
初始化映像代碼
啟動參數
內核映像
未用
堆棧
2.4.3 配置內核啟動參數
我們采用的uClinux是2.4.x內核版本,該版本的內核支持參數啟動過程。在嵌入式系統中,啟動參數的傳入主要是依靠bootloader程序向標記列表(tagged list)的相關域中填寫相應的值來完成的。
2.5 uClinux內核引導
當我們初始化完畢uClinux的啟動參數之后,控制權就可以交給uClinux內核了,uClinux系統調用內核解壓函數(decompress_kernel)來對上一個階段拷貝的uClinux內核在RAM空間里進行解壓(當然如果系統內核在建立的時候沒有配置成壓縮格式,則解壓過程略去)。在解壓完畢后,跳轉到內核調用函數(call_kernel),該函數實際上執行的是start_kernel(),這個函數包含了有關處理器初始化、中斷初始化、進程初始化等操作。最后,將控制權完全的交與uClinux操作系統來執行。
偽處理過程如下:
IF(啟動參數正確)
CALL decmporess_kernel()
CALL call_kernel()
ELSE
啟動失敗
decompress_kernel()
{
解壓內核映像
}
call_kernel()
{
。..
start_kernel()
。..。
}
3 總結
本文是對S3C44B0的啟動過程進行了一次分析,啟動部分的代碼可以說是嵌入式設備開發比較重要的部分。而且該部分的處理工作往往又比較麻煩,因此在這里我只是想起到拋磚引玉的作用。因為成文時間比較倉促,難免有錯誤,請大家批評指正。
==========================================================
《ARM7 uClinux開發實驗與實踐》P130
Bootloader完成系統初始化工作后,將運行控制權交給uClinux內核。根據內核是否壓縮以及內核是否在本地執行,uClinux通常有以下兩種可選的啟動方式:
(1)Flash本地運行方式。內核中未經壓縮的可執行映像固化在Flash中,系統啟動時,內核在Flash中開始逐句執行。
(2)壓縮內核加載方式。內核的壓縮映像固化在Flash上,系統啟動時,由附加在壓縮映像前的解壓復制程序讀取壓縮映像,并在內存中解壓后執行。這種方式相對復雜,但是運行速度更快。
首先介紹內核的Flash本地運行方式。
本地運行時,內核的啟動包括特定體系結構設置和uClinux系統初始化兩步,內核啟動的入口文件是head-armv.s。
評論
查看更多