Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2289841
  • 博文数量: 218
  • 博客积分: 5767
  • 博客等级: 大校
  • 技术积分: 5883
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-01 14:44
文章存档

2012年(53)

2011年(131)

2009年(1)

2008年(33)

分类: LINUX

2011-08-24 14:38:34

linux内核启动第一阶段分析
http://blog.csdn.net/aaronychen/article/details/2838341
本文的很多内容是参考了网上某位大侠的文章写的<<>>,有些东西是直接从他那copy过来的。

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

由于启动部分有一些代码是平台相关的,虽然大部分的平台所的功能都比较类似,但是为了更好的对code进行说明,对于平台相关的代码,我们选择smdk2410平台, CPUs3c2410(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)模式,并且IRQFIQ中断都是禁止的;
   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)

阅读(1554) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~