低温linux内核启动readl,linux内核启动第一阶段分析-2.6.36

linux内核启动第一阶段分析

http://blog.csdn.net/aaronychen/article/details/2838341

本文的很多内容是参考了网上某位大侠的文章写的<<>>,有些东西是直接从他那copy过来的。

本文从kernel的第一条指令开始分析,一直分析到进入start_kernel()函数,也就是kernel启动的汇编部分,我们把它称之为第一部分,以后有时间在把启动的第二部分在分析一下。当前以linux-3.0内核版本来分析,本文中所有的代码前面都会加上行号以便于讲解。

由于启动部分有一些代码是平台相关的,虽然大部分的平台所的功能都比较类似,但是为了更好的对code进行说明,对于平台相关的代码,我们选择smdk2410平台, CPU是s3c2410(arm核是arm920T)进行分析。另外,本文是以未压缩的kernel来分析的.对于内核解压缩部分的code,在arch/arm/boot/compressed中,本文不做讨论。

一.启动通常从系统上电执行的boot loader的代码,而要从boot loader跳转到linux kernel的第一条指令处执行需要一些特定的条件。关于对boot loader的分析请看我的另一篇文档u-boot源码分析。这里讨论下进入到linux kernel时必须具备的一些条件,这一般是boot loader在跳转到kernel之前要完成的:

1. CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断都是禁止的;

2. MMU(内存单元)必须是关闭的,此时地址就是物理地址;

3.数据cache(Data cache)必须是关闭的4.指令cache(Instruction cache)可以是打开的,也可以是关闭的,这个没有强制要求;

5. CPU通用寄存器0 (r0)必须是0;

6. CPU通用寄存器1 (r1)必须是ARM Linux machine type (关于machine type,我们后面会有讲解)

7. CPU通用寄存器2 (r2)必须是kernel parameter list的物理地址(parameter list是由boot loader传递给kernel,用来描述设备信息属性的列表)。

更详细的关于启动arm linux之前要做哪些准备工作可以参考,“Booting ARM Linux"文档

二. starting kernel

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

TEXT_OFFSET  内核在RAM中的起始位置相对于RAM起始地址偏移。值为0x00008000

./arch/arm/Makefile

118 textofs-y       := 0x00008000

222 TEXT_OFFSET := $(textofs-y)

PAGE_OFFSE   内核镜像起始虚拟地址。值为0xC0000000

/arch/arm/configs/s3c2410_defconfig

CONFIG_PAGE_OFFSET=0xC0000000

./arch/arm/include/asm/memory.h

34 #define PAGE_OFFSET             UL(CONFIG_PAGE_OFFSET)

PHYS_OFFSET  RAM启始物理地址,对于2410来说值为0x30000000,RAM接在片选6上

arch/arm/mach-s3c2410/include/mach/memory.h

from arch/arm/mach-rpc/include/mach/memory.h

#define PHYS_OFFSET    UL(0x30000000)

KERNEL_RAM_VADDR  内核在RAM中的虚拟地址。值为0xC0008000

KERNEL_RAM_PADDR  内核在RAM中的物理地址。值为0x30008000

arch/arm/kernel/head.S

29 #define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET)

30 #define KERNEL_RAM_PADDR        (PHYS_OFFSET + TEXT_OFFSET)

PLAT_PHYS_OFFSET     arch/arm/mach-s3c2410/include/mach/memory.h值为0x30000000

arm linux boot的主线可以概括为以下几个步骤:

1. 确定 processor type

2. 确定 machine type

3.检查参数合法性

4. 创建页表

5. 调用平台特定的__cpu_flush函数        (在struct proc_info_list中)

6. 开启mmu

7. 切换数据

最终跳转到start_kernel (在__switch_data的结束的时候,调用了 b start_kernel)

内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中定义的:

31 ENTRY(stext)对于vmlinux.lds.S,这是ld script文件,此文件的格式和汇编及C程序都不同,本文不对ld script作过多的介绍,只对内核中用到的内容进行讲解,关于ld的详细内容可以参考ld.info这里的ENTRY(stext)表示程序的入口是在符号stext.而符号stext是在arch/arm/kernel/head.S中定义的:

下面我们将arm linux boot的主要代码列出来进行一个概括的介绍,然后,我们会逐个的进行详细的讲解.

在arch/arm/kernel/head.S中77 - 101行,是arm linux boot的主代码:

arch/arm/kernel/head.S

77         __HEAD

78 ENTRY(stext)

79         setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ 确保进入管理(svc)模式

80                                                 @ 并且禁止中断

81         mrc     p15, 0, r9, c0, c0              @ 读取CPU ID,存入r9寄存器

82         bl      __lookup_processor_type      @ 调用函数,输入参数r9=cpuid,

@ 返回值r5=procinfo

83         movs    r10, r5                   @ 如果不支持当前CPU,则返回 (r5=0),/*判断如果r10的值为0,跳转到出错处理,*/

84         beq     __error_p                       @ yes, error 'p'

//查询machine ID并检查合法性

85         bl      __lookup_machine_type           @ r5=machinfo

86         movs    r8, r5                          @ invalid machine (r5=0)?

87         beq     __error_a                       @ yes, error 'a'

88         bl      __vet_atags   //检查bootloader传入的参数列表atags的合法性

89         bl      __create_page_tables  //创建初始页表

90

91         /*

92          * The following calls CPU specific code in a position independent

93          * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of

94          * xxx_proc_info structure selected by __lookup_machine_type

95          * above.  On return, the CPU will be ready for the MMU to be

96          * turned on, and r0 will hold the CPU control register value.

97          */

98         ldr     r13, __switch_data              @ address to jump to after //将列表__switch_data存到r13中后面会跳到该列表出

99                                                 @ mmu has been enabled

100         adr     lr, BSYM(__enable_mmu)          @ return (PIC) address//将程序段__enable_mmu的地址存到lr中。此命令将导致程序段__arm920_setup的执行,后面会将到。

101  ARM(   add     pc, r10, #PROCINFO_INITFUNC     )//r10中存放的基地址是从__lookup_processor_type中得到的,如上面movs r10, r5

102  THUMB( add     r12, r10, #PROCINFO_INITFUNC    )

103  THUMB( mov     pc, r12                         )

104 ENDPROC(stext)

76 __error_p:

77 #ifdef CONFIG_DEBUG_LL

78         adr     r0, str_p1

79         bl      printascii

80         mov     r0, r9

81         bl      printhex8

82         adr     r0, str_p2

83         bl      printascii

84         b       __error

85 str_p1: .asciz  "\nError: unrecognized/unsupported processor variant (0x"

86 str_p2: .asciz  ").\n"

87         .align

88 #endif

89 ENDPROC(__error_p)

91 __error_a:

92 #ifdef CONFIG_DEBUG_LL

93         mov     r4, r1                          @ preserve machine ID

94         adr     r0, str_a1

95         bl      printascii

96         mov     r0, r4

97         bl      printhex8

98         adr     r0, str_a2

99         bl      printascii

100         adr     r3, 4f

101         ldmia   r3, {r4, r5, r6}                @ get machine desc list

102         sub     r4, r3, r4                      @ get offset between virt&phys

103         add     r5, r5, r4                      @ convert virt addresses to

104         add     r6, r6, r4                      @ physical address space

105 1:      ldr     r0, [r5, #MACHINFO_TYPE]        @ get machine type

106         bl      printhex8

107         mov     r0, #'\t'

108         bl      printch

109         ldr     r0, [r5, #MACHINFO_NAME]        @ get machine name

110         add     r0, r0, r4

111         bl      printascii

112         mov     r0, #'\n'

113         bl      printch

114         add     r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc

115         cmp     r5, r6

116         blo     1b

117         adr     r0, str_a3

118         bl      printascii

119         b       __error

120 ENDPROC(__error_a)

121

122 str_a1: .asciz  "\nError: unrecognized/unsupported machine ID (r1 = 0x"

123 str_a2: .asciz  ").\n\nAvailable machine support:\n\nID (hex)\tNAME\n"

124 str_a3: .asciz  "\nPlease check your kernel config and/or bootloader.\n"

125         .align

126 #endif

128 __error:

129 #ifdef CONFIG_ARCH_RPC

130 /*

131  * Turn the screen red on a error - RiscPC only.

132  */

133         mov     r0, #0x02000000

134         mov     r3, #0x11

135         orr     r3, r3, r3, lsl #8

136         orr     r3, r3, r3, lsl #16

137         str     r3, [r0], #4

138         str     r3, [r0], #4

139         str     r3, [r0], #4

140         str     r3, [r0], #4

141 #endif

142 1:      mov     r0, r0

143         b       1b

144 ENDPROC(__error)

__lookup_processor_type

---------------------------------------------------------------

在讲解lookup_processor_type程序段之前先来看一些相关知识。

内核做支持的每一种CPU类型都由结构体proc_info_list 来描述。

该结构体在文件arch/arm/include/asm/procinfo.h中定义:

struct proc_info_list {

unsigned int            cpu_val;

unsigned int            cpu_mask;

unsigned long           __cpu_mm_mmu_flags;     /* used by head.S */

unsigned long           __cpu_io_mmu_flags;     /* used by head.S */

unsigned long           __cpu_flush;            /* used by head.S */

const char              *arch_name;

const char              *elf_name;

unsigned int            elf_hwcap;

const char              *cpu_name;

struct processor        *proc;

struct cpu_tlb_fns      *tlb;

struct cpu_user_fns     *user;

struct cpu_cache_fns    *cache;

};

对于arm920来说,其对应结构体在文件 linux/arch/arm/mm/proc-arm920.S中初始化。

442         .align

443

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

445

446         .type   __arm920_proc_info,#object

447 __arm920_proc_info:

448         .long   0x41009200

449         .long   0xff00fff0

450         .long   PMD_TYPE_SECT | \

451                 PMD_SECT_BUFFERABLE | \

452                 PMD_SECT_CACHEABLE | \

453                 PMD_BIT4 | \

454                 PMD_SECT_AP_WRITE | \

455                 PMD_SECT_AP_READ

456         .long   PMD_TYPE_SECT | \

457                 PMD_BIT4 | \

458                 PMD_SECT_AP_WRITE | \

459                 PMD_SECT_AP_READ

460         b       __arm920_setup

461         .long   cpu_arch_name

462         .long   cpu_elf_name

463         .long   HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB

464         .long   cpu_arm920_name

465         .long   arm920_processor_functions

466         .long   v4wbi_tlb_fns

467         .long   v4wb_user_fns

468 #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH

469         .long   arm920_cache_fns

470 #else

471         .long   v4wt_cache_fns

472 #endif

473         .size   __arm920_proc_info, . - __arm920_proc_info

.section ".proc.info.init"表明了该结构在编译后存放的位置。

在链接文件arch/arm/kernel/vmlinux.lds.S中,编译后它生成arch/arm/kernel/vmlinux.lds文件

20 SECTIONS

21 {

22 #ifdef CONFIG_XIP_KERNEL

23         . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

24 #else

25         . = PAGE_OFFSET + TEXT_OFFSET;

26 #endif

27

28         .init : {                       /* Init code and data           */

29                 _stext = .;

30                 _sinittext = .;

31                         HEAD_TEXT

32                         INIT_TEXT

33                 _einittext = .;

34                 __proc_info_begin = .;

35                         *(.proc.info.init)

36                 __proc_info_end = .;

37                 __arch_info_begin = .;

38                         *(.arch.info.init)

39                 __arch_info_end = .;

40                 __tagtable_begin = .;

41                         *(.taglist.init)

42                 __tagtable_end = .;

所有CPU类型对应的被初始化的proc_info_list结构体都放在__proc_info_begin和__proc_info_end之间。

现在再来分析lookup_processor_type

147 /*

148  * Read processor ID register (CP#15, CR0), and look up in the linker-built

149  * supported processor list.  Note that we can't use the absolute addresses

150  * for the __proc_info lists since we aren't running with the MMU on

151  * (and therefore, we are not in the correct address space).  We have to

152  * calculate the offset.

153  *

154  *      r9 = cpuid

155  * Returns:

156  *      r3, r4, r6 corrupted

157  *      r5 = proc_info pointer in physical address space

158  *      r9 = cpuid (preserved)

159  */

160 __lookup_processor_type:

161         adr     r3, 3f //r3存储的是标号3的物理地址(由于没有启用mmu,所以当前肯定是物理地址)

162         ldmia   r3, {r5 - r7} //R5=__proc_info_begin,r6=__proc_info_end,r7=标号3处的虚拟地址。

163         add     r3, r3, #8

164         sub     r3, r3, r7    //得到虚拟地址和物理地址之间的offset

165         add     r5, r5, r3    //利用offset,将r5和r6中保存的虚拟地址转变为物理地址

166         add     r6, r6, r3                      @ physical address space

167 1:      ldmia   r5, {r3, r4}    //r3=cpu_val,r4= cpu_mask,

168         and     r4, r4, r9    //r9中存放的是先前读出的processor ID,此处屏蔽不需要的位。

//查看代码和CPU硬件是否匹配(比如想在arm920t上运行为cortex-a8编译的内核?不让!)

169         teq     r3, r4

170         beq     2f  //如果匹配成功就返回

//PROC_INFO_SZ (proc_info_list结构的长度,在这等于48),跳到下一个proc_info_list处

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

//判断是否已经到了结构体proc_info_list存放区域的末尾__proc_info_end,

172         cmp     r5, r6

173         blo     1b

//如果没有匹配成功就将r5清零,如果匹配成功r5中放的是该CPU类型对应的结构体//proc_info_list的基地址。

174         mov     r5, #0                          @ unknown processor

175 2:      mov     pc, lr  //子程序返回。

176 ENDPROC(__lookup_processor_type)

177

178 /*

179  * This provides a C-API version of the above function.

180  */

181 ENTRY(lookup_processor_type)

182         stmfd   sp!, {r4 - r7, r9, lr}

183         mov     r9, r0

184         bl      __lookup_processor_type

185         mov     r0, r5

186         ldmfd   sp!, {r4 - r7, r9, pc}

187 ENDPROC(lookup_processor_type)

188

189 /*

190  * Look in and arch/arm/kernel/arch.[ch] for

191  * more information about the __proc_info and __arch_info structures.

192  */

193         .align  2

194 3:      .long   __proc_info_begin

195         .long   __proc_info_end

196 4:      .long   .

197         .long   __arch_info_begin

198         .long   __arch_info_end

__lookup_machine_type

---------------------------------------------------------------

每一个CPU平台都可能有其不一样的结构体,描述这个平台的结构体是machine_desc。

这个结构体在文件arch/arm/include/asm/mach/arch.h中定义:

17 struct machine_desc {

18         /*

19          * Note! The first four elements are used

20          * by assembler code in head.S, head-common.S

21          */

22         unsigned int            nr;             /* architecture number  */

23         unsigned int            nr_irqs;        /* number of IRQs */

24         unsigned int            phys_io;        /* start of physical io */

25         unsigned int            io_pg_offst;    /* byte offset for io

26                                                  * page tabe entry      */

27

28         const char              *name;          /* architecture name    */

29         unsigned long           boot_params;    /* tagged list          */

30

31         unsigned int            video_start;    /* start of video RAM   */

32         unsigned int            video_end;      /* end of video RAM     */

33

34         unsigned int            reserve_lp0 :1; /* never has lp0        */

35         unsigned int            reserve_lp1 :1; /* never has lp1        */

36         unsigned int            reserve_lp2 :1; /* never has lp2        */

37         unsigned int            soft_reboot :1; /* soft reboot          */

38         void                    (*fixup)(struct machine_desc *,

39                                          struct tag *, char **,

40                                          struct meminfo *);

41         void                    (*reserve)(void);/* reserve mem blocks  */

42         void                    (*map_io)(void);/* IO mapping function  */

43         void                    (*init_irq)(void);

44         struct sys_timer        *timer;         /* system tick timer    */

45         void                    (*init_machine)(void);

46 };

47

对于平台smdk2410来说其对应machine_desc结构在文件

linux/arch/arm/mach-s3c2410/mach-smdk2410.c中初始化:

MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch

* to SMDK2410 */

/* Maintainer: Jonas Dietsche */

.phys_io        = S3C2410_PA_UART,

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

.boot_params    = S3C2410_SDRAM_PA + 0x100,

.map_io         = smdk2410_map_io,

.init_irq       = s3c24xx_init_irq,

.init_machine   = smdk2410_init,

.timer          = &s3c24xx_timer,

MACHINE_END

对于宏MACHINE_START在文件arch/arm/include/asm/mach/arch.h中定义:

48 /*

49  * Set of macros to define architecture features.  This is built into

50  * a table by the linker.

51  */

52 #define MACHINE_START(_type,_name)                      \

53 static const struct machine_desc __mach_desc_##_type    \

54  __used                                                 \

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

56         .nr             = MACH_TYPE_##_type,            \

57         .name           = _name,

58

59 #define MACHINE_END                             \

60 };

61

62 #endif

__attribute__((__section__(".arch.info.init")))表明该结构体在并以后存放的位置。

在链接文件arch/arm/kernel/vmlinux.lds.S中,编译后它生成arch/arm/kernel/vmlinux.lds文件

20 SECTIONS

21 {

22 #ifdef CONFIG_XIP_KERNEL

23         . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

24 #else

25         . = PAGE_OFFSET + TEXT_OFFSET;

26 #endif

27

28         .init : {                       /* Init code and data           */

29                 _stext = .;

30                 _sinittext = .;

31                         HEAD_TEXT

32                         INIT_TEXT

33                 _einittext = .;

34                 __proc_info_begin = .;

35                         *(.proc.info.init)

36                 __proc_info_end = .;

37                 __arch_info_begin = .;

38                         *(.arch.info.init)

39                 __arch_info_end = .;

40                 __tagtable_begin = .;

41                         *(.taglist.init)

42                 __tagtable_end = .;

在__arch_info_begin和 __arch_info_end之间存放了linux内核所支持的所有平台对应的machine_desc结构体。

现在再来看__lookup_machine_type

200 /*

201  * Lookup machine architecture in the linker-build list of architectures.

202  * Note that we can't use the absolute addresses for the __arch_info

203  * lists since we aren't running with the MMU on (and therefore, we are

204  * not in the correct address space).  We have to calculate the offset.

205  *

206  *  r1 = machine architecture number

207  * Returns:

208  *  r3, r4, r6 corrupted

209  *  r5 = mach_info pointer in physical address space

210  */

211 __lookup_machine_type:

212         adr     r3, 4b //r3存储的是标号3的物理地址

//R4=标号3处的虚拟地址,r5=__arch_info_begin,r6=__arch_info_end。

213         ldmia   r3, {r4, r5, r6}

214         sub     r3, r3, r4                      @ get offset between virt&phys

215         add     r5, r5, r3                      @ convert virt addresses to

216         add     r6, r6, r3                      @ physical address space

//读取machine_desc结构的nr参数,对于smdk2410来说该值是MACH_TYPE_SMDK2410。

//这个值在文件linux/arch/arm/tools/mach-types中:

//smdk2410 ARCH_SMDK2410 SMDK2410 193

217 1:      ldr     r3, [r5, #MACHINFO_TYPE]        @ get machine type

218         teq     r3, r1     //r1就是bootloader传递过来的机器码,就是上面的平台编号。

219         beq     2f        //如果匹配成功就返回

220         add     r5, r5, #SIZEOF_MACHINE_DESC  //没匹配成功就跳到下一个结构体machine_desc

221         cmp     r5, r6

222         blo     1b

223         mov     r5, #0        //如果匹配失败就将r5清零。

224 2:      mov     pc, lr        //子程序返回。

225 ENDPROC(__lookup_machine_type)

226

227 /*

228  * This provides a C-API version of the above function.

229  */

230 ENTRY(lookup_machine_type)

231         stmfd   sp!, {r4 - r6, lr}

232         mov     r1, r0

233         bl      __lookup_machine_type

234         mov     r0, r5

235         ldmfd   sp!, {r4 - r6, pc}

236 ENDPROC(lookup_machine_type)

__vet_atags 检查bootloader传入的参数列表atags的合法性

---------------------------------------------------------------

关于参数链表:

内核参数链表的格式和说明可以从内核源代码目录树中的arch/arm/include/asm/setup.h中

找到,参数链表必须以ATAG_CORE 开始,以ATAG_NONE结束。这里的 ATAG_CORE,

ATAG_NONE是各个参数的标记,本身是一个32位值,例如:ATAG_CORE=0x54410001。

其它的参数标记还包括: ATAG_MEM32 , ATAG_INITRD , ATAG_RAMDISK ,

ATAG_COMDLINE 等。每个参数标记就代表一个参数结构体,由各个参数结构体构成了参

数链表。参数结构体的定义如下:

146 struct tag {

147         struct tag_header hdr;

148         union {

149                 struct tag_core         core;

150                 struct tag_mem32        mem;

151                 struct tag_videotext    videotext;

152                 struct tag_ramdisk      ramdisk;

153                 struct tag_initrd       initrd;

154                 struct tag_serialnr     serialnr;

155                 struct tag_revision     revision;

156                 struct tag_videolfb     videolfb;

157                 struct tag_cmdline      cmdline;

158

159                 /*

160                  * Acorn specific

161                  */

162                 struct tag_acorn        acorn;

163

164                 /*

165                  * DC21285 specific

166                  */

167                 struct tag_memclk       memclk;

168         } u;

169 };

参数结构体包括两个部分,一个是 tag_header结构体,一个是u联合体。

tag_header结构体的定义如下:

24 struct tag_header {

25         __u32 size;

26         __u32 tag;

27 };

28

其中 size:表示整个 tag 结构体的大小(用字的个数来表示,而不是字节的个数),等于tag_header的大小加上 u联合体的大小,例如,参数结构体 ATAG_CORE 的 size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通过函数 tag_size(struct * tag_xxx)来获得每个参数结构体的 size。其中 tag:表示整个 tag 结构体的标记,如:ATAG_CORE等。

238 /* Determine validity of the r2 atags pointer.  The heuristic requires

239  * that the pointer be aligned, in the first 16k of physical RAM and

240  * that the ATAG_CORE marker is first and present.  Future revisions

241  * of this function may be more lenient with the physical address and

242  * may also be able to move the ATAGS block if necessary.

243  *

244  * r8  = machinfo

245  *

246  * Returns:

247  *  r2 either valid atags pointer, or zero

248  *  r5, r6 corrupted

249  */

250 __vet_atags:

251         tst     r2, #0x3  //r2指向该参数链表的起始位置,此处判断它是否字对齐

252         bne     1f

253

254         ldr     r5, [r2, #0]  //获取第一个tag结构的size

//#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2) 判断该tag的长度是否合法

255         cmp     r5, #ATAG_CORE_SIZE

256         cmpne   r5, #ATAG_CORE_SIZE_EMPTY

257         bne     1f

258         ldr     r5, [r2, #4] //获取第一个tag结构体的标记,

259         ldr     r6, =ATAG_CORE

260         cmp     r5, r6  //判断第一个tag结构体的标记是不是ATAG_CORE

261         bne     1f

262

263         mov     pc, lr   //正常退出

264

265 1:      mov     r2, #0  //参数连表不正确

266         mov     pc, lr

267 ENDPROC(__vet_atags)

arch/arm/kernel/head.S

206 /*

207  * Setup the initial page tables.  We only setup the barest

208  * amount which are required to get the kernel running, which

209  * generally means mapping in the kernel code.

210  *

211  * r8  = machinfo

212  * r9  = cpuid

213  * r10 = procinfo

214  *

215  * Returns:

216  *  r0, r3, r6, r7 corrupted

217  *  r4 = physical page table address

218  */

219 __create_page_tables:

220         pgtbl   r4                              @ page table address

//r4 = 0x30004000这是转换表的物理基地址,最终将写入CP15的寄存器2,C2。这个值必须是16K对齐的。

//为内核代码存储区域创建页表,首先将内核起始地址-0x4000到内核起始地址之间的16K 存储器清0,将创建的页表存于此处。

221

222         /*

223          * Clear the 16K level 1 swapper page table

224          */

225         mov     r0, r4

226         mov     r3, #0

227         add     r6, r0, #0x4000

228 1:      str     r3, [r0], #4

229         str     r3, [r0], #4

230         str     r3, [r0], #4

231         str     r3, [r0], #4

232         teq     r0, r6

233         bne     1b

//从proc_info_list结构中获取字段__cpu_mm_mmu_flags,该字段包含了存储空间访问权限 等。此处指令执行之后r7=0x00000c1e

234

235         ldr     r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

236

237         /*

238          * Create identity mapping for first MB of kernel to

239          * cater for the MMU enable.  This identity mapping

240          * will be removed by paging_init().  We use our current program

241          * counter to determine corresponding section base address.

242          */

243         mov     r6, pc

/*

此处建立一个物理地址到物理地址的平板映射,这个映射将在函数paging_init().  被清除。

r6 = 0x300  r3 = 0x30000c1e    [0x30004c00]=0x30000c1e

*/

244         mov     r6, r6, lsr #20                 @ start of kernel section

245         orr     r3, r7, r6, lsl #20             @ flags + kernel base

246         str     r3, [r4, r6, lsl #2]            @ identity mapping //字对齐

247

/*

MMU是通过C2中基地址(高18位)与虚拟地址的高12位组合成物理地址,在转换表中查找地址条目。R4中存放的就是这个基地址0x30004000。下面通过两次获取虚拟地址

KERNEL_START的高12位。KERNEL_START是内核存放的起始地址,为0X30008000。

*/

248         /*

249          * Now setup the pagetables for our kernel direct

250          * mapped region.

251          */

252         add     r0, r4,  #(KERNEL_START & 0xff000000) >> 18 // r0 = 0x30007000

253         str     r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! // r0 存放的是转换表的起始位置

254         ldr     r6, =(KERNEL_END - 1) //获取内核的尾部虚拟地址存于r6中

255         add     r0, r0, #4 //第一个地址条目存放在0x30007004处,以后一次递增

256         add     r6, r4, r6, lsr #18  //计算最后一个地址条目存放的位置

257 1:      cmp     r0, r6  //填充这之间的地址条目

258         add     r3, r3, #1 << 20 //每一个地址条目代表了1MB空间的地址映射。物理地址将从 0x30100000 开始映射。0X30000000开始的1MB空间将在下面映射。

259         strls   r3, [r0], #4

260         bls     1b

261

//如果是XIP就进行以下映射,这只是将内核代码存储的空间重新映射,

262 #ifdef CONFIG_XIP_KERNEL

263         /*

264          * Map some ram to cover our .data and .bss areas.

265          */

266         orr     r3, r7, #(KERNEL_RAM_PADDR & 0xff000000)

267         .if     (KERNEL_RAM_PADDR & 0x00f00000)

268         orr     r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000)

269         .endif

270         add     r0, r4,  #(KERNEL_RAM_VADDR & 0xff000000) >> 18

271         str     r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!

272         ldr     r6, =(_end - 1)

273         add     r0, r0, #4

274         add     r6, r4, r6, lsr #18

275 1:      cmp     r0, r6

276         add     r3, r3, #1 << 20

277         strls   r3, [r0], #4

278         bls     1b

279 #endif

280

281         /*

282          * Then map first 1MB of ram in case it contains our boot params.

283          */

/*

映射0X30000000开始的1MB空间。

PAGE_OFFSET = 0XC0000000,PHYS_OFFSET = 0X30000000,

*/

//r0 = 0x30007000,上面是从0x30007004开始存放地址条目的。

284         add     r0, r4, #PAGE_OFFSET >> 18

285         orr     r6, r7, #(PHYS_OFFSET & 0xff000000)

286         .if     (PHYS_OFFSET & 0x00f00000)

287         orr     r6, r6, #(PHYS_OFFSET & 0x00f00000)

288         .endif

289         str     r6, [r0]  //将0x30000c1e存于0x30007000处。

290

291 #ifdef CONFIG_DEBUG_LL  //下面是为了调试而做的相关映射,跳过。

292         ldr     r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags

293         /*

294          * Map in IO space for serial debugging.

295          * This allows debug messages to be output

296          * via a serial console before paging_init.

297          */

298         ldr     r3, [r8, #MACHINFO_PGOFFIO]

299         add     r0, r4, r3

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

301         cmp     r3, #0x0800                     @ limit to 512MB

302         movhi   r3, #0x0800

303         add     r6, r0, r3

304         ldr     r3, [r8, #MACHINFO_PHYSIO]

305         orr     r3, r3, r7

306 1:      str     r3, [r0], #4

307         add     r3, r3, #1 << 20

308         teq     r0, r6

309         bne     1b

310 #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)

311         /*

312          * If we're using the NetWinder or CATS, we also need to map

313          * in the 16550-type serial port for the debug messages

314          */

315         add     r0, r4, #0xff000000 >> 18

316         orr     r3, r7, #0x7c000000

317         str     r3, [r0]

318 #endif

319 #ifdef CONFIG_ARCH_RPC

320         /*

321          * Map in screen at 0x02000000 & SCREEN2_BASE

322          * Similar reasons here - for debug.  This is

323          * only for Acorn RiscPC architectures.

324          */

325         add     r0, r4, #0x02000000 >> 18

326         orr     r3, r7, #0x02000000

327         str     r3, [r0]

328         add     r0, r4, #0xd8000000 >> 18

329         str     r3, [r0]

330 #endif

331 #endif

332         mov     pc, lr  //子程序返回。

333 ENDPROC(__create_page_tables)

334         .ltorg

335

336 #include "head-common.S"

__arm920_setup

---------------------------------------------------------------

回到arch/arm/kernel/head.S中有这么几条

ldr r13, __switch_data @ address to jump to after

@ mmu has been enabled

adr lr, __enable_mmu @ return (PIC) address

add pc, r10, #PROCINFO_INITFUNC

R10中存放的是在函数__lookup_processor_type中成功匹配的结构体proc_info_list。

对于arm920来说在文件arch/arm/mm/proc-arm920.S中有:

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

445

446         .type   __arm920_proc_info,#object

447 __arm920_proc_info:

448         .long   0x41009200

449         .long   0xff00fff0

450         .long   PMD_TYPE_SECT | \

451                 PMD_SECT_BUFFERABLE | \

452                 PMD_SECT_CACHEABLE | \

453                 PMD_BIT4 | \

454                 PMD_SECT_AP_WRITE | \

455                 PMD_SECT_AP_READ

456         .long   PMD_TYPE_SECT | \

457                 PMD_BIT4 | \

458                 PMD_SECT_AP_WRITE | \

459                 PMD_SECT_AP_READ

460         b       __arm920_setup

所以add pc, r10, #PROCINFO_INITFUNC的意思跳到函数__arm920_setup去执行。

文件arch/arm/mm/proc-arm920.S中

380         .type   __arm920_setup, #function  //表明这是一个函数

381 __arm920_setup:

382         mov     r0, #0

383         mcr     p15, 0, r0, c7, c7    //使数据cahche, 指令cache无效。

384         mcr     p15, 0, r0, c7, c10, 4   //使write buffer无效。

385 #ifdef CONFIG_MMU

386         mcr     p15, 0, r0, c8, c7    //使数据TLB,指令TLB无效。

387 #endif

388         adr     r5, arm920_crval  //获取arm920_crval的地址,并存入r5。

389         ldmia   r5, {r5, r6}  //获取arm920_crval地址处的连续8字节分别存入r5,r6。

390         mrc     p15, 0, r0, c1, c0  //获取CP15下控制寄存器的值,并存入r0。

//通过查看arm920_crval的值可知该行是清除r0中相关位,为以后对这些位的赋值做准备

391         bic     r0, r0, r5

392         orr     r0, r0, r6 //设置r0中的相关位,即为mmu做相应设置。

393         mov     pc, lr  //上面有操作adr lr, __enable_mmu 此处将跳到程序段__enable_mmu处。

394         .size   __arm920_setup, . - __arm920_setup

arch/arm/kernel/head.S

160 __enable_mmu:

161 #ifdef CONFIG_ALIGNMENT_TRAP

162         orr     r0, r0, #CR_A   //使能地址对齐错误检测

163 #else

164         bic     r0, r0, #CR_A

165 #endif

166 #ifdef CONFIG_CPU_DCACHE_DISABLE

167         bic     r0, r0, #CR_C  //禁止数据cache

168 #endif

169 #ifdef CONFIG_CPU_BPREDICT_DISABLE

170         bic     r0, r0, #CR_Z

171 #endif

172 #ifdef CONFIG_CPU_ICACHE_DISABLE

173         bic     r0, r0, #CR_I  //禁止指令cache

174 #endif

//配置相应的访问权限并存入r5中

175         mov     r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \

176                       domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \

177                       domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \

178                       domain_val(DOMAIN_IO, DOMAIN_CLIENT))

179         mcr     p15, 0, r5, c3, c0, 0           @ load domain access register//将访问权限写入协处理器

180         mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer//将页表基地址写入基址寄存器C2,0X30004000

181         b       __turn_mmu_on  //跳转到程序段去打开MMU

182 ENDPROC(__enable_mmu)

183

184 /*

185  * Enable the MMU.  This completely changes the structure of the visible

186  * memory space.  You will not be able to trace execution through this.

187  * If you have an enquiry about this, *please* check the linux-arm-kernel

188  * mailing list archives BEFORE sending another post to the list.

189  *

190  *  r0  = cp#15 control register

191  *  r13 = *virtual* address to jump to upon completion

192  *

193  * other registers depend on the function called upon completion

194  */

195         .align  5

196 __turn_mmu_on:

197         mov     r0, r0

198         mcr     p15, 0, r0, c1, c0, 0           @ write control reg//打开MMU同时打开cache等。

199         mrc     p15, 0, r3, c0, c0, 0           @ read id reg//读取id寄存器

200         mov     r3, r3   //两个空操作,等待前面所取的指令得以执行。

201         mov     r3, r13

202         mov     pc, r3  //程序跳转,如下面解释。

203 ENDPROC(__turn_mmu_on)

在前面有过这样的指令操作ldr r13, __switch_data ,

mov pc, r13 就是将跳转到__switch_data处。

arch/arm/kernel/head-common.S

14 #define ATAG_CORE 0x54410001

15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)

16 #define ATAG_CORE_SIZE_EMPTY ((2*4) >> 2)

17

18         .align  2

19         .type   __switch_data, %object    //定义一个对象

20 __switch_data:

21         .long   __mmap_switched   //由此可知上面程序将跳转到该程序段处。

22         .long   __data_loc                      @ r4

23         .long   _data                           @ r5

24         .long   __bss_start                     @ r6

25         .long   _end                            @ r7

26         .long   processor_id                    @ r4

27         .long   __machine_arch_type             @ r5

28         .long   __atags_pointer                 @ r6

29         .long   cr_alignment                    @ r7

30         .long   init_thread_union + THREAD_START_SP @ sp

31

/*

__data_loc 是数据存放的位置

_data 是数据开始的位置

__bss_start 是bss开始的位置

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

.data 段,后面的AT(__data_loc) 的意思是这部分的内容是在__data_loc中存储的(要注意,储存的位置和链接的位置是可以不相同的).

这几个字段在文件arch/arm/kernel/vmlinux.lds.S中指定位置如下:

20 SECTIONS

21 {

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

134         .data : AT(__data_loc) {  //此处数据存储在上面__data_loc处。

135                 _data = .;              /* address in memory */

136                 _sdata = .;

137

138                 /*

139                  * first, the init task union, aligned

140                  * to an 8192 byte boundary.

141                  */

142                 INIT_TASK_DATA(THREAD_SIZE)

。。。。。。。。。。。。。。。。。。。。。。。。。。。

231

232         BSS_SECTION(0, 0, 0)

233         _end = .;

234

235         STABS_DEBUG

236         .comment 0 : { *(.comment) }

237

238         /* Default discards */

239         DISCARDS

240 }

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

27 union thread_union init_thread_union __init_task_data =

28         { INIT_THREAD_INFO(init_task) };

对照 vmlnux.lds.S 中,我们可以知道init task是存放在 .data 段的开始8k, 并且是THREAD_SIZE(8k)对齐的

*/

32 /*

33  * The following fragment of code is executed with the MMU on in MMU mode,

34  * and uses absolute addresses; this is not position independent.

35  *

36  *  r0  = cp#15 control register

37  *  r1  = machine ID

38  *  r2  = atags pointer

39  *  r9  = processor ID

40  */

41 __mmap_switched:

42         adr     r3, __switch_data + 4

43

44         ldmia   r3!, {r4, r5, r6, r7}

45         cmp     r4, r5                          @ Copy data segment if needed

46 1:      cmpne   r5, r6   //将 __data_loc处数据搬移到_data处

47         ldrne   fp, [r4], #4

48         strne   fp, [r5], #4

49         bne     1b

50

51         mov     fp, #0                          @ Clear BSS (and zero fp)//清除BSS段内容

52 1:      cmp     r6, r7

53         strcc   fp, [r6],#4

54         bcc     1b

55

56  ARM(   ldmia   r3, {r4, r5, r6, r7, sp})

57  THUMB( ldmia   r3, {r4, r5, r6, r7}    )

58  THUMB( ldr     sp, [r3, #16]           )

59         str     r9, [r4]                        @ Save processor ID

60         str     r1, [r5]                        @ Save machine type

61         str     r2, [r6]                        @ Save atags pointer

62         bic     r4, r0, #CR_A                   @ Clear 'A' bit

63         stmia   r7, {r0, r4}                    @ Save control register values

64         b       start_kernel    //程序跳转到函数start_kernel进入C语言部分。

65 ENDPROC(__mmap_switched)