滾滾長江東四水,浪花淘淨英雄。
大家好,許多人和我一樣,正在苦讀Linux源代碼,希望有照一日,寶典在手,天下我有。小弟不才,也讀了兩年,寫的幾首歪詩。從本級開始,把我所理解的linux如何啟動貼出來,不懂之處大家討論一番。也希望把linux從頭到尾討論一遍,計劃寫它240回,三年寫完(笑。。。),歡迎大家動員一些牛人來參與討論,提高人氣,增加流量。
小弟用的是arm920T,跑LINUX 2。4。18,下面是第一回。。。。。
長篇連載--arm linux演藝---第一回
--------------------------------------------------------------------------------
話說。。。(噓聲,“入正題把!“)
好好:
首先,porting linux的時候要規劃內存影像,如小弟的系統有64m SDRAM,
地址從0x 0800 0000 -0x0bff ffff,32m flash,地址從0x0c00 0000-0x0dff ffff.
規劃如下:bootloader, linux kernel, rootdisk放在flash裡。
具體從 0x0c00 0000開始的第一個1M放bootloader,
0x0c10 0000開始的2m放linux kernel,從 0x0c30 0000開始都給rootdisk。
啟動:
首先,啟動後arm920T將地址0x0c00 0000映射到0(可通過跳線設置),
實際上從0x0c00 0000啟動,進入我們的bootloader,但由於flash速度慢,
所以bootloader前面有一小段程序把bootloader拷貝到SDRAM 中的0x0AFE0100,
再從0x 0800 0000 運行bootloader,我們叫這段小程序為flashloader,
flashloader必須要首先初始化SDRAM,不然往那放那些東東:
.equ SOURCE, 0x0C000100 bootloader的存放地址
.equ TARGET, 0x0AFE0100 目標地址
.equ SDCTL0, 0x221000 SDRAM控制器寄存器
// size is stored in location 0x0C0000FC
.global _start
_start: //入口點
//;***************************************
//;* Init SDRAM
//;***************************************
// ;***************
// ;* SDRAM
// ;***************
LDR r1, =SDCTL0 //
// ; Set Precharge Command
LDR r3, =0x92120200
//ldr r3,=0x92120251
STR r3, [r1]
// ; Issue Precharge All Commad
LDR r3, =0x8200000
LDR r2, [r3]
// ; Set AutoRefresh Command
LDR r3, =0xA2120200
STR r3, [r1]
// ; Issue AutoRefresh Command
LDR r3, =0x8000000
LDR r2, [r3]
LDR r2, [r3]
LDR r2, [r3]
LDR r2, [r3]
LDR r2, [r3]
LDR r2, [r3]
LDR r2, [r3]
LDR r2, [r3]
// ; Set Mode Register
LDR r3, =0xB2120200
STR r3, [r1]
// ; Issue Mode Register Command
LDR r3, =0x08111800 //; Mode Register Value
LDR r2, [r3]
// ; Set Normal Mode
LDR r3, =0x82124200
STR r3, [r1]
//;***************************************
//;* End of SDRAM and SyncFlash Init *
//;***************************************
// copy code from FLASH to SRAM
_CopyCodes:
ldr r0,=SOURCE
ldr r1,=TARGET
sub r3,r0,#4
ldr r2,[r3]
_CopyLoop:
ldr r3,[r0]
str r3,[r1]
add r0,r0,#4
add r1,r1,#4
sub r2,r2,#4
teq r2,#0
beq _EndCopy
b _CopyLoop
_EndCopy:
ldr r0,=TARGET
mov pc,r0
欲知後事如何,下回分解:
長篇連載--arm linux演藝---第二回
--------------------------------------------------------------------------------
上回書說到flashloader把bootloader load到0x0AFE0100, 然回跳了過去,
其實0x0AFE0100 就是燒在flash 0x0C000100中的真正的bootloader:
bootloader 有幾個文件組成,先是START.s,也是唯一的一個匯編程序,其余的都是C寫成的,START.s主要初始化堆棧:
_start:
ldr r1,=StackInit
ldr sp,[r1]
b main
//此處我們跳到了C代碼的main函數,當C代碼執行完後,還要調用
//下面的JumpToKernel0x跳到LINXU kernel運行
.equ StackInitValue, __end_data+0x1000 // 4K __end_data在連結腳本中指定
StackInit:
.long StackInitValue
.global JumpToKernel
JumpToKernel:
// jump to the copy code (get the arguments right)
mov pc, r0
.global JumpToKernel0x
// r0 = jump address
// r1-r4 = arguments to use (these get shifted)
JumpToKernel0x:
// jump to the copy code (get the arguments right)
mov r8, r0
mov r0, r1
mov r1, r2
mov r2, r3
mov r3, r4
mov pc, r8
.section ".data.boot"
.section ".bss.boot"
欲知bootloader中的c代碼如何運行,請看下集
長篇連載--arm linux演藝---第三回
--------------------------------------------------------------------------------
書接上回:
下面讓我們看看bootloader的c代碼干了些什麼。main函數比較長,讓我們分段慢慢看。
int main()
{
U32 *pSource, *pDestin, count;
U8 countDown, bootOption;
U32 delayCount;
U32 fileSize, i;
char c;
char *pCmdLine;
char *pMem;
init(); //初始化FLASH控制器和CPU時鐘
EUARTinit(); //串口初始化
EUARTputString("
DBMX1 linux Bootloader ver 0.2.0
");
EUARTputString("Copyright (C) 2002 Motorola Ltd.
");
EUARTputString((U8 *)cmdLine);
EUARTputString("
");
EUARTputString("Press any key for alternate boot-up options ... ");
小弟的bootloader主要干這麼幾件事:init(); 初始化硬件,打印一些信息和提供一些操作選項:
0. Program bootloader image
1. Program kernel image
2. Program root-disk image
3. Download kernel and boot from RAM
4. Download kernel and boot with ver 0.1.x bootloader format
5. Boot a ver0.1.x kernel
6. Boot with a different command line
也就是說,可以在bootloader裡選擇重新下載kernel,rootdisk並寫入flash,
下載的方法是用usb連接,10m的rootdisk也就刷的一下。關於usb下載的討論請參看先前的貼子“為arm開發平台增加usb下載接口“。
如果不選,直接回車,就開始把整個linux的內核拷貝到SDRAM中運行。
列位看官,可能有人要問,在flashloader中不是已經初始化過sdram控制器了嗎?怎麼init(); 中還要初始化呢,各位有所不知,小弟用的是syncflash,
可以直接使用sdram控制器的接口,切記:在flash中運行的代碼是不能初始化連接flash的sdram控制器的,不然絕對死掉了。所以,當程序在flash中運行的時候,去初始化sdram,而現在在sdram中運行,可放心大膽地初始化flash了,主要是設定字寬,行列延時,因為缺省都是最大的。
另外,如果列位看官的cpu有足夠的片內ram,完全可以先把bootloader放在片內ram,干完一切後再跳到LINUX,小弟著也是不得已而為之啊。
今天太晚了,回去睡覺了。。。
長篇連載--arm linux演藝---第四回
--------------------------------------------------------------------------------
如果直接輸入回車,進入kernel拷貝工作:
EUARTputString("Copying kernel from Flash to RAM ...
");
count = 0x200000; // 2 Mbytes
pSource = (U32 *)0x0C100000;
pDestin = (U32 *)0x08008000;
do
{
*(pDestin++) = *(pSource++);
count -= 4;
} while (count > 0);
}
EUARTputString("Booting kernel ...
");
這一段沒有什麼可說的,運行完後kernel就在0x08008000了,至於為什麼要
空出0x8000的一段,主要是放kelnel的一些全局數據結構,如內核頁表,arm的頁目錄要有16k大。
我們知道,linux內核啟動的時候可以傳入參數,如在PC上,如果使用LILO,
當出現LILO:,我們可以輸入root=/dev/hda1.或mem=128M等指定文件系統的設備或內存大小,在嵌入式系統上,參數的傳入是要靠bootloader完成的,
pMem = (char *)0x083FF000; //參數字符串的目標存放地址
pCmdLine = (char *)&cmdLine; //定義的靜態字符串
while ((*(pMem++)=*(pCmdLine++)) != 0);//拷貝
JumpToKernel((void *)0x8008000, 0x083FF000) ;//跳轉到內核
return (0);
JumpToKernel在前文中的start.S定義過:
JumpToKernel:
// jump to the copy code (get the arguments right)
mov pc, r0
.global JumpToKernel0x
// r0 = jump address
// r1 = arguments to use (these get shifted)
由於arm-GCC的c參數調用的順序是從左到右R0開始,所以R0是KERNKEL的地址,
r1是參數字符串的地址:
到此為止,為linux引導做的准備工作就結束了,下一回我們就正式進入linux的代碼。
困了。。。
長篇連載--arm linux演藝---第五回
--------------------------------------------------------------------------------
好,從本節開始,我們走過了bootloader的漫長征途,開始進入linux的內核:
說實話,linux寶典的確高深莫測,洋人花了十幾年修煉,各種內功心法層處不窮。有些地方反復推敲也領悟不了其中奧妙,煉不到第九重啊。。
linux的入口是一段匯編代碼,用於基本的硬件設置和建立臨時頁表,對於
ARM LINUX是 linux/arch/arm/kernle/head-armv.S, 走!
#if defined(CONFIG_MX1)
mov r1, #MACH_TYPE_MX1
#endif
這第一句話好像就讓人看不懂,好像葵花寶典開頭的八個字:欲練神功。。。。
那來的MACH_TYPE_MX1?其實,在head-armv.S
中的一項重要工作就是設置內核的臨時頁表,不然mmu開起來也玩不轉,但是內核怎麼知道如何映射內存呢?linux的內核將映射到虛地址0xCxxx xxxx處,但他怎麼知道把哪一片ram映射過去呢?
因為不通的系統有不通的內存影像,所以,LINUX約定,內核代碼開始的時候,
R1放的是系統目標平台的代號,對於一些常見的,標准的平台,內核已經提供了支持,只要在編譯的時候選中就行了,例如對X86平台,內核是從物理地址1M開始映射的。如果老兄是自己攢的平台,只好麻煩你自己寫了。
小弟拿人錢財,與人消災,用的是摩托的MX1,只好自己寫了,定義了#MACH_TYPE_MX1,當然,還要寫一個描述平台的數據結構:
MACHINE_START(MX1ADS, "Motorola MX1ADS")
MAINTAINER("SPS Motorola")
BOOT_MEM(0x08000000, 0x00200000, 0xf0200000)
FIXUP(mx1ads_fixup)
MAPIO(mx1ads_map_io)
INITIRQ(mx1ads_init_irq)
MACHINE_END
看起來怪怪的,但現在大家只要知道他定義了基本的內存映象:RAM從0x08000000開始,i/o空間從0x00200000開始,i/o空間映射到虛擬地址空間
0xf0200000開始處。摩托的芯片i/o和內存是統一編址的。
其他的項,在下面的初始化過程中會逐個介紹到。
好了好了,再看下面的指令:
mov r0, #F_BIT I_BIT MODE_SVC @ make sure svc mode //設置為SVC模式,允許中斷和快速中斷
//此處設定系統的工作狀態,arm有7種狀態
//每種狀態有自己的堆棧
msr cpsr_c, r0 @ and all irqs diabled
bl __lookup_processor_type
//定義處理器相關信息,如value, mask, mmuflags,
//放在proc.info段中
//__lookup_processor_type 取得這些信息,在下面
//__lookup_architecture_type 中用
這一段是查詢處理器的種類,大家知道arm有arm7, arm9等類型,如何區分呢?
在arm協處理器中有一個只讀寄存器,存放處理器相關信息。__lookup_processor_type將返回如下的結構:
__arm920_proc_info:
.long 0x41009200 //CPU id
.long 0xff00fff0 //cpu mask
.long 0x00000c1e @ mmuflags
b __arm920_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP HWCAP_HALF HWCAP_26BIT
.long cpu_arm920_info
.long arm920_processor_functions
第一項是CPU id,將與協處理器中讀出的id作比較,其余的都是與處理器相關的
信息,到下面初始化的過程中自然會用到。。
第五回終。。。
長篇連載--arm linux演藝---第六回
--------------------------------------------------------------------------------
查詢到了處理器類型和系統的內存映像後就要進入初始化過程中比較關鍵的一步了,開始設置mmu,但首先要設置一個臨時的內核頁表,映射4m的內存,這在初始化過程中是足夠了:
//r5=0800 0000 ram起始地址 r6=0020 0000 io地址,r7=f020 0000 虛io
teq r7, #0 @ invalid architecture?
moveq r0, #'a' @ yes, error 'a'
beq __error
bl __create_page_tables
其中__create_page_tables為:
__create_page_tables:
pgtbl r4
//r4=0800 4000 臨時頁表的起始地址
//r5=0800 0000, ram的起始地址
//r6=0020 0000, i/o寄存器空間的起始地址
//r7=0000 3c08
//r8=0000 0c1e
//the page table in 0800 4000 is just temp base page, when init_task's sweaper_page_dir ready,
// the temp page will be useless
// the high 12 bit of virtual address is base table index, so we need 4kx4 = 16k temp base page,
mov r0, r4
mov r3, #0
add r2, r0, #0x4000 @ 16k of page table
1: str r3, [r0], #4 @ Clear page table
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r2
bne 1b
/*
* Create identity mapping for first MB of kernel.
* This is marked cacheable and bufferable.
*
* The identity mapping will be removed by
*/
// 由於linux編譯的地址是0xC0008000,load的地址是0x08008000,我們需要將虛地址0xC0008000映射到0800800一段
//同時,由於部分代碼也要直接訪問0x08008000,所以0x08008000對應的表項也要填充
// 頁表中的表象為section,AP=11表示任何模式下可訪問,domain為0。
add r3, r8, r5 @ mmuflags + start of RAM
//r3=0800 0c1e
add r0, r4, r5, lsr #18
//r0=0800 4200
str r3, [r0] @ identity mapping
//*0800 4200 = 0800 0c1e 0x200表象 對應的是0800 0000 的1m
/*
* Now setup the pagetables for our kernel direct
* mapped region. We round TEXTADDR down to the
* nearest megabyte boundary.
*/
//下面是映射4M
add r0, r4, #(TEXTADDR & 0xfff00000) >> 18 @ start of kernel
//r0 = r4+ 0x3000 = 0800 4000 + 3000 = 0800 7000
str r3, [r0], #4 @ PAGE_OFFSET + 0MB
//*0800 7004 = 0800 0c1e
add r3, r3, #1 << 20
//r3=0810 0c1e
str r3, [r0], #4 @ PAGE_OFFSET + 1MB
//*0800 7008 = 0810 0c1e
add r3, r3, #1 << 20
str r3, [r0], #4
//*0800 700c = 0820 0c1e @ PAGE_OFFSET + 2MB
add r3, r3, #1 << 20
str r3, [r0], #4 @ PAGE_OFFSET + 3MB
//*0800 7010 = 0830 0c1e
bic r8, r8, #0x0c @ turn off cacheable
//r8=0000 0c12 @ and bufferable bits
mov pc, lr //子程序返回。
下一回就要開始打開mmu的操作了
長篇連載--arm linux演藝---第七回
--------------------------------------------------------------------------------
上回書講到已經設置好了內核的頁表,然後要跳轉到__arm920_setup,
這個函數在arch/arm/mm/proc-arm929.s
__arm920_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4@ drain write buffer on v4
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
mcr p15, 0, r4, c2, c0 @ load page table pointer
mov r0, #0x1f @ Domains 0, 1 = client
mcr p15, 0, r0, c3, c0 @ load domain Access register
mrc p15, 0, r0, c1, c0 @ get control register v4
/*
* Clear out 'unwanted' bits (then put them in if we need them)
*/
@ VI ZFRS BLDP WCAM
bic r0, r0, #0x0e00
bic r0, r0, #0x0002
bic r0, r0, #0x000c
bic r0, r0, #0x1000 @ ...0 000. .... 000.
/*
* Turn on what we want
*/
orr r0, r0, #0x0031
orr r0, r0, #0x2100 @ ..1. ...1 ..11 ...1
#ifdef CONFIG_CPU_ARM920_D_CACHE_ON
orr r0, r0, #0x0004 @ .... .... .... .1..
#endif
#ifdef CONFIG_CPU_ARM920_I_CACHE_ON
orr r0, r0, #0x1000 @ ...1 .... .... ....
#endif
mov pc, lr
這一段首先關閉i,d cache,清除write buffer ,然後設置頁目錄地址,設置
domain的保護,在上節中,注意到頁目錄項的domain都是0,domain寄存器中
的domain 0 對應的是0b11,表示訪問模式為manager,不受限制。
接下來設置控制寄存器,打開d,i cache和mmu
注意arm的d cache必須和mmu一起打開,而i cache可以單獨打開
其實,cache和mmu的關系實在是緊密,每一個頁表項都有標志標示是否是
cacheable的,可以說本來就是設計一起使用的
最後,自函數返回後,有一句
mcr p15, 0, r0, c1, c0
使設置生效。
長篇連載--arm linux演藝---第八回
--------------------------------------------------------------------------------
上回我們講到arm靠初始化完成了,打開了cache,
到此為止,匯編部分的初始化代碼就差不多了,最後還有幾件事情做:
1。初始化BSS段,全部清零,BSS是全局變量區域。
2。保存與系統相關的信息:如
.long SYMBOL_NAME(compat)
.long SYMBOL_NAME(__bss_start)
.long SYMBOL_NAME(_end)
.long SYMBOL_NAME(processor_id)
.long SYMBOL_NAME(__machine_arch_type)
.long SYMBOL_NAME(cr_alignment)
.long SYMBOL_NAME(init_task_union)+8192
不用講,大家一看就明白意思
3。重新設置堆棧指針,指向init_task的堆棧。init_task是系統的第一個任務,init_task的堆棧在task strUCture的後8K,我們後面會看到。
4。最後就要跳到C代碼的start_kernel。
b SYMBOL_NAME(start_kernel)
現在讓我們來回憶一下目前的系統狀態:
臨時頁表已經建立,在0X08004000處,映射了4M,虛地址0XC000000被映射到0X08000000.
CACHE,MMU都已經打開。
堆棧用的是任務init_task的堆棧。
如果以為到了c代碼可以松一口氣的話,就大錯特措了,linux的c也不比匯編好懂多少,相反到掩蓋了匯編的一些和機器相關的部分,有時候更難懂。其實作為編寫操作系統的c代碼,只不過是匯編的另一種寫法,和機器代碼的聯系是很緊密的。
start_kernel在 /linux/init/main.c中定義:
asmlinkage void __init start_kernel(void)
{
char * command_line;
unsigned long mempages;
extern char saved_command_line[];
lock_kernel();
printk(linux_banner);
setup_arch(&command_line); //arm/kernel/setup.c
printk("Kernel command line: %s
", saved_command_line);
parse_options(command_line);
trap_init(); // arm/kernle/traps.c install
。。。。。。。。。
start_kernel中的函數個個都是重量級的,首先用printk(linux_banner);打出
系統版本號,這裡面就大有文章,系統才剛開張,你讓他打印到哪裡去呢?
先給大家交個底,以後到console的部分自然清楚,printk和printf不同,他首先輸出到系統的一個緩沖區內,大約4k,如果登記了console,則調用console->wirte函數輸出,否則就一直在buffer裡呆著。所以,用printk輸出的信息,如果超出了4k,會沖掉前面的。在系統引導起來後,用dmesg看的也就是這個buffer中的東東。
待續。。。。。
長篇連載--arm linux演藝---第九回
--------------------------------------------------------------------------------
下面就是一個重量級的函數:
setup_arch(&command_line); //arm/kernel/setup.c
完成內存映像的初始化,其中command_line是從bootloader中傳下來的。
void __init setup_arch(char **cmdline_p)
{
struct param_struct *params = NULL;
struct machine_desc *mdesc; //arch structure, for your ads, defined in include/arm-asm/mach/arch.h very long
struct meminfo meminfo;
char *from = default_command_line;
memset(&meminfo, 0, sizeof(meminfo));
首先把meminfo清零,有個背景介紹一下,從linux 2.4的內核開始,支持內存的節點(node),也就是可支持不連續的物理內存區域。這一點在嵌入式系統中很有用,例如對於SDRAM和FALSH,性質不同,可作為不同的內存節點。
meminfo結構定義如下:
/******************************************************/
#define NR_BANKS 4
//define the systen mem region, not consistent
struct meminfo {
int nr_banks;
unsigned long end;
struct {
unsigned long start;
unsigned long size;
int node;
} bank[NR_BANKS];
};
/******************************************************/
下面是:ROOT_DEV = MKDEV(0, 255);
ROOT_DEV是宏,指明啟動的設備,嵌入式系統中通常是flash disk.
這裡面有一個有趣的悖論:linux的設備都是在/dev/下,訪問這些設備文件需要設備驅動程序支持,而訪問設備文件才能取得設備號,才能加載驅動程序,那麼第一個設備驅動程序是怎麼加載呢?就是ROOT_DEV, 不需要訪問設備文件,直接指定設備號。
下面我們准備初始化真正的內核頁表,而不再是臨時的了。
首先還是取得當前系統的內存映像:
mdesc = setup_architecture(machine_arch_type);
//find the machine type in mach-integrator/arch.c
//the ads name, mem map, io map
返回如下結構:
mach-integrator/arch.c
MACHINE_START(INTEGRATOR, "Motorola MX1ADS")
MAINTAINER("ARM Ltd/Deep Blue Solutions Ltd")
BOOT_MEM(0x08000000, 0x00200000, 0xf0200000)
FIXUP(integrator_fixup)
MAPIO(integrator_map_io)
INITIRQ(integrator_init_irq)
MACHINE_END
我們在前面介紹過這個結構,不過這次用它可是玩真的了。
長篇連載--arm linux演藝---第十回
--------------------------------------------------------------------------------
書接上回,
下面是init_mm的初始化,init_mm定義在/arch/arm/kernel/init_task.c:
struct mm_struct init_mm = INIT_MM(init_mm);
從本回開始的相當一部分內容是和內存管理相關的,憑心而論,操作系統的
內存管理是很復雜的,牽扯到處理器的硬件細節和軟件算法,
限於篇幅所限制,請大家先仔細讀一讀arm mmu的部分,
中文參考資料:linux內核源代碼情景對話,
linux2.4.18原代碼分析。
init_mm.start_code = (unsigned long) &_text;
內核代碼段開始
init_mm.end_code = (unsigned long) &_etext;
內核代碼段結束
init_mm.end_data = (unsigned long) &_edata;
內核數據段開始
init_mm.brk = (unsigned long) &_end;
內核數據段結束
每一個任務都有一個mm_struct結構管理任務內存空間,init_mm
是內核的mm_struct,其中設置成員變量* mmap指向自己,
意味著內核只有一個內存管理結構,設置* pgd=swapper_pg_dir,
swapper_pg_dir是內核的頁目錄,在arm體系結構有16k,
所以init_mm定義了整個kernel的內存空間,下面我們會碰到內核
線程,所有的內核線程都使用內核空間,擁有和內核同樣的訪問
權限。
memcpy(saved_command_line, from, COMMAND_LINE_SIZE);
//clear command array
saved_command_line[COMMAND_LINE_SIZE-1] = '
unsigned long start;
unsigned long size;
int node;
} bank[NR_BANKS];
};
/******************************************************/
下面是:ROOT_DEV = MKDEV(0, 255);
ROOT_DEV是宏,指明啟動的設備,嵌入式系統中通常是flash disk.
這裡面有一個有趣的悖論:linux的設備都是在/dev/下,訪問這些設備文件需要設備驅動程序支持,而訪問設備文件才能取得設備號,才能加載驅動程序,那麼第一個設備驅動程序是怎麼加載呢?就是ROOT_DEV, 不需要訪問設備文件,直接指定設備號。
下面我們准備初始化真正的內核頁表,而不再是臨時的了。
首先還是取得當前系統的內存映像:
mdesc = setup_architecture(machine_arch_type);
//find the machine type in mach-integrator/arch.c
//the ads name, mem map, io map
返回如下結構:
mach-integrator/arch.c
MACHINE_START(INTEGRATOR, "Motorola MX1ADS")
MAINTAINER("ARM Ltd/Deep Blue Solutions Ltd")
BOOT_MEM(0x08000000, 0x00200000, 0xf0200000)
FIXUP(integrator_fixup)
MAPIO(integrator_map_io)
INITIRQ(integrator_init_irq)
MACHINE_END
我們在前面介紹過這個結構,不過這次用它可是玩真的了。
長篇連載--arm linux演藝---第十回
--------------------------------------------------------------------------------
書接上回,
下面是init_mm的初始化,init_mm定義在/arch/arm/kernel/init_task.c:
struct mm_struct init_mm = INIT_MM(init_mm);
從本回開始的相當一部分內容是和內存管理相關的,憑心而論,操作系統的
內存管理是很復雜的,牽扯到處理器的硬件細節和軟件算法,
限於篇幅所限制,請大家先仔細讀一讀arm mmu的部分,
中文參考資料:linux內核源代碼情景對話,
linux2.4.18原代碼分析。
init_mm.start_code = (unsigned long) &_text;
內核代碼段開始
init_mm.end_code = (unsigned long) &_etext;
內核代碼段結束
init_mm.end_data = (unsigned long) &_edata;
內核數據段開始
init_mm.brk = (unsigned long) &_end;
內核數據段結束
每一個任務都有一個mm_struct結構管理任務內存空間,init_mm
是內核的mm_struct,其中設置成員變量* mmap指向自己,
意味著內核只有一個內存管理結構,設置* pgd=swapper_pg_dir,
swapper_pg_dir是內核的頁目錄,在arm體系結構有16k,
所以init_mm定義了整個kernel的內存空間,下面我們會碰到內核
線程,所有的內核線程都使用內核空間,擁有和內核同樣的訪問
權限。
memcpy(saved_command_line, from, COMMAND_LINE_SIZE);
//clear command array
saved_command_line[COMMAND_LINE_SIZE-1] = '
parse_options(command_line);
trap_init(); // arm/kernle/traps.c install
。。。。。。。。。
start_kernel中的函數個個都是重量級的,首先用printk(linux_banner);打出
系統版本號,這裡面就大有文章,系統才剛開張,你讓他打印到哪裡去呢?
先給大家交個底,以後到console的部分自然清楚,printk和printf不同,他首先輸出到系統的一個緩沖區內,大約4k,如果登記了console,則調用console->wirte函數輸出,否則就一直在buffer裡呆著。所以,用printk輸出的信息,如果超出了4k,會沖掉前面的。在系統引導起來後,用dmesg看的也就是這個buffer中的東東。
待續。。。。。
長篇連載--arm linux演藝---第九回
--------------------------------------------------------------------------------
下面就是一個重量級的函數:
setup_arch(&command_line); //arm/kernel/setup.c
完成內存映像的初始化,其中command_line是從bootloader中傳下來的。
void __init setup_arch(char **cmdline_p)
{
struct param_struct *params = NULL;
struct machine_desc *mdesc; //arch structure, for your ads, defined in include/arm-asm/mach/arch.h very long
struct meminfo meminfo;
char *from = default_command_line;
memset(&meminfo, 0, sizeof(meminfo));
首先把meminfo清零,有個背景介紹一下,從linux 2.4的內核開始,支持內存的節點(node),也就是可支持不連續的物理內存區域。這一點在嵌入式系統中很有用,例如對於SDRAM和FALSH,性質不同,可作為不同的內存節點。
meminfo結構定義如下:
/******************************************************/
#define NR_BANKS 4
//define the systen mem region, not consistent
struct meminfo {
int nr_banks;
unsigned long end;
struct {
unsigned long start;
unsigned long size;
int node;
} bank[NR_BANKS];
};
/******************************************************/
下面是:ROOT_DEV = MKDEV(0, 255);
ROOT_DEV是宏,指明啟動的設備,嵌入式系統中通常是flash disk.
這裡面有一個有趣的悖論:linux的設備都是在/dev/下,訪問這些設備文件需要設備驅動程序支持,而訪問設備文件才能取得設備號,才能加載驅動程序,那麼第一個設備驅動程序是怎麼加載呢?就是ROOT_DEV, 不需要訪問設備文件,直接指定設備號。
下面我們准備初始化真正的內核頁表,而不再是臨時的了。
首先還是取得當前系統的內存映像:
mdesc = setup_architecture(machine_arch_type);
//find the machine type in mach-integrator/arch.c