您的位置:首页 > 运维架构 > Linux

arm linux kernel 从入口到start_kernel 的代码分析

2014-03-29 16:45 615 查看
参考资料:

《ARM体系结构与编程》

《嵌入式Linux应用开发完全手册》

Linux_Memory_Address_Mapping

http://www.chinaunix.net/old_jh/4/1021226.html

更多文档参见:http://pan.baidu.com/s/1mg3DbHQ

本文针对armlinux,从kernel的第一条指令开始分析,一直分析到进入start_kernel()函数.
我们当前以linux-2.6.19内核版本作为范例来分析,本文中所有的代码,前面都会加上行号以便于和源码进行对照,
例:
在文件init/main.c中:
00478:asmlinkagevoid__initstart_kernel(void)
前面的"00478:"表示478行,冒号后面的内容就是源码了.
在分析代码的过程中,我们使用缩进来表示各个代码的调用层次.
由于启动部分有一些代码是平台特定的,虽然大部分的平台所实现的功能都比较类似,但是为了更好的对code进行说明,对于平台相关的代码,我们选择at91(ARM926EJS)平台进行分析.
另外,本文是以uncompressedkernel开始讲解的.对于内核解压缩部分的code,在arch/arm/boot/compressed中,本文不做讨论.

一.启动条件

通常从系统上电到执行到linuxkenel这部分的任务是由bootloader来完成.
关于bootloader的内容,本文就不做过多介绍.
这里只讨论进入到linuxkernel的时候的一些限制条件,这一般是bootloader在最后跳转到kernel之前要完成的:
1.CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断都是禁止的;
2.MMU(内存管理单元)必须是关闭的,此时虚拟地址对物理地址;
3.数据cache(Datacache)必须是关闭的
4.指令cache(Instructioncache)可以是打开的,也可以是关闭的,这个没有强制要求;
5.CPU通用寄存器0(r0)必须是0;
6.CPU通用寄存器1(r1)必须是ARMLinuxmachinetype(关于machinetype,我们后面会有讲解)
7.CPU通用寄存器2(r2)必须是kernelparameterlist的物理地址(parameterlist是由bootloader传递给kernel,用来描述设备信息属性的列表,详细内容可参考"BootingARMLinux"文档).

二.startingkernel

首先,我们先对几个重要的宏进行说明(我们针对有MMU的情况):

宏位置默认值说明
KERNEL_RAM_ADDRarch/arm/kernel/head.S+260xc0008000kernel在RAM中的的虚拟地址
PAGE_OFFSETinclude/asm-arm/memeory.h+500xc0000000内核空间的起始虚拟地址
TEXT_OFFSETarch/arm/Makefile+1370x00008000内核相对于存储空间的偏移
TEXTADDRarch/arm/kernel/head.S+490xc0008000kernel的起始虚拟地址
PHYS_OFFSETinclude/asm-arm/arch-xxx/memory.h平台相关RAM的起始物理地址
内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中定义的:
00011:ENTRY(stext)
对于vmlinux.lds.S,这是ldscript文件,此文件的格式和汇编及C程序都不同,本文不对ldscript作过多的介绍,只对内核中用到的内容进行讲解,关于ld的详细内容可以参考ld.info
这里的ENTRY(stext)表示程序的入口是在符号stext.
而符号stext是在arch/arm/kernel/head.S中定义的:
下面我们将armlinuxboot的主要代码列出来进行一个概括的介绍,然后,我们会逐个的进行详细的讲解.
在arch/arm/kernel/head.S中72-94行,是armlinuxboot的主代码:
00072:ENTRY(stext)
00073:msrcpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE@ensuresvcmode
00074:@andirqsdisabled
00075:mrcp15,0,r9,c0,c0@getprocessorid
00076:bl__lookup_processor_type@r5=procinfor9=cpuid
00077:movsr10,r5@invalidprocessor(r5=0)?
00078:beq__error_p@yes,error'p'
00079:bl__lookup_machine_type@r5=machinfo
00080:movsr8,r5@invalidmachine(r5=0)?
00081:beq__error_a@yes,error'a'
00082:bl__create_page_tables
00083:
00084:/*
00085:*ThefollowingcallsCPUspecificcodeinapositionindependent
00086:*manner.Seearch/arm/mm/proc-*.Sfordetails.r10=baseof
00087:*xxx_proc_infostructureselectedby__lookup_machine_type
00088:*above.Onreturn,theCPUwillbereadyfortheMMUtobe
00089:*turnedon,andr0willholdtheCPUcontrolregistervalue.
00090:*/
00091:ldrr13,__switch_data@addresstojumptoafter
00092:@mmuhasbeenenabled
00093:adrlr,__enable_mmu@return(PIC)address
00094:addpc,r10,#PROCINFO_INITFUNC
其中,73行是确保kernel运行在SVC模式下,并且IRQ和FIRQ中断已经关闭,这样做是很谨慎的.
armlinuxboot的主线可以概括为以下几个步骤:
1.确定processortype(75-78行)
2.确定machinetype(79-81行)
3.创建页表(82行)
4.调用平台特定的__cpu_flush函数(在structproc_info_list中)(94行)
5.开启mmu(93行)
6.切换数据(91行)
最终跳转到start_kernel(在__switch_data的结束的时候,调用了bstart_kernel)
下面,我们按照这个主线,逐步的分析Code.

1.确定processortype

