ARM linux的启动部分源代码简略分析.docx

上传人:b****6 文档编号:8713853 上传时间:2023-05-14 格式:DOCX 页数:37 大小:32.40KB
下载 相关 举报
ARM linux的启动部分源代码简略分析.docx_第1页
第1页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第2页
第2页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第3页
第3页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第4页
第4页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第5页
第5页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第6页
第6页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第7页
第7页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第8页
第8页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第9页
第9页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第10页
第10页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第11页
第11页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第12页
第12页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第13页
第13页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第14页
第14页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第15页
第15页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第16页
第16页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第17页
第17页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第18页
第18页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第19页
第19页 / 共37页
ARM linux的启动部分源代码简略分析.docx_第20页
第20页 / 共37页
亲,该文档总共37页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

ARM linux的启动部分源代码简略分析.docx

《ARM linux的启动部分源代码简略分析.docx》由会员分享,可在线阅读,更多相关《ARM linux的启动部分源代码简略分析.docx(37页珍藏版)》请在冰点文库上搜索。

ARM linux的启动部分源代码简略分析.docx

ARMlinux的启动部分源代码简略分析

ARMlinux的启动部分源代码简略分析

 

ARMlinux的启动部分源代码简略分析 

以友善之臂的mini2440开发板为平台,以较新的内核linux-2.6.32.7版本为例,仅作说明之用。

当内核映像被加载到RAM之后,Bootloader的控制权被释放。

内核映像并不是可直接运行的目标代码,而是一个压缩过的zImage(小内核)。

但是,也并非是zImage映像中的一切均被压缩了,映像中包含未被压缩的部分,这部分中包含解压缩程序,解压缩程序会解压缩映像中被压缩的部分。

zImage使用gzip压缩的,它不仅仅是一个压缩文件,而且在这个文件的开头部分内嵌有gzip解压缩代码。

当zImage被调用时它从arch/arm/boot/compressed/head.S的start汇编例程开始执行。

这个例程进行一些基本的硬件设置,并调用arch/arm/boot/compressed/misc.c中的decompress_kernel()解压缩内核。

 

arch/arm/kernel/head.S文件是内核真正的启动入口点,一般是由解压缩内核的程序来调用的。

首先先看下对于运行这个文件的要求:

MMU=off;D-cache=off;I-cache=无所谓,开也可以,关也可以;r0=0;r1=机器号;r2=atags指针。

这段代码是位置无关的,所以,如果以地址0xC0008000来链接内核,那么就可以直接用__pa(0xc0008000)地址来调用这里的代码。

 

其实,在这个(Linux内核中总共有多达几十个的以head.S命名的文件)head.S文件中的一项重要工作就是设置内核的临时页表,不然mmu开起来也玩不转,但是内核怎么知道如何映射内存呢?

linux的内核将映射到虚地址0xCxxx xxxx处,但他怎么知道在4GB的地址空间中有哪一片ram是可用的,从而可以映射过去呢?

 

因为不同的系统有不通的内存映像,所以,LINUX约定,要调用内核代码,一定要满足上面的调用要求,以为最初的内核代码提供一些最重要的关于机器的信息。

内核代码开始的时候,R1存放的是系统目标平台的代号,对于一些常见的,标准的平台,内核已经提供了支持,只要在编译的时候选中就行了,例如对X86平台,内核是从物理地址1M开始映射的。

好了好了,看下面的代码。

 arch/arm/kernel/head.S

ENTRY(stext)是这个文件的入口点。

最初的几行是这样的:

  setmode PSR_F_BIT|PSR_I_BIT|SVC_MODE,r9

@ensuresvcmode

@andirqsdisabled

//设置为SVC模式,关闭中断和快速中断 

//此处设定系统的工作状态为SVC,arm有7种状态每种状态

//都有自己的堆栈,SVC为管理模式,具有完全的权限,可以执行任意指令

//访问任意地址的内存

//setmode是一个宏,其定义为:

//.macrosetmode,mode,reg

//msr  cpsr_c,#\mode

//.endm

  mrc  p15,0,r9,c0,c0   @getprocessorid

  bl__lookup_processor_type    @r5=procinfor9=cpuid

  movs r10,r5           @invalidprocessor(r5=0)?

  beq  __error_p      @yes,error'p'

 

这几行是查询处理器的类型的,我们知道arm系列有很多型号,arm7、arm9、arm11、Cortex核等等类型,这么多型号要如何区分呢?

其实,在arm的15号协处理器(其实ARM暂时也就这么一个协处理器)中有一个只读寄存器,存放与处理器相关信息。

 

__lookup_processor_type是arch/arm/kernel/head-common.S文件中定义的一个例程,这个head-common.S用include命令被包含在head.S文件中。

其定义为:

__lookup_processor_type:

  adr  r3,3f

  ldmiar3,{r5-r7}

  add  r3,r3,#8

  sub  r3,r3,r7        @getoffsetbetweenvirt&phys

  add  r5,r5,r3        @convertvirtaddressesto

  add  r6,r6,r3        @physicaladdressspace

1:

ldmiar5,{r3,r4}      @value,mask

  and  r4,r4,r9        @maskwantedbits

  teq  r3,r4

  beq  2f

  add  r5,r5,#PROC_INFO_SZ   @sizeof(proc_info_list)

  cmp  r5,r6

  blo  1b

  mov  r5,#0         @unknownprocessor

2:

mov  pc,lr

ENDPROC(__lookup_processor_type)

 

这个例程接受处理器ID(保存在寄存器r9中)为参数,查找链接器建立的支持的处理器表。

此时此刻还不能使用__proc_info表的绝对地址,因为这时候MMU还没有开启,所以此时运行的程序没有在正确的地址空间中。

所以不得不计算偏移量。

若没有找到processorID对应的处理器,则在r5寄存器中返回返回0,否则返回一个proc_info_list结构体的指针(在物理地址空间)。

proc_info_list结构体在文件中定义:

structproc_info_list{

  unsignedint   cpu_val;

  unsignedint   cpu_mask;

  unsignedlong     __cpu_mm_mmu_flags;  /*usedbyhead.S*/

  unsignedlong     __cpu_io_mmu_flags;  /*usedbyhead.S*/

  unsignedlong     __cpu_flush;   /*usedbyhead.S*/

  constchar     *arch_name;

  constchar     *elf_name;

  unsignedint   elf_hwcap;

  constchar     *cpu_name;

  structprocessor  *proc;

  structcpu_tlb_fns*tlb;

  structcpu_user_fns  *user;

  structcpu_cache_fns *cache;

};

第一项是CPU id,将与协处理器中读出的id作比较,其余的字段也都是与处理器相关的信息,到下面初始化的过程中自然会用到。

 

另外,这个例程加载符地址的代码也是挺值得我辈学习的:

  adr  r3,3f

加载一个符号的地址,这个符号在加载语句前面(下面)定义,forward嘛,这个符号为3,离这条语句最近的那个。

在那个符号为3的位置我们看到这样的代码:

  .align2

3:

.long__proc_info_begin

  .long__proc_info_end

4:

.long.

  .long__arch_info_begin

  .long__arch_info_end

搜索这两个符号的值,在文件arch/arm/kernel/vmlinux.lds.S中:

     __proc_info_begin=.;

        *(.proc.info.init)

     __proc_info_end=.;

这两个符号分别是一种初始化的段的结束开始地址和结束地址。

为了了解由structproc_info_list结构体组成的段的实际构成,我们还是得要了解一下在系统中到底都有哪些变量是声明了要被放到这个段的。

用关键字.proc.info.init来搜,全部都是arch/arm/mm/proc-*.S文件,这些都是特定于处理器的汇编语言文件,对于我们的mini2440,自然是要看proc-arm920.S文件的,在其中可以看到这些内容:

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

  .type__arm920_proc_info,#object

__arm920_proc_info:

  .long0x41009200

  .long0xff00fff0

  .long  PMD_TYPE_SECT|\

     PMD_SECT_BUFFERABLE|\

     PMD_SECT_CACHEABLE|\

     PMD_BIT4|\

     PMD_SECT_AP_WRITE|\

     PMD_SECT_AP_READ

  .long  PMD_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

#ifndefCONFIG_CPU_DCACHE_WRITETHROUGH

  .longarm920_cache_fns

#else

  .longv4wt_cache_fns

#endif

  .size__arm920_proc_info,.-__arm920_proc_info

 

看到这儿我们再回国头去看__lookup_processor_type的代码:

   ldmiar3,{r5-r7}       

  add  r3,r3,#8

  sub  r3,r3,r7

尽管符号3处只有两个有效值,但它加载了三个数,而第三个数,我们看到是这样定义的:

.long.

 

__lookup_processor_type中,给r3加上8,也就是让r3指向“.”的地址,然后用r3减r7来获取虚拟地址与物理地址的差,这样看来,“.”就应该是虚拟空间(编译地址)里那个数据的地址。

 

之后的代码获得__proc_info_begin和__arch_info_end这两个符号在物理空间中的地址:

   addr5,r5,r3       @convertvirtaddressesto

   addr6,r6,r3

 

然后便是在那个段中逐个的检查structproc_info_list结构体,以找到与我们的CPU相匹配的:

1:

ldmiar5,{r3,r4}      @value,mask

  and  r4,r4,r9        @maskwantedbits

  teq  r3,r4

  beq  2f

  add  r5,r5,#PROC_INFO_SZ   @sizeof(proc_info_list)

  cmp  r5,r6

  blo  1b

  mov  r5,#0         @unknownprocessor

2:

mov  pc,lr

 

__lookup_processor_type例程会返回在文件arch/arm/mm/proc-arm920.S中定义的一个保存有与我们的处理器相关的信息的structproc_info_list结构体的地址。

 

接下来我们继续看stext的代码:

  bl__lookup_machine_type   @r5=machinfo

  movs r8,r5         @invalidmachine(r5=0)?

  beq  __error_a      @yes,error'a'

在获得了处理器信息之后,则调用__lookup_machine_type来查找机器信息。

这个例程同样也在arch/arm/kernel/head-common.S文件中定义。

这个例程的定义如下:

__lookup_machine_type:

  adr  r3,4b

  ldmiar3,{r4,r5,r6}

  sub  r3,r3,r4        @getoffsetbetweenvirt&phys

  add  r5,r5,r3        @convertvirtaddressesto

  add  r6,r6,r3        @physicaladdressspace

1:

ldr  r3,[r5,#MACHINFO_TYPE]@getmachinetype

  teq  r3,r1         @matchesloadernumber?

  beq  2f         @found

  add  r5,r5,#SIZEOF_MACHINE_DESC  @nextmachine_desc

  cmp  r5,r6

  blo  1b

  mov  r5,#0         @unknownmachine

2:

mov  pc,lr

ENDPROC(__lookup_machine_type)

 

处理的过程和上面的__lookup_processor_type还是挺相似的。

这个例程接收r1中传进来的机器号作为参数,然后,在一个由structmachine_desc结构体组成的段中查找和我们的机器号匹配的structmachine_desc结构体,这个结构体在arch/arm/include/asm/mach/arch.h文件中定义,用于保存机器的信息:

structmachine_desc{

  /*

   *Note!

Thefirstfourelementsareused

   *byassemblercodeinhead.S,head-common.S

   */

  unsignedint   nr;     /*architecturenumber  */

  unsignedint   phys_io;/*startofphysicalio */

  unsignedint   io_pg_offst;/*byteoffsetforio

                   *pagetabeentry*/

  constchar     *name;  /*architecturename */

  unsignedlong     boot_params;/*taggedlist    */

  unsignedint   video_start;/*startofvideoRAM*/

  unsignedint   video_end; /*endofvideoRAM  */

  unsignedint   reserve_lp0:

1;/*neverhaslp0  */

  unsignedint   reserve_lp1:

1;/*neverhaslp1  */

  unsignedint   reserve_lp2:

1;/*neverhaslp2  */

  unsignedint   soft_reboot:

1;/*softreboot    */

  void       (*fixup)(structmachine_desc*,

                structtag*,char**,

                structmeminfo*);

  void       (*map_io)(void);/*IOmappingfunction */

  void       (*init_irq)(void);

  structsys_timer  *timer;    /*systemticktimer */

  void       (*init_machine)(void);

};

 

同样这个例程也用到了同上面很相似的方式来获得符号的地址:

  adr  r3,4b

b代表back,即向后,这个符号为4,紧接着我们前面看到的那个为3的标号:

4:

.long.

  .long__arch_info_begin

  .long__arch_info_end

在文件arch/arm/kernel/vmlinux.lds.S中我们可以看到段的定义:

     __arch_info_begin=.;

        *(.arch.info.init)

     __arch_info_end=.;

这两个符号也是分别表示某种初始化的段的开始地址和结束地址。

为了找到段的填充内容,还是得要了解一下到底都有哪些structmachine_desc结构体类型变量声明了要被放到这个段的。

用关键字.arch.info.init来搜索所有的内核源文件。

在arch/arm/include/asm/mach/arch.h文件中我们看到:

#defineMACHINE_START(_type,_name)     \

staticconststructmachine_desc__mach_desc_##_type\

 __used                    \

 __attribute__((__section__(".arch.info.init")))={\

  .nr     =MACH_TYPE_##_type,    \

  .name   =_name,

 

#defineMACHINE_END           \

};

定义机器结构体,也就是.arch.info.init段中的内容,都是要通过两个宏MACHINE_START和MACHINE_END来完成的啊,MACHINE_START宏定义一个tructmachine_desc结构体,并初始化它的机器号字段和机器名字段,可以在arch/arm/tools/mach-types文件中看到各种平台的机器号的定义。

那接着我们来搜MACHINE_START吧,这是一个用于定义机器结构体的宏,所以可以看到这个符号好像都是在arch/arm/mach-*/mach-*.c这样的文件中出现的,我们感兴趣的应该是arch/arm/mach-s3c2440/mach-mini2440.c文件中的这个符号:

MACHINE_START(MINI2440,"MINI2440")

  /*Maintainer:

MichelPollet*/

  .phys_io=S3C2410_PA_UART,

  .io_pg_offst=(((u32)S3C24XX_VA_UART)>>18)&0xfffc,

  .boot_params=S3C2410_SDRAM_PA+0x100,

  .map_io    =mini2440_map_io,

  .init_machine  =mini2440_init,

  .init_irq=s3c24xx_init_irq,

  .timer  =&s3c24xx_timer,

MACHINE_END

OK,__lookup_machine_type这个例程的我们也搞明白了。

回忆一下,启动代码现在已经完成的工作,R10寄存器中为指向proc_info_list结构体的指针(物理地址空间),这个结构体包含有关于我们的处理器的一些重要信息。

R8寄存器中为指向一个与我们的平台相匹配的machine_desc结构体的指针,这个结构体中保存有一些关于我们的平台的重要信息。

 

回来接着看arch/arm/kernel/head.S文件中的stext:

  bl__vet_atags

这个例程同样同样也是在arch/arm/kernel/head-common.S文件中定义:

__vet_atags:

  tst  r2,#0x3       @aligned?

  bne  1f

 

  ldr  r5,[r2,#0]      @isfirsttagATAG_CORE?

  cmp  r5,#ATAG_CORE_SIZE

  cmpner5,#ATAG_CORE_SIZE_EMPTY

  bne  1f

  ldr  r5,[r2,#4]

  ldr  r6,=ATAG_CORE

  cmp  r5,r6

  bne  1f

 

  mov  pc,lr         @atagpointerisok

 

1:

mov  r2,#0

  mov  pc,lr

ENDPROC(__vet_atags)

这个例程接收机器信息(R8寄存器)为参数,并检测r2中传入的ATAGS指针的合法性。

内核使用tag来作为bootloader传递内核参数的方式。

系统要求r2中传进来的ATAGS指针式4字节对齐的,同时要求ATAGS列表的第一个tag是一个ATAG_CORE类型的。

 

此时R10寄存器中保存有指向CPU信息结构体的指针,R8寄存器中保存有指向机器结构体的指针,R2寄存器中保存有指向tag表的指针,R9中还保存有CPUID信息。

 

回到arch/arm/kerne

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > IT计算机 > 电脑基础知识

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2