歡迎您光臨本站 註冊首頁

uboot+linux啟動過程

←手機掃碼閱讀     火星人 @ 2014-03-24 , reply:0

首先,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



上回書說到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代碼幹了些什麼。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("\n\nDBMX1 Linux Bootloader ver 0.2.0\n");
EUARTputString("Copyright (C) 2002 Motorola Ltd.\n\n");
EUARTputString((U8 *)cmdLine);
EUARTputString("\n\n");


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,小弟著也是不得已而為之啊。



如果直接輸入回車,進入kernel拷貝工作:



EUARTputString("Copying kernel from Flash to RAM ...\n");
count = 0x200000; // 2 Mbytes
pSource = (U32 *)0x0C100000;
pDestin = (U32 *)0x08008000;
do
{
*(pDestin++) = *(pSource++);
count -= 4;
} while (count > 0);
}


EUARTputString("Booting kernel ...\n\n");


這一段沒有什麼可說的,運行完后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的代碼。



好,從本節開始,我們走過了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_inf
.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作比較,其餘的都是與處理器相關的
信息,到下面初始化的過程中自然會用到。。


查詢到了處理器類型和系統的內存映像后就要進入初始化過程中比較關鍵的一步了,開始設置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的操作了


上回書講到已經設置好了內核的頁表,然後要跳轉到__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靠初始化完成了,打開了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\n", 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中的東東。



下面就是一個重量級的函數:
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


我們在前面介紹過這個結構,不過這次用它可是玩真的了。



書接上回,
下面是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] = '\0';
//set the end flag


parse_cmdline(&meminfo, cmdline_p, from);
//將bootloader的參數拷貝到cmdline_p,



bootmem_init(&meminfo);
定義在arm/mm/init.c
這個函數在內核結尾分一頁出來作點陣圖,根據具體系統的內存大小
映射整個ram


下面是一個非常重要的函數
paging_init(&meminfo, mdesc);
定義在arm/mm/init.c
創建內核頁表,映射所有物理內存和io空間,
對於不同的處理器,這個函數差別很大,


void __init paging_init(struct meminfo *mi, struct machine_desc *mdesc)
{
void *zero_page, *bad_page, *bad_table;
int node;


//static struct meminfo meminfo __initdata = { 0, };


memcpy(&meminfo, mi, sizeof(meminfo));


/*
* allocate what we need for the bad pages.
* note that we count on this going ok.
*/


zero_page = alloc_bootmem_low_pages(PAGE_SIZE);
bad_page = alloc_bootmem_low_pages(PAGE_SIZE);
bad_table = alloc_bootmem_low_pages(TABLE_SIZE);


分配三個頁出來,用於處理異常過程,在armlinux中,得到如下
地址:
zero_page=0xc0000000
bad page=0xc0001000
bad_table=0xc0002000



上回我們說到在paging_init中分配了三個頁:


zero_page=0xc0000000
bad page=0xc0001000
bad_table=0xc0002000


但是奇怪的很,在更新的linux代碼中只分配了一個
zero_page,而且在源代碼中找不到zero_page
用在什麼地方了,大家討論討論吧。


paging_init的主要工作是在
void __init memtable_init(struct meminfo *mi)
中完成的,為系統內存創建頁表:


meminfo結構如下:


struct meminfo {
int nr_banks;
unsigned long end;
struct {
unsigned long start;
unsigned long size;
int node;
} bank[NR_BANKS];
};


是用來紀錄系統中的內存區段的,因為在嵌入式
系統中並不是所有的內存都能映射,例如sdram只有
64m,flash 32m,而且不見得是連續的,所以用
meminfo紀錄這些區段。


void __init memtable_init(struct meminfo *mi)
{
struct map_desc *init_maps, *p, *q;
unsigned long address = 0;
int i;


init_maps = p = alloc_bootmem_low_pages(PAGE_SIZE);


其中map_desc定義為:


struct map_desc {
unsigned long virtual;
unsigned long physical;
unsigned long length;
int domain:4, //頁表的domain
prot_read:1, //保護標誌
prot_write:1, //防寫標誌
cacheable:1, //是否cache
bufferable:1, //是否用write buffer
last:1; //空
};init_maps


map_desc是區段及其屬性的定義,屬性位的意義請
參考ARM MMU的介紹。


下面對meminfo的區段進行遍歷,同時填寫init_maps
中的各項內容:
for (i = 0; i < mi->nr_banks; i++) {
if (mi->bank.size == 0)
continue;


p->physical = mi->bank.start;
p->virtual = __phys_to_virt(p->physical);
p->length = mi->bank.size;
p->domain = DOMAIN_KERNEL;
p->prot_read = 0;
p->prot_write = 1;
p->cacheable = 1; //可以CACHE
p->bufferable = 1; //使用write buffer
p ++; //下一個區段
}


如果系統有flash,
#ifdef FLUSH_BASE
p->physical = FLUSH_BASE_PHYS;
p->virtual = FLUSH_BASE;
p->length = PGDIR_SIZE;
p->domain = DOMAIN_KERNEL;
p->prot_read = 1;
p->prot_write = 0;
p->cacheable = 1;
p->bufferable = 1;


p ++;
#endif


其中的prot_read和prot_write是用來設置頁表的domain的,


下面就是逐個區段建立頁表:


q = init_maps;
do {
if (address < q->virtual || q == p) {
clear_mapping(address);
address += PGDIR_SIZE;
} else {
create_mapping(q);


address = q->virtual + q->length;
address = (address + PGDIR_SIZE - 1) & PGDIR_MASK;


q ++;
}
} while (address != 0);



上次說到memtable_init中初始化頁表的循環,
這個過程比較重要,我們看仔細些:



q = init_maps;
do {
if (address < q->virtual || q == p) {
//由於內核空間是從c000 0000開始,所以c000 0000
//以前的頁表項全部清空


clear_mapping(address);
address += PGDIR_SIZE;
//每個表項增加1m,這裡感到了section的好處
}


其中clear_mapping()是個宏,根據處理器的
不同,在920下被展開為


cpu_arm920_set_pmd(((pmd_t *)(((&init_mm )->pgd+
(( virt) >> 20 )))),((pmd_t){( 0 )}));


其中init_mm為內核的mm_struct,pgd指向
swapper_pg_dir,在arch/arm/kernel/init_task.c中定義


ENTRY(cpu_arm920_set_pmd)
#ifdef CONFIG_CPU_ARM920_WRITETHROUGH
eor r2, r1, #0x0a
tst r2, #0x0b
biceq r1, r1, #4
#endif
str r1, [r0]


把pmd_t填寫到頁表項中,由於pmd_t=0,
實際等於清除了這一項,由於d cache打開,
這一條指令實際並沒有寫回內存,而是寫到cache中


mcr p15, 0, r0, c7, c10, 1


把cache中 地址r0對應的內容寫回內存中,
這一條語句實際是寫到了write buffer中,
還沒有真正寫回內存。


mcr p15, 0, r0, c7, c10, 4


等待把write buffer中的內容寫回內存。在這之前core等待


mov pc, lr


在這裡我們看到,由於頁表的內容十分關鍵,為了確保寫回內存,
採用了直接操作cache的方法。由於在arm core中,打開了d cache
則必定要用write buffer.所以還有wb的回寫問題。
由於考慮到效率,我們使用了cache和buffer,
所以在某些地方要用指令保證數據被及時寫回。



下面映射c000 0000後面的頁表


else {
create_mapping(q);


address = q->virtual + q->length;
address = (address + PGDIR_SIZE - 1) & PGDIR_MASK;


q ++;
}
} while (address != 0);



create_mapping也在mm-armv.c中定義;


static void __init create_mapping(struct map_desc *md)
{
unsigned long virt, length;
int prot_sect, prot_pte;
long off;


prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
(md->prot_read ? L_PTE_USER : 0) |
(md->prot_write ? L_PTE_WRITE : 0) |
(md->cacheable ? L_PTE_CACHEABLE : 0) |
(md->bufferable ? L_PTE_BUFFERABLE : 0);


prot_sect = PMD_TYPE_SECT | PMD_DOMAIN(md->domain) |
(md->prot_read ? PMD_SECT_AP_READ : 0) |
(md->prot_write ? PMD_SECT_AP_WRITE : 0) |
(md->cacheable ? PMD_SECT_CACHEABLE : 0) |
(md->bufferable ? PMD_SECT_BUFFERABLE : 0);


由於arm中section表項的許可權位和page表項的位置不同,
所以根據struct map_desc 中的保護標誌,分別計算頁表項
中的AP,domain,CB標誌位。


有一段時間沒有寫了,道歉先,前一段時間在做arm linux的xip,終於找到了
在flash中運行kernel的方法,同時對系統的存儲管理
的理解更深了一層,我們繼續從上回的create_mapping往下看:


while ((virt & 0xfffff || (virt + off) & 0xfffff) && length >= PAGE_SIZE) {
alloc_init_page(virt, virt + off, md->domain, prot_pte);


virt += PAGE_SIZE;
length -= PAGE_SIZE;
}


while (length >= PGDIR_SIZE) {
alloc_init_section(virt, virt + off, prot_sect);


virt += PGDIR_SIZE;
length -= PGDIR_SIZE;
}


while (length >= PAGE_SIZE) {
alloc_init_page(virt, virt + off, md->domain, prot_pte);


virt += PAGE_SIZE;
length -= PAGE_SIZE;
}
這3個循環的設計還是很巧妙的,create_mapping的作用是設置虛地址virt
到物理地址virt + off的映射頁目錄和頁表。arm提供了4種尺寸的頁表:
1M,4K,16K,64K,armlinux只用到了1M和4K兩種。


這3個while的作用分別是「掐頭「,「去尾「,「砍中間「。
第一個while是判斷要映射的地址長度是否大於1m,且是不是1m對齊,
如果不是,則需要創建頁表,例如,如果要映射的長度為1m零4k,則先要將「零頭「
去掉,4k的一段需要中間頁表,通過第一個while創建中間頁表,
而剩下的1M則交給第二個while循環。最後剩下的交給第三個while循環。



alloc_init_page分配並填充中間頁表項
static inline void
alloc_init_page(unsigned long virt, unsigned long phys, int domain, int prot)
{
pmd_t *pmdp;
pte_t *ptep;


pmdp = pmd_offset(pgd_offset_k(virt), virt);//返回頁目錄中virt對應的表項


if (pmd_none(*pmdp)) {//如果表項是空的,則分配一個中間頁表
pte_t *ptep = alloc_bootmem_low_pages(2 * PTRS_PER_PTE *
sizeof(pte_t));


ptep += PTRS_PER_PTE;
//設置頁目錄表項
set_pmd(pmdp, __mk_pmd(ptep, PMD_TYPE_TABLE | PMD_DOMAIN(domain)));
}
ptep = pte_offset(pmdp, virt);
//如果表項不是空的,則表項已經存在,只需要設置中間頁表表項
set_pte(ptep, mk_pte_phys(phys, __pgprot(prot)));
}


alloc_init_section只需要填充頁目錄項


alloc_init_section(unsigned long virt, unsigned long phys, int prot)
{
pmd_t pmd;


pmd_val(pmd) = phys | prot;//將物理地址和保護標誌合成頁目錄項


set_pmd(pmd_offset(pgd_offset_k(virt), virt), pmd);
}


通過create_mapping可為內核建立所有的地址映射,最後是映射中斷向量表
所在的區域:


init_maps->physical = virt_to_phys(init_maps);
init_maps->virtual = vectors_base();
init_maps->length = PAGE_SIZE;
init_maps->domain = DOMAIN_USER;
init_maps->prot_read = 0;
init_maps->prot_write = 0;
init_maps->cacheable = 1;
init_maps->bufferable = 0;


create_mapping(init_maps);


中斷向量表的虛地址init_maps,是用alloc_bootmem_low_pages分配的,
通常是在c000 8000前面的某一頁, vectors_base()是個宏,arm規定中斷
向量表的地址只能是0或ffff0000,在cp15中設置。所以上述代碼映射一頁到
0或ffff0000,下面我們還會看到,中斷處理程序中的彙編部分也被拷貝到
這一頁中。

[火星人 ] uboot+linux啟動過程已經有749次圍觀

http://coctec.com/docs/linux/show-post-135019.html