arch/arm/kernel/head.S中:
00075:mrcp15,0,r9,c0,c0@getprocessorid
00076:bl__lookup_processor_type@r5=procinfor9=cpuid
00077:movsr10,r5@invalidprocessor(r5=0)?
00078:beq__error_p@yes,error'p'
75行:通过cp15协处理器的c0寄存器来获得processorid的指令.关于cp15的详细内容可参考相关的arm手册
76行:跳转到__lookup_processor_type.在__lookup_processor_type中,会把processortype存储在r5中
77,78行:判断r5中的processortype是否是0,如果是0,说明是无效的processortype,跳转到__error_p(出错)
__lookup_processor_type函数主要是根据从cpu中获得的processorid和系统中的proc_info进行匹配,将匹配到的proc_info_list的基地址存到r5中,0表示没有找到对应的processortype.
下面我们分析__lookup_processor_type函数
arch/arm/kernel/head-common.S中:
00145:.type__lookup_processor_type,%function
00146:__lookup_processor_type:
00147:adrr3,3f
00148:ldmdar3,{r5-r7}
00149:subr3,r3,r7@getoffsetbetweenvirt&phys
00150:addr5,r5,r3@convertvirtaddressesto
00151:addr6,r6,r3@physicaladdressspace
00152:1:ldmiar5,{r3,r4}@value,mask
00153:andr4,r4,r9@maskwantedbits
00154:teqr3,r4
00155:beq2f
00156:addr5,r5,#PROC_INFO_SZ@sizeof(proc_info_list)
00157:cmpr5,r6
00158:blo1b
00159:movr5,#0@unknownprocessor
00160:2:movpc,lr
00161:
00162:/*
00163:*ThisprovidesaC-APIversionoftheabovefunction.
00164:*/
00165:ENTRY(lookup_processor_type)
00166:stmfdsp!,{r4-r7,r9,lr}
00167:movr9,r0
00168:bl__lookup_processor_type
00169:movr0,r5
00170:ldmfdsp!,{r4-r7,r9,pc}
00171:
00172:/*
00173:*Lookininclude/asm-arm/procinfo.handarch/arm/kernel/arch.[ch]for
00174:*moreinformationaboutthe__proc_infoand__arch_infostructures.
00175:*/
00176:.long__proc_info_begin
00177:.long__proc_info_end
00178:3:.long.
00179:.long__arch_info_begin
00180:.long__arch_info_end
145,146行是函数定义
147行:取地址指令,这里的3f是向前symbol名称是3的位置,即第178行,将该地址存入r3.
这里需要注意的是,adr指令取址,获得的是基于pc的一个地址,要格外注意,这个地址是3f处的"运行时地址",由于此时MMU还没有打开,也可以理解成物理地址(实地址).(详细内容可参考arm指令手册)
148行:因为r3中的地址是178行的位置的地址,因而执行完后:(ldmda表示栈指针递减,即r3递减,内存的地址编号较大的对应寄存器编号较大的)
r5存的是176行符号__proc_info_begin的地址;
r6存的是177行符号__proc_info_end的地址;
r7存的是3f处的地址.
这里需要注意链接地址和运行时地址的区别.r3存储的是运行时地址(物理地址),而r7中存储的是链接地址(虚拟地址).
__proc_info_begin和__proc_info_end是在arch/arm/kernel/vmlinux.lds.S中:
00031:__proc_info_begin=.;
00032:*(.proc.info.init)
00033:__proc_info_end=.;
这里是声明了两个变量:__proc_info_begin和__proc_info_end,其中等号后面的"."是locationcounter(详细内容请参考ld.info)
这三行的意思是:__proc_info_begin的位置上,放置所有文件中的".proc.info.init"段的内容,然后紧接着是__proc_info_end的位置.
kernel使用structproc_info_list来描述processortype.
在include/asm-arm/procinfo.h中:
00029:structproc_info_list{
00030:unsignedintcpu_val;
00031:unsignedintcpu_mask;
00032:unsignedlong__cpu_mm_mmu_flags;/*usedbyhead.S*/
00033:unsignedlong__cpu_io_mmu_flags;/*usedbyhead.S*/
00034:unsignedlong__cpu_flush;/*usedbyhead.S*/
00035:constchar*arch_name;
00036:constchar*elf_name;
00037:unsignedintelf_hwcap;
00038:constchar*cpu_name;
00039:structprocessor*proc;
00040:structcpu_tlb_fns*tlb;
00041:structcpu_user_fns*user;
00042:structcpu_cache_fns*cache;
00043:};
我们当前以at91为例,其processor是926的.
在arch/arm/mm/proc-arm926.S中:
00464:.section".proc.info.init",#alloc,#execinstr
00465:
00466:.type__arm926_proc_info,#object
00467:__arm926_proc_info:
00468:.long0x41069260@ARM926EJ-S(v5TEJ)
00469:.long0xff0ffff0
00470:.longPMD_TYPE_SECT|\
00471:PMD_SECT_BUFFERABLE|\
00472:PMD_SECT_CACHEABLE|\
00473:PMD_BIT4|\
00474:PMD_SECT_AP_WRITE|\
00475:PMD_SECT_AP_READ
00476:.longPMD_TYPE_SECT|\
00477:PMD_BIT4|\
00478:PMD_SECT_AP_WRITE|\
00479:PMD_SECT_AP_READ
00480:b__arm926_setup
00481:.longcpu_arch_name
00482:.longcpu_elf_name
00483:.longHWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_VFP|HWCAP_EDSP|HWCAP_JAVA
00484:.longcpu_arm926_name
00485:.longarm926_processor_functions
00486:.longv4wbi_tlb_fns
00487:.longv4wb_user_fns
00488:.longarm926_cache_fns
00489:.size__arm926_proc_info,.-__arm926_proc_info
从464行,我们可以看到__arm926_proc_info被放到了".proc.info.init"段中.
对照structproc_info_list,我们可以看到__cpu_flush的定义是在480行,即__arm926_setup.(我们将在"4.调用平台特定的__cpu_flush函数"一节中详细分析这部分的内容.)
从以上的内容我们可以看出:r5中的__proc_info_begin是proc_info_list的起始地址,r6中的__proc_info_end是proc_info_list的结束地址.
149行:从上面的分析我们可以知道r3中存储的是3f处的物理地址,而r7存储的是3f处的虚拟地址,这一行是计算当前程序运行的物理地址和虚拟地址的差值,将其保存到r3中.
150行:将r5存储的虚拟地址(__proc_info_begin)转换成物理地址
151行:将r6存储的虚拟地址(__proc_info_end)转换成物理地址
152行:对照structproc_info_list,可以得知,这句是将当前proc_info的cpu_val和cpu_mask分别存r3,r4中
153行:r9中存储了processorid(arch/arm/kernel/head.S中的75行),与r4的cpu_mask进行逻辑与操作,得到我们需要的值
154行:将153行中得到的值与r3中的cpu_val进行比较
155行:如果相等,说明我们找到了对应的processortype,跳到160行,返回
156行:(如果不相等),将r5指向下一个proc_info,
157行:和r6比较,检查是否到了__proc_info_end.
158行:如果没有到__proc_info_end,表明还有proc_info配置,返回152行继续查找
159行:执行到这里,说明所有的proc_info都匹配过了,但是没有找到匹配的,将r5设置成0(unknownprocessor)
160行:返回

2.确定machinetype

