arm 嵌入式LINUX启动过程(1)
时间:11-09
来源:互联网
点击:
嵌入式系统上,参数的传入是要靠bootloader完成的,
pMem=(char*)0x083FF000;//参数字符串的目标存放地址
pCmdLine=(char*)&cmdLine;//定义的静态字符串
while((*(pMem++)=*(pCmdLine++))!=0);//拷贝
JumpToKernel((void*)0x8008000,0x083FF000)//跳转到内核
return(0);
JumpToKernel在前文中的start.S定义过:
JumpToKernel:
//jumptothecopycode(gettheargumentsright)
movpc,r0
.globalJumpToKernel0x
//r0=jumpaddress
//r1=argumentstouse(thesegetshifted)
由于arm-GCC的c参数调用的顺序是从左到右R0开始,所以R0是KERNKEL的地址,
r1是参数字符串的地址:
到此为止,为linux引导做的准备工作就结束了,下一回我们就正式进入linux的代码。
好,从本节开始,我们走过了bootloader的漫长征途,开始进入linux的内核:
说实话,linux宝典的确高深莫测,洋人花了十几年修炼,各种内功心法层处不穷。有些地方反复推敲也领悟不了其中奥妙,炼不到第九重啊。。
linux的入口是一段汇编代码,用于基本的硬件设置和建立临时页表,对于
ARMLINUX是linux/arch/arm/kernle/head-armv.S,走!
#ifdefined(CONFIG_MX1)
movr1,#MACH_TYPE_MX1
#endif
这第一句话好像就让人看不懂,好像葵花宝典开头的八个字:欲练神功。。。。
那来的MACH_TYPE_MX1?其实,在head-armv.S
中的一项重要工作就是设置内核的临时页表,不然mmu开起来也玩不转,但是内核怎么知道如何映射内存呢?linux的内核将映射到虚地址0xCxxxxxxx处,但他怎么知道把哪一片ram映射过去呢?
因为不通的系统有不通的内存影像,所以,LINUX约定,内核代码开始的时候,
R1放的是系统目标平台的代号,对于一些常见的,标准的平台,内核已经提供了支持,只要在编译的时候选中就行了,例如对X86平台,内核是从物理地址1M开始映射的。如果老兄是自己攒的平台,只好麻烦你自己写了。
小弟拿人钱财,与人消灾,用的是摩托的MX1,只好自己写了,定义了#MACH_TYPE_MX1,当然,还要写一个描述平台的数据结构:
MACHINE_START(MX1ADS,"MotorolaMX1ADS")
MAINTAINER("SPSMotorola")
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和内存是统一编址的。
其他的项,在下面的初始化过程中会逐个介绍到。
好了好了,再看下面的指令:
movr0,#F_BIT|I_BIT|MODE_SVC@makesuresvcmode//设置为SVC模式,允许中断和快速中断
//此处设定系统的工作状态,arm有7种状态
//每种状态有自己的堆栈
msrcpsr_c,r0@andallirqsdiabled
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
.long0x41009200//CPUid
.long0xff00fff0//cpumask
.long0x00000c1e@mmuflags
b__arm920_setup
.longcpu_arch_name
.longcpu_elf_name
.longHWCAP_SWP|HWCAP_HALF|HWCAP_26BIT
.longcpu_arm920_info
.longarm920_processor_functions
第一项是CPUid,将与协处理器中读出的id作比较,其余的都是与处理器相关的
信息,到下面初始化的过程中自然会用到。。
查询到了处理器类型和系统的内存映像后就要进入初始化过程中比较关键的一步了,开始设置mmu,但首先要设置一个临时的内核页表,映射4m的内存,这在初始化过程中是足够了:
//r5=08000000ram起始地址r6=00200000io地址,r7=f0200000虚io
teqr7,#0@invalidarchitecture?
moveqr0,#a@yes,errora
beq__error
bl__create_page_tables
其中__create_page_tables为:
__create_page_tables:
pgtblr4
//r4=08004000临时页表的起始地址
//r5=08000000,ram的起始地址
//r6=00200000,i/o寄存器空间的起始地址
//r7=00003c08
//r8=00000c1e
//thepagetablein08004000isjusttempbasepage,wheninit_taskssweaper_page_dirready,
//thetemppagewillbeuseless
//thehigh12bitofvirtualaddressisbasetableindex,soweneed4kx4=16ktempbasepage,
movr0,r4
movr3,#0
addr2,r0,#0x4000@16kofpagetable
1:strr3,[r0],#4@Clearpagetable
strr3,[r0],#4
strr3,[r0],#4
strr3,[r0],#4
teqr0,r2
bne1b
/*
*CreateidentitymappingforfirstMBofkernel.
*Thisismarkedcacheableandbufferable.
*
*Theidentitymappingwillberemovedby
*/
//由于linux编译的地址是0xC0008000,load的地址是0x08008000,我们需要将虚地址0xC0008000映射到0800800一段
//同时,由于部分代码也要直接访问0x08008000,所以0x08008000对应的表项也要填充
//页表中的表象为section,AP=11表示任何模式下可访问,domain为0。
addr3,r8,r5@mmuflags+startofRAM
//r3=08000c1e
addr0,r4,r5,lsr#18
//r0=08004200
strr3,[r0]@identitymapping
//*08004200=08000c1e0x200表象对应的是08000000的1m
/*
*Nowsetupthepagetablesforourkerneldirect
*mappedregion.WeroundTEXTADDRdowntothe
*nearestmegabyteboundary.
*/
//下面是映射4M
addr0,r4,#(TEXTADDR&0xfff00000)>>18@startofkernel
//r0=r4+0x3000=08004000+3000=08007000
strr3,[r0],#4@PAGE_OFFSET+0MB
//*08007004=08000c1e
addr3,r3,#1<20
//r3=08100c1e
strr3,[r0],#4@PAGE_OFFSET+1MB
//*08007008=08100c1e
addr3,r3,#1<20
strr3,[r0],#4
//*0800700c=08200c1e@PAGE_OFFSET+2MB
addr3,r3,#1<20
strr3,[r0],#4@PAGE_OFFSET+3MB
//*08007010=08300c1e
bicr8,r8,#0x0c@turnoffcacheable
//r8=00000c12@andbufferablebits
movpc,lr//子程序返回。
下一回就要开始打开mmu的操作了
上回书讲到已经设置好了内核的页表,然后要跳转到__arm920_setup,
这个函数在arch/arm/mm/proc-arm929.s
__arm920_setup:
movr0,#0
mcrp15,0,r0,c7,c7@invalidateI,Dcachesonv4
mcrp15,0,r0,c7,c10,4@drainwritebufferonv4
mcrp15,0,r0,c8,c7@invalidateI,DTLBsonv4
mcrp15,0,r4,c2,c0@loadpagetablepointer
movr0,#0x1f@Domains0,1=client
mcrp15,0,r0,c3,c0@loaddomainaccessregister
mrcp15,0,r0,c1,c0@getcontrolregisterv4
/*
*Clearoutunwantedbits(thenputtheminifweneedthem)
*/
@VIZFRSBLDPWCAM
bicr0,r0,#0x0e00
bicr0,r0,#0x0002
bicr0,r0,#0x000c
bicr0,r0,#0x1000@...0000.....000.
/*
*Turnonwhatwewant
*/
orrr0,r0,#0x0031
orrr0,r0,#0x2100@..1....1..11...1
#ifdefCONFIG_CPU_ARM920_D_CACHE_ON
orrr0,r0,#0x0004@.............1..
#endif
#ifdefCONFIG_CPU_ARM920_I_CACHE_ON
orrr0,r0,#0x1000@...1............
#endif
movpc,lr
这一段首先关闭i,dcache,清除writebuffer,然后设置页目录地址,设置
domain的保护,在上节中,注意到页目录项的domain都是0,domain寄存器中
的domain0对应的是0b11,表示访问模式为manager,不受限制。
接下来设置控制寄存器,打开d,icache和mmu
注意arm的dcache必须和mmu一起打开,而icache可以单独打开
其实,cache和mmu的关系实在是紧密,每一个页表项都有标志标示是否是
cacheable的,可以说本来就是设计一起使用的
最后,自函数返回后,有一句
mcrp15,0,r0,c1,c0
使设置生效。
上回我们讲到arm靠初始化完成了,打开了cache,
到此为止,汇编部分的初始化代码就差不多了,最后还有几件事情做:
1。初始化BSS段,全部清零,BSS是
pMem=(char*)0x083FF000;//参数字符串的目标存放地址
pCmdLine=(char*)&cmdLine;//定义的静态字符串
while((*(pMem++)=*(pCmdLine++))!=0);//拷贝
JumpToKernel((void*)0x8008000,0x083FF000)//跳转到内核
return(0);
JumpToKernel在前文中的start.S定义过:
JumpToKernel:
//jumptothecopycode(gettheargumentsright)
movpc,r0
.globalJumpToKernel0x
//r0=jumpaddress
//r1=argumentstouse(thesegetshifted)
由于arm-GCC的c参数调用的顺序是从左到右R0开始,所以R0是KERNKEL的地址,
r1是参数字符串的地址:
到此为止,为linux引导做的准备工作就结束了,下一回我们就正式进入linux的代码。
好,从本节开始,我们走过了bootloader的漫长征途,开始进入linux的内核:
说实话,linux宝典的确高深莫测,洋人花了十几年修炼,各种内功心法层处不穷。有些地方反复推敲也领悟不了其中奥妙,炼不到第九重啊。。
linux的入口是一段汇编代码,用于基本的硬件设置和建立临时页表,对于
ARMLINUX是linux/arch/arm/kernle/head-armv.S,走!
#ifdefined(CONFIG_MX1)
movr1,#MACH_TYPE_MX1
#endif
这第一句话好像就让人看不懂,好像葵花宝典开头的八个字:欲练神功。。。。
那来的MACH_TYPE_MX1?其实,在head-armv.S
中的一项重要工作就是设置内核的临时页表,不然mmu开起来也玩不转,但是内核怎么知道如何映射内存呢?linux的内核将映射到虚地址0xCxxxxxxx处,但他怎么知道把哪一片ram映射过去呢?
因为不通的系统有不通的内存影像,所以,LINUX约定,内核代码开始的时候,
R1放的是系统目标平台的代号,对于一些常见的,标准的平台,内核已经提供了支持,只要在编译的时候选中就行了,例如对X86平台,内核是从物理地址1M开始映射的。如果老兄是自己攒的平台,只好麻烦你自己写了。
小弟拿人钱财,与人消灾,用的是摩托的MX1,只好自己写了,定义了#MACH_TYPE_MX1,当然,还要写一个描述平台的数据结构:
MACHINE_START(MX1ADS,"MotorolaMX1ADS")
MAINTAINER("SPSMotorola")
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和内存是统一编址的。
其他的项,在下面的初始化过程中会逐个介绍到。
好了好了,再看下面的指令:
movr0,#F_BIT|I_BIT|MODE_SVC@makesuresvcmode//设置为SVC模式,允许中断和快速中断
//此处设定系统的工作状态,arm有7种状态
//每种状态有自己的堆栈
msrcpsr_c,r0@andallirqsdiabled
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
.long0x41009200//CPUid
.long0xff00fff0//cpumask
.long0x00000c1e@mmuflags
b__arm920_setup
.longcpu_arch_name
.longcpu_elf_name
.longHWCAP_SWP|HWCAP_HALF|HWCAP_26BIT
.longcpu_arm920_info
.longarm920_processor_functions
第一项是CPUid,将与协处理器中读出的id作比较,其余的都是与处理器相关的
信息,到下面初始化的过程中自然会用到。。
查询到了处理器类型和系统的内存映像后就要进入初始化过程中比较关键的一步了,开始设置mmu,但首先要设置一个临时的内核页表,映射4m的内存,这在初始化过程中是足够了:
//r5=08000000ram起始地址r6=00200000io地址,r7=f0200000虚io
teqr7,#0@invalidarchitecture?
moveqr0,#a@yes,errora
beq__error
bl__create_page_tables
其中__create_page_tables为:
__create_page_tables:
pgtblr4
//r4=08004000临时页表的起始地址
//r5=08000000,ram的起始地址
//r6=00200000,i/o寄存器空间的起始地址
//r7=00003c08
//r8=00000c1e
//thepagetablein08004000isjusttempbasepage,wheninit_taskssweaper_page_dirready,
//thetemppagewillbeuseless
//thehigh12bitofvirtualaddressisbasetableindex,soweneed4kx4=16ktempbasepage,
movr0,r4
movr3,#0
addr2,r0,#0x4000@16kofpagetable
1:strr3,[r0],#4@Clearpagetable
strr3,[r0],#4
strr3,[r0],#4
strr3,[r0],#4
teqr0,r2
bne1b
/*
*CreateidentitymappingforfirstMBofkernel.
*Thisismarkedcacheableandbufferable.
*
*Theidentitymappingwillberemovedby
*/
//由于linux编译的地址是0xC0008000,load的地址是0x08008000,我们需要将虚地址0xC0008000映射到0800800一段
//同时,由于部分代码也要直接访问0x08008000,所以0x08008000对应的表项也要填充
//页表中的表象为section,AP=11表示任何模式下可访问,domain为0。
addr3,r8,r5@mmuflags+startofRAM
//r3=08000c1e
addr0,r4,r5,lsr#18
//r0=08004200
strr3,[r0]@identitymapping
//*08004200=08000c1e0x200表象对应的是08000000的1m
/*
*Nowsetupthepagetablesforourkerneldirect
*mappedregion.WeroundTEXTADDRdowntothe
*nearestmegabyteboundary.
*/
//下面是映射4M
addr0,r4,#(TEXTADDR&0xfff00000)>>18@startofkernel
//r0=r4+0x3000=08004000+3000=08007000
strr3,[r0],#4@PAGE_OFFSET+0MB
//*08007004=08000c1e
addr3,r3,#1<20
//r3=08100c1e
strr3,[r0],#4@PAGE_OFFSET+1MB
//*08007008=08100c1e
addr3,r3,#1<20
strr3,[r0],#4
//*0800700c=08200c1e@PAGE_OFFSET+2MB
addr3,r3,#1<20
strr3,[r0],#4@PAGE_OFFSET+3MB
//*08007010=08300c1e
bicr8,r8,#0x0c@turnoffcacheable
//r8=00000c12@andbufferablebits
movpc,lr//子程序返回。
下一回就要开始打开mmu的操作了
上回书讲到已经设置好了内核的页表,然后要跳转到__arm920_setup,
这个函数在arch/arm/mm/proc-arm929.s
__arm920_setup:
movr0,#0
mcrp15,0,r0,c7,c7@invalidateI,Dcachesonv4
mcrp15,0,r0,c7,c10,4@drainwritebufferonv4
mcrp15,0,r0,c8,c7@invalidateI,DTLBsonv4
mcrp15,0,r4,c2,c0@loadpagetablepointer
movr0,#0x1f@Domains0,1=client
mcrp15,0,r0,c3,c0@loaddomainaccessregister
mrcp15,0,r0,c1,c0@getcontrolregisterv4
/*
*Clearoutunwantedbits(thenputtheminifweneedthem)
*/
@VIZFRSBLDPWCAM
bicr0,r0,#0x0e00
bicr0,r0,#0x0002
bicr0,r0,#0x000c
bicr0,r0,#0x1000@...0000.....000.
/*
*Turnonwhatwewant
*/
orrr0,r0,#0x0031
orrr0,r0,#0x2100@..1....1..11...1
#ifdefCONFIG_CPU_ARM920_D_CACHE_ON
orrr0,r0,#0x0004@.............1..
#endif
#ifdefCONFIG_CPU_ARM920_I_CACHE_ON
orrr0,r0,#0x1000@...1............
#endif
movpc,lr
这一段首先关闭i,dcache,清除writebuffer,然后设置页目录地址,设置
domain的保护,在上节中,注意到页目录项的domain都是0,domain寄存器中
的domain0对应的是0b11,表示访问模式为manager,不受限制。
接下来设置控制寄存器,打开d,icache和mmu
注意arm的dcache必须和mmu一起打开,而icache可以单独打开
其实,cache和mmu的关系实在是紧密,每一个页表项都有标志标示是否是
cacheable的,可以说本来就是设计一起使用的
最后,自函数返回后,有一句
mcrp15,0,r0,c1,c0
使设置生效。
上回我们讲到arm靠初始化完成了,打开了cache,
到此为止,汇编部分的初始化代码就差不多了,最后还有几件事情做:
1。初始化BSS段,全部清零,BSS是
arm嵌入式LINUX启动过 相关文章:
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)
- Windows CE 进程、线程和内存管理(二)(11-09)