arch/arm/kernel/head.S中:
00079:bl__lookup_machine_type@r5=machinfo
00080:movsr8,r5@invalidmachine(r5=0)?
00081:beq__error_a@yes,error'a'
79行:跳转到__lookup_machine_type函数,在__lookup_machine_type中,会把structmachine_desc的基地址(machinetype)存储在r5中
80,81行:将r5中的machine_desc的基地址存储到r8中,并判断r5是否是0,如果是0,说明是无效的machinetype,跳转到__error_a(出错)
__lookup_machine_type函数
下面我们分析__lookup_machine_type函数:
arch/arm/kernel/head-common.S中:
00176:.long__proc_info_begin
00177:.long__proc_info_end
00178:3:.long.
00179:.long__arch_info_begin
00180:.long__arch_info_end
00181:
00182:/*
00183:*Lookupmachinearchitectureinthelinker-buildlistofarchitectures.
00184:*Notethatwecan'tusetheabsoluteaddressesforthe__arch_info
00185:*listssincewearen'trunningwiththeMMUon(andtherefore,weare
00186:*notinthecorrectaddressspace).Wehavetocalculatetheoffset.
00187:*
00188:*r1=machinearchitecturenumber
00189:*Returns:
00190:*r3,r4,r6corrupted
00191:*r5=mach_infopointerinphysicaladdressspace
00192:*/
00193:.type__lookup_machine_type,%function
00194:__lookup_machine_type:
00195:adrr3,3b
00196:ldmiar3,{r4,r5,r6}
00197:subr3,r3,r4@getoffsetbetweenvirt&phys
00198:addr5,r5,r3@convertvirtaddressesto
00199:addr6,r6,r3@physicaladdressspace
00200:1:ldrr3,[r5,#MACHINFO_TYPE]@getmachinetype
00201:teqr3,r1@matchesloadernumber?
00202:beq2f@found
00203:addr5,r5,#SIZEOF_MACHINE_DESC@nextmachine_desc
00204:cmpr5,r6
00205:blo1b
00206:movr5,#0@unknownmachine
00207:2:movpc,lr
193,194行:函数声明
195行:取地址指令,这里的3b是向后symbol名称是3的位置,即第178行,将该地址存入r3.
和上面我们对__lookup_processor_type函数的分析相同,r3中存放的是3b处物理地址.
196行:r3是3b处的地址,因而执行完后:(ldmia表示栈是递增的,即r3递增,低内存地址对应小号寄存器)
r4存的是3b处的地址
r5存的是__arch_info_begin的地址
r6存的是__arch_info_end的地址
__arch_info_begin和__arch_info_end是在arch/arm/kernel/vmlinux.lds.S中:
00034:__arch_info_begin=.;
00035:*(.arch.info.init)
00036:__arch_info_end=.;
这里是声明了两个变量:__arch_info_begin和__arch_info_end,其中等号后面的"."是locationcounter(详细内容请参考ld.info)
这三行的意思是:__arch_info_begin的位置上,放置所有文件中的".arch.info.init"段的内容,然后紧接着是__arch_info_end的位置.
kernel使用structmachine_desc来描述machinetype.
在include/asm-arm/mach/arch.h中:
00017:structmachine_desc{
00018:/*
00019:*Note!Thefirstfourelementsareused
00020:*byassemblercodeinhead-armv.S
00021:*/
00022:unsignedintnr;/*architecturenumber*/
00023:unsignedintphys_io;/*startofphysicalio*/
00024:unsignedintio_pg_offst;/*byteoffsetforio
00025:*pagetabeentry*/
00026:
00027:constchar*name;/*architecturename*/
00028:unsignedlongboot_params;/*taggedlist*/
00029:
00030:unsignedintvideo_start;/*startofvideoRAM*/
00031:unsignedintvideo_end;/*endofvideoRAM*/
00032:
00033:unsignedintreserve_lp0:1;/*neverhaslp0*/
00034:unsignedintreserve_lp1:1;/*neverhaslp1*/
00035:unsignedintreserve_lp2:1;/*neverhaslp2*/
00036:unsignedintsoft_reboot:1;/*softreboot*/
00037:void(*fixup)(structmachine_desc*,
00038:structtag*,char**,
00039:structmeminfo*);
00040:void(*map_io)(void);/*IOmappingfunction*/
00041:void(*init_irq)(void);
00042:structsys_timer*timer;/*systemticktimer*/
00043:void(*init_machine)(void);
00044:};
00045:
00046:/*
00047:*Setofmacrostodefinearchitecturefeatures.Thisisbuiltinto
00048:*atablebythelinker.
00049:*/
00050:#defineMACHINE_START(_type,_name)\
00051:staticconststructmachine_desc__mach_desc_##_type\
00052:__attribute_used__\
00053:__attribute__((__section__(".arch.info.init"

))={\
00054:.nr=MACH_TYPE_##_type,\
00055:.name=_name,
00056:
00057:#defineMACHINE_END\
00058:};
内核中,一般使用宏MACHINE_START来定义machinetype.
对于at91,在arch/arm/mach-at91rm9200/board-ek.c中:
00137:MACHINE_START(AT91RM9200EK,"AtmelAT91RM9200-EK"


00138:/*Maintainer:SANPeople/Atmel*/
00139:.phys_io=AT91_BASE_SYS,
00140:.io_pg_offst=(AT91_VA_BASE_SYS>>1

&0xfffc,
00141:.boot_params=AT91_SDRAM_BASE+0x100,
00142:.timer=&at91rm9200_timer,
00143:.map_io=ek_map_io,
00144:.init_irq=ek_init_irq,
00145:.init_machine=ek_board_init,
00146:MACHINE_END
197行:r3中存储的是3b处的物理地址,而r4中存储的是3b处的虚拟地址,这里计算处物理地址和虚拟地址的差值,保存到r3中
198行:将r5存储的虚拟地址(__arch_info_begin)转换成物理地址
199行:将r6存储的虚拟地址(__arch_info_end)转换成物理地址
200行:MACHINFO_TYPE在arch/arm/kernel/asm-offset.c101行定义,这里是取structmachine_desc中的nr(architecturenumber)到r3中
201行:将r3中取到的machinetype和r1中的machinetype(见前面的"启动条件"

进行比较
202行:如果相同,说明找到了对应的machinetype,跳转到207行的2f处,此时r5中存储了对应的structmachine_desc的基地址
203行:(不相同),取下一个machine_desc的地址
204行:和r6进行比较,检查是否到了__arch_info_end.
205行:如果不相同,说明还有machine_desc,返回200行继续查找.
206行:执行到这里,说明所有的machind_desc都查找完了,并且没有找到匹配的,将r5设置成0(unknownmachine).
207行:返回

3.创建页表

通过前面的两步,我们已经确定了processortype和machinetype.
此时,一些特定寄存器的值如下所示:
r8=machineinfo(structmachine_desc的基地址)
r9=cpuid(通过cp15协处理器获得的cpuid)
r10=procinfo(structproc_info_list的基地址)
创建页表是通过函数__create_page_tables来实现的.
这里,我们使用的是arm的L1主页表,L1主页表也称为段页表(sectionpagetable)
L1主页表将4GB的地址空间分成若干个1MB的段(section),因此L1页表包含4096个页表项(sectionentry).每个页表项是32bits(4bytes)
因而L1主页表占用4096*4=16k的内存空间.
对于ARM926,其L1sectionentry的格式为

可参考arm926EJSTRM):

(一级描述符的格式可以参考《ARM体系结构与编程》P180)




下面我们来分析__create_page_tables函数:
在arch/arm/kernel/head.S中:
00206:.type__create_page_tables,%function
00207:__create_page_tables:
00208:pgtblr4@pagetableaddress
00209:
00210:/*
00211:*Clearthe16Klevel1swapperpagetable
00212:*/
00213:movr0,r4
00214:movr3,#0
00215:addr6,r0,#0x4000
00216:1:strr3,[r0],#4
00217:strr3,[r0],#4
00218:strr3,[r0],#4
00219:strr3,[r0],#4
00220:teqr0,r6
00221:bne1b
00222:
00223:ldrr7,[r10,#PROCINFO_MM_MMUFLAGS]@mm_mmuflags
00224:
00225:/*
00226:*CreateidentitymappingforfirstMBofkernelto
00227:*caterfortheMMUenable.Thisidentitymapping
00228:*willberemovedbypaging_init().Weuseourcurrentprogram
00229:*countertodeterminecorrespondingsectionbaseaddress.
00230:*/
00231:movr6,pc,lsr#20@startofkernelsection
00232:orrr3,r7,r6,lsl#20@flags+kernelbase
00233:strr3,[r4,r6,lsl#2]@identitymapping
00234:
00235:/*
00236:*Nowsetupthepagetablesforourkerneldirect
00237:*mappedregion.
00238:*/
00239:addr0,r4,#(TEXTADDR&0xff000000)>>18@startofkernel
00240:strr3,[r0,#(TEXTADDR&0x00f00000)>>18]!
00241:
00242:ldrr6,=(_end-PAGE_OFFSET-1)@r6=numberofsections
00243:movr6,r6,lsr#20@neededforkernelminus1
00244:
00245:1:addr3,r3,#1<<20
00246:strr3,[r0,#4]!
00247:subsr6,r6,#1
00248:bgt1b
00249:
00250:/*
00251:*Thenmapfirst1MBoframincaseitcontainsourbootparams.
00252:*/
00253:addr0,r4,#PAGE_OFFSET>>18
00254:orrr6,r7,#PHYS_OFFSET
00255:strr6,[r0]
...
00314:movpc,lr
00315:.ltorg
206,207行:函数声明
208行:通过宏pgtbl将r4设置成页表的基地址(物理地址)
宏pgtbl在arch/arm/kernel/head.S中:
00042:.macropgtbl,rd
00043:ldr\rd,=(__virt_to_phys(KERNEL_RAM_ADDR-0x4000))
00044:.endm
可以看到,页表是位于KERNEL_RAM_ADDR下面16k的位置
宏__virt_to_phys是在incude/asm-arm/memory.h中:
00125:#ifndef__virt_to_phys
00126:#define__virt_to_phys(x)((x)-PAGE_OFFSET+PHYS_OFFSET)
00127:#define__phys_to_virt(x)((x)-PHYS_OFFSET+PAGE_OFFSET)
00128:#endif
下面从213行-221行,是将这16k的页表清0.
213行:r0=r4,将页表基地址存在r0中
214行:将r3置成0
215行:r6=页表基地址+16k,可以看到这是页表的尾地址
216-221行:循环,从r0到r6将这16k页表用0填充.
223行:获得proc_info_list的__cpu_mm_mmu_flags的值,并存储到r7中.(宏PROCINFO_MM_MMUFLAGS是在arch/arm/kernel/asm-offset.c中定义,值为8)(可以参考《嵌入式Linux应用完全开发手册》P118)(r7的值就是设置这个段描述符的权限、域字段,)

在arch/arm/mm/proc-arm926.S中:


00464:.section".proc.info.init",#alloc,#execinstr


00465:


00466:.type__arm926_proc_info,#object


00467:__arm926_proc_info:


00468:.long0x41069260@ARM926EJ-S(v5TEJ)


00469:.long0xff0ffff0


00470:.longPMD_TYPE_SECT|\


00471:PMD_SECT_BUFFERABLE|\


00472:PMD_SECT_CACHEABLE|\


00473:PMD_BIT4|\


00474:PMD_SECT_AP_WRITE|\


00475:PMD_SECT_AP_READ


00476:.longPMD_TYPE_SECT|\


00477:PMD_BIT4|\


00478:PMD_SECT_AP_WRITE|\


00479:PMD_SECT_AP_READ


00480:b__arm926_setup


00481:.longcpu_arch_name


00482:.longcpu_elf_name


00483:.longHWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_VFP|HWCAP_EDSP|HWCAP_JAVA


00484:.longcpu_arm926_name


00485:.longarm926_processor_functions


00486:.longv4wbi_tlb_fns


00487:.longv4wb_user_fns


00488:.longarm926_cache_fns


00489:.size__arm926_proc_info,.-__arm926_proc_info





231行:通过pc值的高12位(右移20位),得到kernel的section,并存储到r6中.因为当前是通过运行时地址得到的kernel的section,因而是物理地址.

232行:r3=r7|(r6<<20);flags+kernelbase,得到页表中需要设置的值.

233行:设置页表:mem[r4+r6*4]=r3

这里,因为页表的每一项是32bits(4bytes),所以要乘以4(<<2).

上面这三行,设置了kernel的第一个section(物理地址所在的pageentry)的页表项

239,240行:TEXTADDR是内核的起始虚拟地址(0xc0008000),这两行是设置kernel起始虚拟地址的页表项(注意,这里设置的页表项和上面的231-233行设置的页表项是不同的)

执行完后,r0指向kernel的第2个section的虚拟地址所在的页表项.

/*TODO:这两行的code很奇怪,为什么要先取TEXTADDR的高8位(Bit[31:24])0xff000000,然后再取后面的8位(Bit[23:20])0x00f00000*/
242行:这一行计算kernel镜像的大小(bytes).

_end是在vmlinux.lds.S中162行定义的,标记kernel的结束位置(虚拟地址):

00158.bss:{

00159__bss_start=.;/*BSS*/

00160*(.bss)

00161*(COMMON)

00162_end=.;

00163}

kernel的size=_end-PAGE_OFFSET-1,这里减1的原因是因为_end是locationcounter,它的地址是kernel镜像后面的一个byte的地址.

243行:地址右移20位,计算出kernel有多少sections(也就是有多少兆,因为段描述符每个可以映射1MiB的虚拟地址),并将结果存到r6中

245-248行:这几行用来填充kernel所有section虚拟地址对应的页表项.

253行:将r0设置为RAM第一兆虚拟地址的页表项地址(pageentry)

254行:r7中存储的是mmuflags,逻辑或上RAM的起始物理地址,得到RAM第一个MB页表项的值.

255行:设置RAM的第一个MB虚拟地址的页表.

上面这三行是用来设置RAM中第一兆虚拟地址的页表.之所以要设置这个页表项的原因是RAM的第一兆内存中可能存储着bootparams.

这样,kernel所需要的基本的页表我们都设置完了,如下图所示:





下面是linux-2.6.30.4中的arch/arm/kernel/head.S,代码有一些不同,但是效果一样:

/*


*linux/arch/arm/kernel/head.S


*


*Copyright(C)1994-2002RussellKing


*Copyright(c)2003ARMLimited


*AllRightsReserved


*


*Thisprogramisfreesoftware;youcanredistributeitand/ormodify


*itunderthetermsoftheGNUGeneralPublicLicenseversion2as


*publishedbytheFreeSoftwareFoundation.


*


*Kernelstartupcodeforall32-bitCPUs


*/


#include<linux/linkage.h>


#include<linux/init.h>




#include<asm/assembler.h>


#include<asm/domain.h>


#include<asm/ptrace.h>


#include<asm/asm-offsets.h>


#include<asm/memory.h>


#include<asm/thread_info.h>


#include<asm/system.h>




#if(PHYS_OFFSET&0x001fffff)


#error"PHYS_OFFSETmustbeataneven2MiBboundary!"


#endif




#defineKERNEL_RAM_VADDR(PAGE_OFFSET+TEXT_OFFSET)


#defineKERNEL_RAM_PADDR(PHYS_OFFSET+TEXT_OFFSET)






/*


*swapper_pg_diristhevirtualaddressoftheinitialpagetable.


*Weplacethepagetables16KbelowKERNEL_RAM_VADDR.Therefore,wemust


*makesurethatKERNEL_RAM_VADDRiscorrectlyset.Currently,weexpect


*theleastsignificant16bitstobe0x8000,butwecouldprobably


*relaxthisrestrictiontoKERNEL_RAM_VADDR>=PAGE_OFFSET+0x4000.


*/


#if(KERNEL_RAM_VADDR&0xffff)!=0x8000


#errorKERNEL_RAM_VADDRmuststartat0xXXXX8000


#endif




.globlswapper_pg_dir


.equswapper_pg_dir,KERNEL_RAM_VADDR-0x4000




.macropgtbl,rd


ldr\rd,=(KERNEL_RAM_PADDR-0x4000)


.endm




#ifdefCONFIG_XIP_KERNEL


#defineKERNEL_STARTXIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)


#defineKERNEL_END_edata_loc


#else


#defineKERNEL_STARTKERNEL_RAM_VADDR


#defineKERNEL_END_end


#endif




/*


*Kernelstartupentrypoint.


*---------------------------


*


*Thisisnormallycalledfromthedecompressorcode.Therequirements


*are:MMU=off,D-cache=off,I-cache=dontcare,r0=0,


*r1=machinenr,r2=atagspointer.


*


*Thiscodeismostlypositionindependent,soifyoulinkthekernelat


*0xc0008000,youcallthisat__pa(0xc0008000).


*


*Seelinux/arch/arm/tools/mach-typesforthecompletelistofmachine


*numbersforr1.


*


*We'retryingtokeepcraptoaminimum;DONOTaddanymachinespecific


*craphere-that'swhatthebootloader(orinextreme,welljustified


*circumstances,zImage)isfor.


*/


.section".text.head","ax"


ENTRY(stext)


msrcpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE@ensuresvcmode


@andirqsdisabled


mrcp15,0,r9,c0,c0@getprocessorid


bl__lookup_processor_type@r5=procinfor9=cpuid


movsr10,r5@invalidprocessor(r5=0)?


beq__error_p@yes,error'p'


bl__lookup_machine_type@r5=machinfo


movsr8,r5@invalidmachine(r5=0)?


beq__error_a@yes,error'a'


bl__vet_atags


bl__create_page_tables




/*


*ThefollowingcallsCPUspecificcodeinapositionindependent


*manner.Seearch/arm/mm/proc-*.Sfordetails.r10=baseof


*xxx_proc_infostructureselectedby__lookup_machine_type


*above.Onreturn,theCPUwillbereadyfortheMMUtobe


*turnedon,andr0willholdtheCPUcontrolregistervalue.


*/


ldrr13,__switch_data@addresstojumptoafter


@mmuhasbeenenabled


adrlr,__enable_mmu@return(PIC)address


addpc,r10,#PROCINFO_INITFUNC


ENDPROC(stext)




#ifdefined(CONFIG_SMP)


ENTRY(secondary_startup)


/*


*CommonentrypointforsecondaryCPUs.


*


*Ensurethatwe'reinSVCmode,andIRQsaredisabled.Lookup


*theprocessortype-thereisnoneedtocheckthemachinetype


*asithasalreadybeenvalidatedbytheprimaryprocessor.


*/


msrcpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE


mrcp15,0,r9,c0,c0@getprocessorid


bl__lookup_processor_type


movsr10,r5@invalidprocessor?


moveqr0,#'p'@yes,error'p'


beq__error




/*


*Usethepagetablessuppliedfrom__cpu_up.


*/


adrr4,__secondary_data


ldmiar4,{r5,r7,r13}@addresstojumptoafter


subr4,r4,r5@mmuhasbeenenabled


ldrr4,[r7,r4]@getsecondary_data.pgdir


adrlr,__enable_mmu@returnaddress


addpc,r10,#PROCINFO_INITFUNC@initialiseprocessor


@(returncontrolreg)


ENDPROC(secondary_startup)




/*


*r6=&secondary_data


*/


ENTRY(__secondary_switched)


ldrsp,[r7,#4]@getsecondary_data.stack


movfp,#0


bsecondary_start_kernel


ENDPROC(__secondary_switched)




.type__secondary_data,%object


__secondary_data:


.long.


.longsecondary_data


.long__secondary_switched


#endif/*defined(CONFIG_SMP)*/








/*


*SetupcommonbitsbeforefinallyenablingtheMMU.Essentially


*thisisjustloadingthepagetablepointeranddomainaccess


*registers.


*/


__enable_mmu:


#ifdefCONFIG_ALIGNMENT_TRAP


orrr0,r0,#CR_A


#else


bicr0,r0,#CR_A


#endif


#ifdefCONFIG_CPU_DCACHE_DISABLE


bicr0,r0,#CR_C


#endif


#ifdefCONFIG_CPU_BPREDICT_DISABLE


bicr0,r0,#CR_Z


#endif


#ifdefCONFIG_CPU_ICACHE_DISABLE


bicr0,r0,#CR_I


#endif


movr5,#(domain_val(DOMAIN_USER,DOMAIN_MANAGER)|\


domain_val(DOMAIN_KERNEL,DOMAIN_MANAGER)|\


domain_val(DOMAIN_TABLE,DOMAIN_MANAGER)|\


domain_val(DOMAIN_IO,DOMAIN_CLIENT))


mcrp15,0,r5,c3,c0,0@loaddomainaccessregister


mcrp15,0,r4,c2,c0,0@loadpagetablepointer


b__turn_mmu_on


ENDPROC(__enable_mmu)




/*


*EnabletheMMU.Thiscompletelychangesthestructureofthevisible


*memoryspace.Youwillnotbeabletotraceexecutionthroughthis.


*Ifyouhaveanenquiryaboutthis,*please*checkthelinux-arm-kernel


*mailinglistarchivesBEFOREsendinganotherposttothelist.


*


*r0=cp#15controlregister


*r13=*virtual*addresstojumptouponcompletion


*


*otherregistersdependonthefunctioncalleduponcompletion


*/


.align5


__turn_mmu_on:


movr0,r0


mcrp15,0,r0,c1,c0,0@writecontrolreg


mrcp15,0,r3,c0,c0,0@readidreg


movr3,r3


movr3,r3


movpc,r13


ENDPROC(__turn_mmu_on)






/*


*Setuptheinitialpagetables.Weonlysetupthebarest


*amountwhicharerequiredtogetthekernelrunning,which


*generallymeansmappinginthekernelcode.


*


*r8=machinfo


*r9=cpuid


*r10=procinfo


*


*Returns:


*r0,r3,r6,r7corrupted


*r4=physicalpagetableaddress


*/


__create_page_tables:


pgtblr4@pagetableaddress




/*


*Clearthe16Klevel1swapperpagetable


*/


movr0,r4


movr3,#0


addr6,r0,#0x4000


1:strr3,[r0],#4


strr3,[r0],#4


strr3,[r0],#4


strr3,[r0],#4


teqr0,r6


bne1b




ldrr7,[r10,#PROCINFO_MM_MMUFLAGS]@mm_mmuflags




/*


*CreateidentitymappingforfirstMBofkernelto


*caterfortheMMUenable.Thisidentitymapping


*willberemovedbypaging_init().Weuseourcurrentprogram


*countertodeterminecorrespondingsectionbaseaddress.


*/


movr6,pc,lsr#20@startofkernelsection


orrr3,r7,r6,lsl#20@flags+kernelbase


strr3,[r4,r6,lsl#2]@identitymapping




/*


*Nowsetupthepagetablesforourkerneldirect


*mappedregion.


*/


addr0,r4,#(KERNEL_START&0xff000000)>>18


strr3,[r0,#(KERNEL_START&0x00f00000)>>18]!


ldrr6,=(KERNEL_END-1)


addr0,r0,#4


addr6,r4,r6,lsr#18


1:cmpr0,r6


addr3,r3,#1<<20


strlsr3,[r0],#4


bls1b




#ifdefCONFIG_XIP_KERNEL


/*


*Mapsomeramtocoverour.dataand.bssareas.


*/


orrr3,r7,#(KERNEL_RAM_PADDR&0xff000000)


.if(KERNEL_RAM_PADDR&0x00f00000)


orrr3,r3,#(KERNEL_RAM_PADDR&0x00f00000)


.endif


addr0,r4,#(KERNEL_RAM_VADDR&0xff000000)>>18


strr3,[r0,#(KERNEL_RAM_VADDR&0x00f00000)>>18]!


ldrr6,=(_end-1)


addr0,r0,#4


addr6,r4,r6,lsr#18


1:cmpr0,r6


addr3,r3,#1<<20


strlsr3,[r0],#4


bls1b


#endif




/*


*Thenmapfirst1MBoframincaseitcontainsourbootparams.


*/


addr0,r4,#PAGE_OFFSET>>18


orrr6,r7,#(PHYS_OFFSET&0xff000000)


.if(PHYS_OFFSET&0x00f00000)


orrr6,r6,#(PHYS_OFFSET&0x00f00000)


.endif


strr6,[r0]




#ifdefCONFIG_DEBUG_LL


ldrr7,[r10,#PROCINFO_IO_MMUFLAGS]@io_mmuflags


/*


*MapinIOspaceforserialdebugging.


*Thisallowsdebugmessagestobeoutput


*viaaserialconsolebeforepaging_init.


*/


ldrr3,[r8,#MACHINFO_PGOFFIO]


addr0,r4,r3


rsbr3,r3,#0x4000@PTRS_PER_PGD*sizeof(long)


cmpr3,#0x0800@limitto512MB


movhir3,#0x0800


addr6,r0,r3


ldrr3,[r8,#MACHINFO_PHYSIO]


orrr3,r3,r7


1:strr3,[r0],#4


addr3,r3,#1<<20


teqr0,r6


bne1b


#ifdefined(CONFIG_ARCH_NETWINDER)||defined(CONFIG_ARCH_CATS)


/*


*Ifwe'reusingtheNetWinderorCATS,wealsoneedtomap


*inthe16550-typeserialportforthedebugmessages


*/


addr0,r4,#0xff000000>>18


orrr3,r7,#0x7c000000


strr3,[r0]


#endif


#ifdefCONFIG_ARCH_RPC


/*


*Mapinscreenat0x02000000&SCREEN2_BASE


*Similarreasonshere-fordebug.Thisis


*onlyforAcornRiscPCarchitectures.


*/


addr0,r4,#0x02000000>>18


orrr3,r7,#0x02000000


strr3,[r0]


addr0,r4,#0xd8000000>>18


strr3,[r0]


#endif


#endif


movpc,lr


ENDPROC(__create_page_tables)


.ltorg




#include"head-common.S"


下面仅对__create_page_tables进行简单注释:



[code]__create_page_tables:


pgtblr4@pagetableaddress




/*


*Clearthe16Klevel1swapperpagetable


*/


movr0,r4


movr3,#0


addr6,r0,#0x4000


1:strr3,[r0],#4


strr3,[r0],#4


strr3,[r0],#4


strr3,[r0],#4


teqr0,r6


bne1b




ldrr7,[r10,#PROCINFO_MM_MMUFLAGS]@mm_mmuflags




/*


*CreateidentitymappingforfirstMBofkernelto


*caterfortheMMUenable.Thisidentitymapping


*willberemovedbypaging_init().Weuseourcurrentprogram


*countertodeterminecorrespondingsectionbaseaddress.


下面三句完成:


以tq2440为例:




将虚拟机地址0x30000000~0x30100000映射到物理地址的0x30000000~0x30100000-1




*/


movr6,pc,lsr#20@startofkernelsection此时pc在0x30008000附近,r6=0x300


orrr3,r7,r6,lsl#20@flags+kernelbase构造段描述符的内容,为什么是20,参见《ARM体系结构与编程》


strr3,[r4,r6,lsl#2]@identitymapping填写页表项,完成映射






/*


*Nowsetupthepagetablesforourkerneldirect


*mappedregion.


KERNEL_START=0xC0008000


KERNEL_END=_end在链接脚本中,它的地址是kernel镜像后面的一个byte的地址




*/


addr0,r4,#(KERNEL_START&0xff000000)>>18


@为什么是18,因为一级页表每个描述符4个字节,r4是一个字节一个字节的加


strr3,[r0,#(KERNEL_START&0x00f00000)>>18]!


@上面完成的任务:将虚拟地址0xC0000000~0xC0100000-1映射到物理地址的0x30000000~0x30100000-1,因为r3


@中还是上次的值




ldrr6,=(KERNEL_END-1)@可以知道r6是一个虚拟地址,0xC0008000+解压后的内核大小-1


addr0,r0,#4@r0指向下一个待填写的页表项


addr6,r4,r6,lsr#18@r6指向最后一个页表项的地址ls后缀:无符号数小于等于


1:cmpr0,r6


addr3,r3,#1<<20


strlsr3,[r0],#4


bls1b


@通过循环,将内核所在的虚拟地址空间(0xC0008000+解压内核大小-1)映射到物理内存


@0x30008000+解压内核大小-1,接下来,mmu开启后,就不用考虑是不是位置无关码了。






/*


*Thenmapfirst1MBoframincaseitcontainsourbootparams.


个人感觉:


对于tq2440将内核加载到距离物理内存起始地址32KiB的地方时,也就是0x30008000,下面的代码


不要也可以,因为下面的目的就是将虚拟地址0xC0000000映射到物理地址的0x30000000,这个


上面的代码已经完成了。




但是,如果没有将内核加载到距离物理内存起始地址32KiB的地方,比如加载到0x30300000,即距离


物理内存起始地址3MiB的地方,下面的代码就有必要了,这种情况下,上面的代码仅仅完成了将:




虚拟地址0xC0300000~解压内核大小-1映射到物理内存0x30300000~解压内核大小-1,没有将uboot传给


内核的参数所在的内存区域(一般在距离物理内存起始地址16KiB范围内)进行映射。下面的代码完成了


这个任务,此时PAGE_OFFSET=0xc0000000PHYS_OFFSET=0x30000000


完成将虚拟地址0xC0000000~0xC0100000-1映射到物理地址的0x30000000~0x30100000-1


*/


addr0,r4,#PAGE_OFFSET>>18


orrr6,r7,#(PHYS_OFFSET&0xff000000)


.if(PHYS_OFFSET&0x00f00000)


orrr6,r6,#(PHYS_OFFSET&0x00f00000)


.endif


strr6,[r0]




movpc,lr


ENDPROC(__create_page_tables)


.ltorg

[/code]

4.调用平台特定的__cpu_flush函数

当__create_page_tables返回之后

此时,一些特定寄存器的值如下所示:

r4=pgtbl(pagetable的物理基地址)

r8=machineinfo(structmachine_desc的基地址)

r9=cpuid(通过cp15协处理器获得的cpuid)

r10=procinfo(structproc_info_list的基地址)

在我们需要在开启mmu之前,做一些必须的工作:清除ICache,清除DCache,清除Writebuffer,清除TLB等.

这些一般是通过cp15协处理器来实现的,并且是平台相关的.这就是__cpu_flush需要做的工作.

在arch/arm/kernel/head.S中

00091:ldrr13,__switch_data@addresstojumptoafter

00092:@mmuhasbeenenabled
00093:adrlr,__enable_mmu@return(PIC)address
00094:addpc,r10,#PROCINFO_INITFUNC
第91行:将r13设置为__switch_data的地址

第92行:将lr设置为__enable_mmu的地址

第93行:r10存储的是procinfo的基地址,PROCINFO_INITFUNC是在arch/arm/kernel/asm-offset.c中107行定义.

则该行将pc设为proc_info_list的__cpu_flush函数的地址,即下面跳转到该函数.

对于arm920t来说,PROCINFO_INITFUNC=16,此时r10+16---->b__arm920_setup

.section".proc.info.init",#alloc,#execinstr




.type__arm920_proc_info,#object


m920_proc_info:


.long0x41009200


.long0xff00fff0


.longPMD_TYPE_SECT|\


PMD_SECT_BUFFERABLE|\


PMD_SECT_CACHEABLE|\


PMD_BIT4|\


PMD_SECT_AP_WRITE|\


PMD_SECT_AP_READ


.longPMD_TYPE_SECT|\


PMD_BIT4|\


PMD_SECT_AP_WRITE|\


PMD_SECT_AP_READ


b__arm920_setup


.longcpu_arch_name


.longcpu_elf_name


.longHWCAP_SWP|HWCAP_HALF|HWCAP_THUMB


.longcpu_arm920_name


.longarm920_processor_functions


.longv4wbi_tlb_fns


.longv4wb_user_fns


defCONFIG_CPU_DCACHE_WRITETHROUGH


.longarm920_cache_fns


e


.longv4wt_cache_fns


if


.size__arm920_proc_info,.-__arm920_proc_info


在分析__lookup_processor_type的时候,我们已经知道,对于ARM926EJS来说,其__cpu_flush指向的是函数__arm926_setup

下面我们来分析函数__arm926_setup

在arch/arm/mm/proc-arm926.S中:

00391:.type__arm926_setup,#function

00392:__arm926_setup:

00393:movr0,#0

00394:mcrp15,0,r0,c7,c7@invalidateI,Dcachesonv4

00395:mcrp15,0,r0,c7,c10,4@drainwritebufferonv4

00396:#ifdefCONFIG_MMU

00397:mcrp15,0,r0,c8,c7@invalidateI,DTLBsonv4

00398:#endif

00399:

00400:

00401:#ifdefCONFIG_CPU_DCACHE_WRITETHROUGH

00402:movr0,#4@disablewrite-backoncachesexplicitly

00403:mcrp15,7,r0,c15,c0,0

00404:#endif

00405:

00406:adrr5,arm926_crval

00407:ldmiar5,{r5,r6}

00408:mrcp15,0,r0,c1,c0@getcontrolregisterv4

00409:bicr0,r0,r5

00410:orrr0,r0,r6

00411:#ifdefCONFIG_CPU_CACHE_ROUND_ROBIN

00412:orrr0,r0,#0x4000@.1..............

00413:#endif

00414:movpc,lr
00415:.size__arm926_setup,.-__arm926_setup

00416:

00417:/*

00418:*R

00419:*.RVIZFRSBLDPWCAM

00420:*.0110001..110101

00421:*

00422:*/

00423:.typearm926_crval,#object

00424:arm926_crval:

00425:crvalclear=0x00007f3f,mmuset=0x00003135,ucset=0x00001134

第391,392行:是函数声明

第393行:将r0设置为0

第394行:清除(invalidate)InstructionCache和DataCache.

第395行:清除(drain)WriteBuffer.

第396-398行:如果有配置了MMU,则需要清除(invalidate)InstructionTLB和DataTLB

接下来,是对控制寄存器c1进行配置,请参考ARM926TRM.

第401-404行:如果配置了DataCache使用writethrough方式,需要关掉write-back.

第406行:取arm926_crval的地址到r5中,arm926_crval在第424行

第407行:这里我们需要看一下424和425行,其中用到了宏crval,crval是在arch/arm/mm/proc-macro.S中:

00053:.macrocrval,clear,mmuset,ucset

00054:#ifdefCONFIG_MMU

00055:.word\clear

00056:.word\mmuset

00057:#else

00058:.word\clear

00059:.word\ucset

00060:#endif

00061:.endm

配合425行,我们可以看出,首先在arm926_crval的地址处存放了clear的值,然后接下来的地址存放了mmuset的值(对于配置了MMU的情况)
所以,在407行中,我们将clear和mmuset的值分别存到了r5,r6中

第408行:获得控制寄存器c1的值

第409行:将r0中的clear(r5)对应的位都清除掉

第410行:设置r0中mmuset(r6)对应的位

第411-413行:如果配置了使用roundrobin方式,需要设置控制寄存器c1的Bit[16]

第412行:取lr的值到pc中.

而lr中的值存放的是__enable_mmu的地址(arch/arm/kernel/head.S93行),所以,接下来就是跳转到函数__enable_mmu

5.开启mmu

开启mmu是又函数__enable_mmu实现的.

在进入__enable_mmu的时候,r0中已经存放了控制寄存器c1的一些配置(在上一步中进行的设置),但是并没有真正的打开mmu,

在__enable_mmu中,我们将打开mmu.

此时,一些特定寄存器的值如下所示:

r0=c1parameters(用来配置控制寄存器的参数)
r4=pgtbl(pagetable的物理基地址)

r8=machineinfo(structmachine_desc的基地址)

r9=cpuid(通过cp15协处理器获得的cpuid)

r10=procinfo(structproc_info_list的基地址)

在arch/arm/kernel/head.S中:

00146:.type__enable_mmu,%function

00147:__enable_mmu:

00148:#ifdefCONFIG_ALIGNMENT_TRAP

00149:orrr0,r0,#CR_A

00150:#else

00151:bicr0,r0,#CR_A

00152:#endif

00153:#ifdefCONFIG_CPU_DCACHE_DISABLE

00154:bicr0,r0,#CR_C

00155:#endif

00156:#ifdefCONFIG_CPU_BPREDICT_DISABLE

00157:bicr0,r0,#CR_Z

00158:#endif

00159:#ifdefCONFIG_CPU_ICACHE_DISABLE

00160:bicr0,r0,#CR_I

00161:#endif

00162:movr5,#(domain_val(DOMAIN_USER,DOMAIN_MANAGER)|\

00163:domain_val(DOMAIN_KERNEL,DOMAIN_MANAGER)|\

00164:domain_val(DOMAIN_TABLE,DOMAIN_MANAGER)|\

00165:domain_val(DOMAIN_IO,DOMAIN_CLIENT))

00166:mcrp15,0,r5,c3,c0,0@loaddomainaccessregister

00167:mcrp15,0,r4,c2,c0,0@loadpagetablepointer

00168:b__turn_mmu_on

00169:

00170:/*

00171:*EnabletheMMU.Thiscompletelychangesthestructureofthevisible

00172:*memoryspace.Youwillnotbeabletotraceexecutionthroughthis.

00173:*Ifyouhaveanenquiryaboutthis,*please*checkthelinux-arm-kernel

00174:*mailinglistarchivesBEFOREsendinganotherposttothelist.

00175:*

00176:*r0=cp#15controlregister

00177:*r13=*virtual*addresstojumptouponcompletion

00178:*

00179:*otherregistersdependonthefunctioncalleduponcompletion

00180:*/

00181:.align5

00182:.type__turn_mmu_on,%function

00183:__turn_mmu_on:

00184:movr0,r0

00185:mcrp15,0,r0,c1,c0,0@writecontrolreg

00186:mrcp15,0,r3,c0,c0,0@readidreg

00187:movr3,r3

00188:movr3,r3

00189:movpc,r13

第146,147行:函数声明

第148-161行:根据相应的配置,设置r0中的相应的Bit.(r0将用来配置控制寄存器c1)

第162-165行:设置domain参数r5.(r5将用来配置domain)

第166行:配置domain(详细信息清参考arm相关手册)

第167行:配置页表在存储器中的位置(setttb).这里页表的基地址是r4,通过写cp15的c2寄存器来设置页表基地址.

第168行:跳转到__turn_mmu_on.从名称我们可以猜到,下面是要真正打开mmu了.

(继续向下看,我们会发现,__turn_mmu_on就下当前代码的下方,为什么要跳转一下呢?这是有原因的.goon)

第169-180行:空行和注释.这里的注释我们可以看到,r0是cp15控制寄存器的内容,r13存储了完成后需要跳转的虚拟地址(因为完成后mmu已经打开了,都是虚拟地址了).

第181行:.algin5这句是cacheline对齐.我们可以看到下面一行就是__turn_mmu_on,之所以

第182-183行:__turn_mmu_on的函数声明.这里我们可以看到,__turn_mmu_on是紧接着上面第168行的跳转指令的,只是中间在第181行多了一个cacheline对齐.

这么做的原因是:下面我们要进行真正的打开mmu操作了,我们要把打开mmu的操作放到一个单独的cacheline上.而在之前的"启动条件"一节我们说了,ICache是可以打开也可以关闭的,这里这么做的原因是要保证在ICache打开的时候,打开mmu的操作也能正常执行.

第184行:这是一个空操作,相当于nop.在arm中,nop操作经常用指令movrd,rd来实现.

注意:为什么这里要有一个nop,我思考了很长时间,这里是我的猜测,可能不是正确的:

因为之前设置了页表基地址(setttb),到下一行(185行)打开mmu操作,中间的指令序列是这样的:

setttb(第167行)

branch(第168行)

nop(第184行)

enablemmu(第185行)

对于arm的五级流水线:fetch-decode-execute-memory-write

他们执行的情况如下图所示:





这里需要说明的是,branch操作会在3个cycle中完成,并且会导致重新取指.

从这个图我们可以看出来,在enablemmu操作取指的时候,setttb操作刚好完成.

第185行:写cp15的控制寄存器c1,这里是打开mmu的操作,同时会打开cache等(根据r0相应的配置)

第186行:读取id寄存器.

第187-188行:两个nop.

第189行:取r13到pc中,我们前面已经看到了,r13中存储的是__switch_data(在arch/arm/kernel/head.S91行),下面会跳到__switch_data.

第187,188行的两个nop是非常重要的,因为在185行打开mmu操作之后,要等到3个cycle之后才会生效,这和arm的流水线有关系.

因而,在打开mmu操作之后的加了两个nop操作.

6.切换数据

在arch/arm/kernel/head-common.S中:

00014:.type__switch_data,%object

00015:__switch_data:

00016:.long__mmap_switched

00017:.long__data_loc@r4

00018:.long__data_start@r5

00019:.long__bss_start@r6

00020:.long_end@r7

00021:.longprocessor_id@r4

00022:.long__machine_arch_type@r5

00023:.longcr_alignment@r6

00024:.longinit_thread_union+THREAD_START_SP@sp

00025:

00026:/*

00027:*ThefollowingfragmentofcodeisexecutedwiththeMMUoninMMUmode,

00028:*andusesabsoluteaddresses;thisisnotpositionindependent.

00029:*

00030:*r0=cp#15controlregister

00031:*r1=machineID

00032:*r9=processorID

00033:*/

00034:.type__mmap_switched,%function

00035:__mmap_switched:

00036:adrr3,__switch_data+4

00037:

00038:ldmiar3!,{r4,r5,r6,r7}

00039:cmpr4,r5@Copydatasegmentifneeded

00040:1:cmpner5,r6

00041:ldrnefp,[r4],#4

00042:strnefp,[r5],#4

00043:bne1b

00044:

00045:movfp,#0@ClearBSS(andzerofp)

00046:1:cmpr6,r7

00047:strccfp,[r6],#4

00048:bcc1b

00049:

00050:ldmiar3,{r4,r5,r6,sp}

00051:strr9,[r4]@SaveprocessorID

00052:strr1,[r5]@Savemachinetype

00053:bicr4,r0,#CR_A@Clear'A'bit

00054:stmiar6,{r0,r4}@Savecontrolregistervalues

00055:bstart_kernel
第14,15行:函数声明

第16-24行:定义了一些地址,例如第16行存储的是__mmap_switched的地址,第17行存储的是__data_loc的地址......

第34,35行:函数__mmap_switched

第36行:取__switch_data+4的地址到r3.从上文可以看到这个地址就是第17行的地址.

第37行:依次取出从第17行到第20行的地址,存储到r4,r5,r6,r7中.并且累加r3的值.当执行完后,r3指向了第21行的位置.

对照上文,我们可以得知:

r4-__data_loc

r5-__data_start

r6-__bss_start

r7-_end

这几个符号都是在arch/arm/kernel/vmlinux.lds.S中定义的变量:

00102:#ifdefCONFIG_XIP_KERNEL

00103:__data_loc=ALIGN(4);/*locationinbinary*/

00104:.=PAGE_OFFSET+TEXT_OFFSET;

00105:#else

00106:.=ALIGN(THREAD_SIZE);

00107:__data_loc=.;

00108:#endif

00109:

00110:.data:AT(__data_loc){

00111:__data_start=.;/*addressinmemory*/

00112:

00113:/*

00114:*first,theinittaskunion,aligned

00115:*toan8192byteboundary.

00116:*/

00117:*(.init.task)

......

00158:.bss:{

00159:__bss_start=.;/*BSS*/

00160:*(.bss)

00161:*(COMMON)

00162:_end=.;

00163:}

对于这四个变量,我们简单的介绍一下:

__data_loc是数据存放的位置

__data_start是数据开始的位置

__bss_start是bss开始的位置

_end是bss结束的位置,也是内核结束的位置

其中对第110行的指令讲解一下:这里定义了.data段,后面的AT(__data_loc)的意思是这部分的内容是在__data_loc中存储的(要注意,储存的位置和链接的位置是可以不相同的).

关于AT详细的信息请参考ld.info

第38行:比较__data_loc和__data_start

第39-43行:这几行是判断数据存储的位置和数据的开始的位置是否相等,如果不相等,则需要搬运数据,从__data_loc将数据搬到__data_start.

其中__bss_start是bss的开始的位置,也标志了data结束的位置,因而用其作为判断数据是否搬运完成.

第45-48行:是清除bss段的内容,将其都置成0.这里使用_end来判断bss的结束位置.

第50行:因为在第38行的时候,r3被更新到指向第21行的位置.因而这里取得r4,r5,r6,sp的值分别是:

r4-processor_id

r5-__machine_arch_type

r6-cr_alignment

sp-init_thread_union+THREAD_START_SP

processor_id和__machine_arch_type这两个变量是在arch/arm/kernel/setup.c中第62,63行中定义的.

cr_alignment是在arch/arm/kernel/entry-armv.S中定义的:

00182:.globlcr_alignment

00183:.globlcr_no_alignment

00184:cr_alignment:

00185:.space4

00186:cr_no_alignment:

00187:.space4

init_thread_union是init进程的基地址.在arch/arm/kernel/init_task.c中:

00033:unionthread_unioninit_thread_union

00034:__attribute__((__section__(".init.task")))=

00035:{INIT_THREAD_INFO(init_task)};
对照vmlnux.lds.S中的的117行,我们可以知道inittask是存放在.data段的开始8k,并且是THREAD_SIZE(8k)对齐的

第51行:将r9中存放的processorid(在arch/arm/kernel/head.S75行)赋值给变量processor_id

第52行:将r1中存放的machineid(见"启动条件"一节)赋值给变量__machine_arch_type

第53行:清除r0中的CR_A位并将值存到r4中.CR_A是在include/asm-arm/system.h21行定义,是cp15控制寄存器c1的Bit[1](alignmentfaultenable/disable)

第54行:这一行是存储控制寄存器的值.

从上面arch/arm/kernel/entry-armv.S的代码我们可以得知.

这一句是将r0存储到了cr_alignment中,将r4存储到了cr_no_alignment中.

第55行:最终跳转到start_kernel
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: