您的位置 首页 培训

简略介绍ARM linux的发动部分源代码

简单介绍ARM linux的启动部分源代码-当内核映像被加载到RAM之后,Bootloader的控制权被释放。内核映像并不是可直接运行的目标代码,而是一个压缩过的zImage(小内核)。

当内核映像被加载到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开端映射的。

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

ENTRY(stext)是这个文件的进口点。开端的几行是这样的:

setmode  PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9

@ ensure svc mode

@ and irqs disabled

// 设置为SVC形式,封闭中止和快速中止 
// 此处设定体系的作业状况为SVC,arm有7种状况每种状况

// 都有自己的仓库,SVC为管理形式,具有彻底的权限,能够履行恣意指令

// 拜访恣意地址的内存

// setmode是一个宏,其界说为:

// .macro setmode, mode, reg

// msr   cpsr_c, #\mode

// .endm
 

mrc   p15, 0, r9, c0, c0    @ get processor id

bl __lookup_processor_type     @ r5=procinfo r9=cpuid

movs  r10, r5            @ invalid processor (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

ldmia r3, {r5 – r7}

add   r3, r3, #8

sub   r3, r3, r7         @ get offset between virt&phys

add   r5, r5, r3         @ convert virt addresses to

add   r6, r6, r3         @ physical address space

1: ldmia r5, {r3, r4}       @ value, mask

and   r4, r4, r9         @ mask wanted bits

teq   r3, r4

beq   2f

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

cmp   r5, r6

blo   1b

mov   r5, #0          @ unknown processor

2: mov   pc, lr

ENDPROC(__lookup_processor_type)

这个例程承受处理器ID(保存在寄存器r9中)为参数,查找链接器树立的支撑的处理器表。此刻此刻还不能运用__proc_info表的肯定地址,由于这时分MMU还没有敞开,所以此刻运转的程序没有在正确的地址空间中。所以不得不核算偏移量。若没有找到processor ID对应的处理器,则在r5寄存器中回来回来0,不然回来一个proc_info_list结构体的指针(在物理地址空间)。proc_info_list结构体在文件中界说:

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;

};
榜首项是CPU id,将与协处理器中读出的id作比较,其他的字段也都是与处理器相关的信息,到下面初始化的进程中自然会用到。

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

adr   r3, 3f
加载一个符号的地址,这个符号在加载句子前面(下面)界说,forward嘛,这个符号为3,离这条句子最近的那个。在那个符号为3的方位咱们看到这样的代码:

.align 2

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 = .;

这两个符号别离是一种初始化的段的完毕开端地址和完毕地址。为了了解由struct proc_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:

.long 0x41009200

.long 0xff00fff0

.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

.long cpu_arch_name

.long cpu_elf_name

.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB

.long cpu_arm920_name

.long arm920_processor_funcTIons

.long v4wbi_tlb_fns

.long v4wb_user_fns

#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH

.long arm920_cache_fns

#else

.long v4wt_cache_fns

#endif

.size __arm920_proc_info, . – __arm920_proc_info

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

ldmia r3, {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这两个符号在物理空间中的地址:

add r5, r5, r3        @ convert virt addresses to

add r6, r6, r3

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

1: ldmia r5, {r3, r4}       @ value, mask

and   r4, r4, r9         @ mask wanted bits

teq   r3, r4

beq   2f

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

cmp   r5, r6

blo   1b

mov   r5, #0          @ unknown processor

2: mov   pc, lr

__lookup_processor_type例程会回来在文件arch/arm/mm/proc-arm920.S中界说的一个保存有与咱们的处理器相关的信息的struct proc_info_list结构体的地址。

接下来咱们持续看stext的代码:
   bl __lookup_machine_type    @ r5=machinfo

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

beq   __error_a       @ yes, error 'a'

在取得了处理器信息之后,则调用__lookup_machine_type来查找机器信息。这个例程相同也在arch/arm/kernel/head-common.S文件中界说。这个例程的界说如下:

__lookup_machine_type:

adr   r3, 4b

ldmia r3, {r4, r5, r6}

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

add   r5, r5, r3         @ convert virt addresses to

add   r6, r6, r3         @ physical address space

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

teq   r3, r1          @ matches loader number?

beq   2f          @ found

add   r5, r5, #SIZEOF_MACHINE_DESC   @ next machine_desc

cmp   r5, r6

blo   1b

mov   r5, #0          @ unknown machine

2: mov   pc, lr

ENDPROC(__lookup_machine_type)

处理的进程和上面的__lookup_processor_type仍是挺类似的。这个例程接纳r1中传进来的机器号作为参数,然后,在一个由struct machine_desc结构体组成的段中查找和咱们的机器号匹配的struct machine_desc结构体,这个结构体在arch/arm/include/asm/mach/arch.h文件中界说,用于保存机器的信息:

struct machine_desc {

/*

* Note! The first four elements are used

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

*/

unsigned int    nr;      /* architecture number   */

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

unsigned int    io_pg_offst; /* byte offset for io

* page tabe entry */

const char      *name;   /* architecture name  */

unsigned long      boot_params; /* tagged list     */

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

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

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

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

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

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

void        (*fixup)(struct machine_desc *,

struct tag *, char **,

struct meminfo *);

void        (*map_io)(void);/* IO mapping funcTIon  */

void        (*init_irq)(void);

struct sys_TImer   *TImer;     /* system tick timer  */

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 = .;

这两个符号也是别离表明某种初始化的段的开端地址和完毕地址。为了找到段的填充内容,仍是得要了解一下究竟都有哪些struct machine_desc结构体类型变量声明晰要被放到这个段的。用要害字.arch.info.init 来查找悉数的内核源文件。在arch/arm/include/asm/mach/arch.h文件中咱们看到:

#define MACHINE_START(_type,_name)      \

static const struct machine_desc __mach_desc_##_type \

__used                     \

__attribute__((__section__(“.arch.info.init”))) = { \

.nr      = MACH_TYPE_##_type,     \

.name    = _name,

#define MACHINE_END            \

};

界说机器结构体,也便是.arch.info.init段中的内容,都是要经过两个宏MACHINE_START和MACHINE_END来完结的啊,MACHINE_START宏界说一个truct machine_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: Michel Pollet */

.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]       @ is first tag ATAG_CORE?

cmp   r5, #ATAG_CORE_SIZE

cmpne r5, #ATAG_CORE_SIZE_EMPTY

bne   1f

ldr   r5, [r2, #4]

ldr   r6, =ATAG_CORE

cmp   r5, r6

bne   1f

mov   pc, lr          @ atag pointer is ok

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中还保存有CPU ID信息。

回到arch/arm/kernel/head.S文件中的stext,之后就要进入初始化进程中比较要害的一步了,开端设置mmu,但首要要填充一个暂时的内核页表,映射4m的内存,这在初始化进程中是足够了:

bl  __create_page_tables

这个例程设置初始页表,这儿只设置最起码的数量,只需能使内核运转即可,r8  = machinfo,r9  = cpuid,r10 = procinfo,在r4寄存器中回来物理页表地址。

__create_page_tables例程在文件arch/arm/kernel/head.S中界说:

__create_page_tables:

pgtbl r4          @ page table address

// pgtbl是一个宏,本文件的前面部分有界说:

// .macro pgtbl, rd

// ldr   \rd, =(KERNEL_RAM_PADDR – 0x4000)

// .endm

// KERNEL_RAM_PADDR在本文件的前面有界说,为(PHYS_OFFSET + TEXT_OFFSET)

// PHYS_OFFSET在arch/arm/mach-s3c2410/include/mach/memory.h界说,

// 为UL(0x30000000)

// 而TEXT_OFFSET在arch/arm/Makefile中界说,为内核镜像在内存中到内存

// 开端方位的偏移(字节),为$(textofs-y)

// textofs-y也在文件arch/arm/Makefile中界说,

// 为textofs-y   := 0x00008000

// r4 = 30004000为暂时页表的开端地址

// 首要便是初始化16K的页表,高12位虚拟地址为页表索引,所认为

// 4K*4 = 16K,大页表,每一个页表项,映射1MB虚拟地址。

// 这个当地还来了个循环展开,以优化功能。

mov   r0, r4

mov   r3, #0

add   r6, r0, #0x4000

1: str   r3, [r0], #4

str   r3, [r0], #4

str   r3, [r0], #4

str   r3, [r0], #4

teq   r0, r6

bne   1b

ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

// PROCINFO_MM_MMUFLAGS在arch/arm/kernel/asm-offsets.c文件中界说,

// 为DEFINE(PROCINFO_MM_MMUFLAGS, 

// offsetof(struct proc_info_list, __cpu_mm_mmu_flags));

// R10寄存器保存的指针指向是咱们前面找到的proc_info_list结构嘛。

// 为内核的榜首个MB创立共同的映射,认为翻开MMU做准备,这个映射将会被

// paging_init()移除,这儿运用程序计数器来取得相应的段的基地址。

// 这个当地是直接映射。

mov   r6, pc

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

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

str   r3, [r4, r6, lsl #2]     @ identity mapping

// 接下来为内核的直接映射区设置页表。KERNEL_START在文件的前面界说,

// 为KERNEL_RAM_VADDR,即内核的虚拟地址。

// 而KERNEL_RAM_VADDR在文件的前面界说,则为(PAGE_OFFSET + TEXT_OFFSET)

// 映射完好的内核代码段,初始化数据段。

// PAGE_OFFSET为内核镜像开端的虚拟地址,在

// arch/arm/include/asm/memory.h中界说。在装备内核时选定具体值,默许

// 为0xC0000000。

// 由于最高12位的值是页表中的偏移地址,而第三高的四位必定为0,

// 每个页表项为4字节,右移20位之后,还得再左移两位回来,所以,这儿只// 是左移18位。

// R3寄存器在经过了上面的操作之后,实践上是变成了指向内核镜像代码段

// 的指针(物理地址),在这个当地,再一次为内核镜像的榜首个MB做了映射。

// R6随后指向了内核镜像的尾部。R0为页表项指针。

// 这儿以1MB为单位来映射内核镜像。

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

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

ldr   r6, =(KERNEL_END – 1)

add   r0, r0, #4

add   r6, r4, r6, lsr #18  //得到页表的完毕物理地址

1: cmp   r0, r6

add   r3, r3, #1 << 20

strls r3, [r0], #4

bls   1b

// 为了运用发动参数,将物理内存的榜首MB映射到内核虚拟地址空间的

// 榜首个MB,r4寄存的是页表的地址。这儿的PAGE_OFFSET的虚拟地址

// 比上面的KERNEL_START要小0x8000

add   r0, r4, #PAGE_OFFSET >> 18

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

.if   (PHYS_OFFSET & 0x00f00000)

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

.endif

str   r6, [r0]

// 上面的这个进程显得好像有些剩余。

// 总结一下,这个树立暂时页表的进程:

// 1、为内核镜像的榜首个MB树立直接映射

// 2、为内核镜像完好的树立从虚拟地址到物理地址的映射

// 3、为物理内存的榜首个MB树立到内核的虚拟地址空间的榜首个MB的映射。

// OK,内核的暂时页表树立完毕。整个初始化暂时页表的进程都没有修正R8,

// R9和R10。

mov   pc, lr

ENDPROC(__create_page_tables)

回到stext:

ldr   r13, __switch_data    @ address to jump to after

@ mmu has been enabled

这个当地实践上是在r13中保存了另一个例程的地址。后边的剖析中,遇到履行到这个例程的状况时会有具体阐明。

接着看stext:

adr   lr, BSYM(__enable_mmu)      @ return (PIC) address

BSYM()是一个宏,在文件arch/arm/include/asm/unified.h中界说,为:

#define BSYM(sym) sym

也便是说这个句子也仅仅是把__enable_mmu例程的地址加载进lr寄存器中。为了便利之后调用的函数回来时,直接履行__enable_mmu例程。

接着看stext下一句:

ARM( add   pc, r10, #PROCINFO_INITFUNC )

ARM()也是一个宏,相同在文件arch/arm/include/asm/unified.h中界说,当装备内核为生成ARM镜像,则为:#define ARM(x…)  x

所以这一条句子也便是在调用一个例程。R10中保存的是procinfo结构的地址。PROCINFO_INITFUNC符号在arch/arm/kernel/asm-offsets.c文件中界说,为:

DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));

也便是调用结构体proc_info_list的__cpu_flush成员函数。回去检查arch/arm/mm/proc-arm920.S文件中struct proc_info_list结构体的变量的界说,能够看到这个成员为:

b  __arm920_setup

也便是说,在设置好内核暂时页表之后调用了例程__arm920_setup,这个例程相同在arch/arm/mm/proc-arm920.S中:

__arm920_setup:

mov   r0, #0

mcr   p15, 0, r0, c7, c7    @ invalidate I,D caches on v4

mcr   p15, 0, r0, c7, c10, 4      @ drain write buffer on v4

#ifdef CONFIG_MMU

mcr   p15, 0, r0, c8, c7    @ invalidate I,D TLBs on v4

#endif

adr   r5, arm920_crval

ldmia r5, {r5, r6}

mrc   p15, 0, r0, c1, c0    @ get control register v4

bic   r0, r0, r5

orr   r0, r0, r6

mov   pc, lr

这一段首要使i,d caches内容无效,然后铲除write buffer,接着使TLB内容无效。接下来加载变量arm920_crval的地址,咱们看到arm920_crval变量的内容为:

rm920_crval:

crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130

crval为一个宏,在arch/arm/mm/proc-macros.S中界说:

.macro crval, clear, mmuset, ucset

#ifdef CONFIG_MMU

.word \clear

.word \mmuset

#else

.word \clear

.word \ucset

#endif

.endm

其实也便是界说两个变量罢了。之后,在r0中,得到了咱们想要往协处理器相应寄存器中写入的内容。

之后的 __arm920_setup回来,mov  pc, lr,便是调用例程__enable_mmu,这个例程在文件arch/arm/kernel/head.S中:

__enable_mmu:

#ifdef CONFIG_ALIGNMENT_TRAP

orr   r0, r0, #CR_A

#else

bic   r0, r0, #CR_A

#endif

#ifdef CONFIG_CPU_DCACHE_DISABLE

bic   r0, r0, #CR_C

#endif

#ifdef CONFIG_CPU_BPREDICT_DISABLE

bic   r0, r0, #CR_Z

#endif

#ifdef CONFIG_CPU_ICACHE_DISABLE

bic   r0, r0, #CR_I

#endif

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

domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \

domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \

domain_val(DOMAIN_IO, DOMAIN_CLIENT))

mcr   p15, 0, r5, c3, c0, 0    @ load domain access register

mcr   p15, 0, r4, c2, c0, 0    @ load page table pointer

b  __turn_mmu_on

在这儿设置了页目录地址(r4寄存器中保存),然后设置domain的维护,在前面树立页表的例程中,留意到,页表项的操控信息,是从struct proc_info_list结构体的某字段中取的,其页目录项的 domain都是0,domain寄存器中的domain 0对应的是0b11,表明拜访形式为manager,不受约束。在这儿一同也完结r0的某些位的进一步设置。

然后,__enable_mmu例程又调用了__turn_mmu_on,在同一个文件中界说:

__turn_mmu_on:

mov   r0, r0

mcr   p15, 0, r0, c1, c0, 0    @ write control reg

mrc   p15, 0, r3, c0, c0, 0    @ read id reg

mov   r3, r3

mov   r3, r13

mov   pc, r3

ENDPROC(__turn_mmu_on)


接下来写操控寄存器:

mcr p15, 0, r0, c1, c0 ,0

悉数设置就此收效,到此算是完结了翻开d,icache和mmu的作业。

留意:arm的d cache有必要和mmu一同翻开,而i cache能够独自翻开。其实,cache和mmu的联络实在是严密,每一个页表项都有标志标明是否是cacheable的,能够说原本便是规划一同运用的


前面有提到过,r13中寄存的其实是别的一个例程的地址,其值是变量__switch_data的榜首个字段,即一个函数指针的值,__switch_data变量是在arch/arm/kernel/head-common.S中界说的:

__switch_data:

.long __mmap_switched

.long __data_loc         @ r4

.long _data           @ r5

.long __bss_start        @ r6

.long _end            @ r7

.long processor_id       @ r4

.long __machine_arch_type      @ r5

.long __atags_pointer       @ r6

.long cr_alignment       @ r7

.long init_thread_union + THREAD_START_SP @ sp

前面的ldr r13 __switch_data,实践上也便是加载符号__mmap_switched的地址,实践上__mmap_switched是一个arch/arm/kernel/head-common.S中界说的例程。接着来看这个例程的界说,在arch/arm/kernel/head-common.S文件中:

__mmap_switched:

adr   r3, __switch_data + 4

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

cmp   r4, r5          @ Copy data segment if needed

1: cmpne r5, r6

ldrne fp, [r4], #4

strne fp, [r5], #4

bne   1b

mov   fp, #0          @ Clear BSS (and zero fp)

1: cmp   r6, r7

strcc fp, [r6],#4

bcc   1b

ldmia r3, {r4, r5, r6, r7, sp}

str   r9, [r4]        @ Save processor ID

str   r1, [r5]        @ Save machine type

str   r2, [r6]        @ Save atags pointer

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

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

b  start_kernel

ENDPROC(__mmap_switched)
 

这个例程完结如下作业:

1、使r3指向__switch_data变量的第二个字段(从1开端计数)。

2、履行了一条加载指令,也便是在r4, r5, r6, r7寄存器中别离加载4个符号__data_loc,_data, __bss_start ,_end的地址,这四个符号都是在链接脚本arch/arm/kernel/vmlinux.lds.S中呈现的,标识了镜像各个段的地址,咱们应该不难猜出他们所代表的段。

3、假如需要的话则仿制数据段(数据段和BSS段是紧邻的)。

4、初始化BSS段,悉数清零,BSS是未初始化的全局变量区域。
5、又看到一条加载指令,相同在一组寄存器中加载借个符号的地址,r4中为processor_id,r5中为__machine_arch_type, r6中为__atags_pointer, r7中为cr_alignment ,sp中为init_thread_union + THREAD_START_SP。

6、接着咱们看到下面的几条句子,则是用前面获取的信息来初始化那些全局变量r9,机器号被保存到processor_id处;r1寄存器的值,机器号,被保存到变量__machine_arch_type中,其他的也相同。

7、从头设置仓库指针,指向init_task的仓库。init_task是体系的榜首个任务,init_task的仓库在task structure的后8K,咱们后边会看到。 
8、最终就要跳到C代码的 start_kernel。 
   b  start_kernel
到此为止,汇编部分的初始化代码就完毕了

O,My God.初始化代码的汇编部分总算完毕。然后进入了与体系结构无关的Linux内核部分。start_kernel()会调用一系列初始化函数来设置中止,履行进一步的内存装备。

现在让咱们来回想一下现在的体系状况: 
暂时页表现已树立,在0X30004000处,映射了映像文件巨细空间,虚地址0XC000000被映射到0X30000000。CACHE,MMU 都现已翻开。仓库用的是任务init_task的仓库。 

假如认为到了c代码能够松一口气的话,就大错特措了,linux的c也不比汇编好懂多少,相反倒掩盖了汇编的一些和机器相关的部分,有时分更难明。其实作 为编写操作体系的c代码,只不过是汇编的另一种写法,和机器代码的联络是很严密的。别的,这些start_kernel()中调用的C函数,每一个都具有无足轻重的位置,它们中的许多都肩负着初始化内核中的某个子体系的重要任务,而Linux内核中每一个子体系都扑朔迷离,牵涉到各种软件、硬件的杂乱算法,所以了解起来倒真的是挺困难的。 

start_kernel函数在 init/main.c中界说:

528 asmlinkage void __init start_kernel(void)

529 {

530   char * command_line;

531   extern struct kernel_param __start___param[], __stop___param[];

532

533   smp_setup_processor_id();

534

535   /*

536    * Need to run as early as possible, to initialize the

537    * lockdep hash:

538    */

539   lockdep_init();

540   debug_objects_early_init();

541

542   /*

543    * Set up the the initial canary ASAP:

544    */

545   boot_init_stack_canary();

546

547   cgroup_init_early();

548

549   local_irq_disable();

550   early_boot_irqs_off();

551   early_init_irq_lock_class();

552

553 /*

554  * Interrupts are still disabled. Do necessary setups, then

555  * enable them

556  */

557   lock_kernel();

558   tick_init();

559   boot_cpu_init();

560   page_address_init();

561   printk(KERN_NOTICE “%s”, linux_banner);

562   setup_arch(&command_line);

563   mm_init_owner(&init_mm, &init_task);

564   setup_command_line(command_line);

565   setup_nr_cpu_ids();

566   setup_per_cpu_areas();

567   smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */

568

569   build_all_zonelists();

570   page_alloc_init();

571

572 printk(KERN_NOTICE “Kernel command line: %s\n”, boot_command_line);

573   parse_early_param();

574   parse_args(“Booting kernel”, static_command_line,

575              __start___param, __stop___param – __start___param,

576              &unknown_bootoption);

577   /*

578    * These use large bootmem allocations and must precede

579    * kmem_cache_init()

580    */

581   pidhash_init();

582   vfs_caches_init_early();

583   sort_main_extable();

584   trap_init();

585   mm_init();

586   /*

587    * Set up the scheduler prior starting any interrupts (such as the

588    * timer interrupt). Full topology setup happens at smp_init()

589    * time – but meanwhile we still have a functioning scheduler.

590   */

591   sched_init();

592   /*

593    * Disable preemption – early bootup scheduling is extremely

594    * fragile until we cpu_idle() for the first time.

595    */

596   preempt_disable();

597   if (!irqs_disabled()) {

598        printk(KERN_WARNING “start_kernel(): bug: interrupts were “

599                            “enabled *very* early, fixing it\n”);

600        local_irq_disable();

601   }

602   rcu_init();

603   radix_tree_init();

604   /* init some links before init_ISA_irqs() */

605   early_irq_init();

606   init_IRQ();

607   prio_tree_init();

608   init_timers();

609   hrtimers_init();

610   softirq_init();

611   timekeeping_init();

612   time_init();

613   profile_init();

614   if (!irqs_disabled())

615           printk(KERN_CRIT “start_kernel(): bug: interrupts were “

616                            “enabled early\n”);

617   early_boot_irqs_on();

618   local_irq_enable();

619

620   /* Interrupts are enabled now so all GFP allocations are safe. */

621   gfp_allowed_mask = __GFP_BITS_MASK;

622

623   kmem_cache_init_late();

624

625   /*

626    * HACK ALERT! This is early. We're enabling the console before

627    * we've done PCI setups etc, and console_init() must be aware of

628    * this. But we do want output early, in case something goes wrong.

629    */

630   console_init();

631   if (panic_later)

632           panic(panic_later, panic_param);

633

634   lockdep_info();

635

636   /*

637    * Need to run this when irqs are enabled, because it wants

638    * to self-test [hard/soft]-irqs on/off lock inversion bugs

639    * too:

640    */

641   locking_selftest();

642

643 #ifdef CONFIG_BLK_DEV_INITRD

644   if (initrd_start && !initrd_below_start_ok &&

645   page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {

646   printk(KERN_CRIT “initrd overwritten (0x%08lx < 0x%08lx) - "

647               “disabling it.\n”,

648               page_to_pfn(virt_to_page((void *)initrd_start)),

649               min_low_pfn);

650               initrd_start = 0;

651   }

652 #endif

653   page_cgroup_init();

654   enable_debug_pagealloc();

655   kmemtrace_init();

656   kmemleak_init();

657   debug_objects_mem_init();

658   idr_init_cache();

659   setup_per_cpu_pageset();

660   numa_policy_init();

661   if (late_time_init)

662   late_time_init();

663   sched_clock_init();

664   calibrate_delay();

665   pidmap_init();

666   anon_vma_init();

667 #ifdef CONFIG_X86

668   if (efi_enabled)

669           efi_enter_virtual_mode();

670 #endif

671   thread_info_cache_init();

672   cred_init();

673   fork_init(totalram_pages);

674   proc_caches_init();

675   buffer_init();

676   key_init();

677   security_init();

678   vfs_caches_init(totalram_pages);

679   signals_init();

680   /* rootfs populating might need page-writeback */

681   page_writeback_init();

682 #ifdef CONFIG_PROC_FS

683   proc_root_init();

684 #endif

685   cgroup_init();

686   cpuset_init();

687   taskstats_init_early();

688   delayacct_init();

689

690   check_bugs();

691

692   acpi_early_init(); /* before LAPIC and SMP init */

693   sfi_init_late();

694

695   ftrace_init();

696

697   /* Do the rest non-__init'ed, we're now alive */

698   rest_init();

699 }

接着咱们来近距离的调查一下start_kernel函数中调用的这些重量级的函数。

首要来看setup_arch(&command_line)函数,这个函数(关于咱们的mini2440渠道来说)在arch/arm/kernel/setup.c中界说:

664 void __init setup_arch(char **cmdline_p)

665 {

666   struct tag *tags = (struct tag *)&init_tags;

667   struct machine_desc *mdesc;

668   char *from = default_command_line;

669

670   unwind_init();

671

672   setup_processor();

673   mdesc = setup_machine(machine_arch_type);

674   machine_name = mdesc->name;

675

676   if (mdesc->soft_reboot)

677          reboot_setup(“s”);

678

679   if (__atags_pointer)

680           tags = phys_to_virt(__atags_pointer);

681   else if (mdesc->boot_params)

682           tags = phys_to_virt(mdesc->boot_params);

683

684   /*

685    * If we have the old style parameters, convert them to

686    * a tag list.

687    */

688   if (tags->hdr.tag != ATAG_CORE)

689           convert_to_tag_list(tags);

690   if (tags->hdr.tag != ATAG_CORE)

691           tags = (struct tag *)&init_tags;

692

693   if (mdesc->fixup)

694           mdesc->fixup(mdesc, tags, &from, &meminfo);

695

696   if (tags->hdr.tag == ATAG_CORE) {

697           if (meminfo.nr_banks != 0)

698                   squash_mem_tags(tags);

699          save_atags(tags);

700          parse_tags(tags);

701   }

702

703   init_mm.start_code = (unsigned long) _text;

704   init_mm.end_code   = (unsigned long) _etext;

705   init_mm.end_data   = (unsigned long) _edata;

706   init_mm.brk        = (unsigned long) _end;

707

708   /* parse_early_param needs a boot_command_line */

709   strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

710

711   /* populate cmd_line too for later use, preserving boot_command_line */

712   strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);

713   *cmdline_p = cmd_line;

714

715   parse_early_param();

716

717   paging_init(mdesc);

718   request_standard_resources(&meminfo, mdesc);

719

720 #ifdef CONFIG_SMP

721   smp_init_cpus();

722 #endif

723

724   cpu_init();

725   tcm_init();

726

727   /*

728    * Set up various architecture-specific pointers

729    */

730   init_arch_irq = mdesc->init_irq;

731   system_timer = mdesc->timer;

732   init_machine = mdesc->init_machine;

733

734 #ifdef CONFIG_VT

735 #if defined(CONFIG_VGA_CONSOLE)

736   conswitchp = &vga_con;

737 #elif defined(CONFIG_DUMMY_CONSOLE)

738   conswitchp = &dummy_con;

739 #endif

740 #endif

741   early_trap_init();

742 }

来看一些咱们比较感兴趣的当地:

1、666行,struct tag指针类型的局部变量指向了默许的tag列表init_tags,该静态变量在setup_arch()界说同文件的前面有如下界说:

636 /*

637  * This holds our defaults.

638  */

639 static struct init_tags {

640         struct tag_header hdr1;

641         struct tag_core   core;

642         struct tag_header hdr2;

643         struct tag_mem32  mem;

644         struct tag_header hdr3;

645 } init_tags __initdata = {

646         { tag_size(tag_core), ATAG_CORE },

647         { 1, PAGE_SIZE, 0xff },

648         { tag_size(tag_mem32), ATAG_MEM },

649         { MEM_SIZE, PHYS_OFFSET },

650         { 0, ATAG_NONE }

651 };

第679行查看__atags_pointer指针的有效性,这个指针是在前面,跳转到start_kernel函数的汇编例程最终设置的几个变量之一,用的是R2寄存器的值。假如bootloader经过R2传递了tag列表的话,自然是要运用bootloader穿的进来的tag列表的。

2、第688行的字符指针类型的局部变量from指向了default_command_line静态变量,这个变量相同在前面有界说:

124 static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;

传递给内核的指令行参数,是能够在内核装备的时分设置的。

3、第673行以machine_arch_type为参数调用了setup_machine()函数,而这个函数的界说为:

369 static struct machine_desc * __init setup_machine(unsigned int nr)

370 {

371         struct machine_desc *list;

372

373         /*

374          * locate machine in the list of supported machines.

375          */

376         list = lookup_machine_type(nr);

377         if (!list) {

378                 printk(“Machine configuration botched (nr %d), “

379                        ” unable to continue.\n”, nr);

380                 while (1);

381         }

382

383         printk(“Machine: %s\n”, list->name);

384

385         return list;

386 }

在arch/arm/kernel/head-common.S文件中,咱们看到了一个关于__lookup_machine_type例程的封装的可被C言语程序调用的汇编言语编写的函数lookup_machine_type(),接纳机器号,查表,然后回来匹配的struct machine_desc结构体的指针。在这儿,关于咱们的mini2440,回来的自然是arch/arm/mach-s3c2440/ mach-mini2440.c文件中界说的结构体了:

MACHINE_START(MINI2440, “MINI2440”)

/* Maintainer: Michel Pollet */

.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

然后,machine_desc结构体的name成员的值被赋给全局变量machine_name。

第681行,若bootloader没有传递tag列表给内核,则检测machine_desc结构体的boot_params字段,看看特定的渠道是否传递了符号列表。

第730、731、732行别离将machine_desc结构体的init_irq、timer和init_machine成员值赋给了三个全局变量init_arch_irq、system_timer和init_machine,便是设置特定体系结构的指针。初始化的后边阶段自然会用到。

start_kernel()函数调用同文件下的rest_init(void)函数,rest_init(void)函数调用 kernel_thread()函数以发动榜首个中心线程,该线程履行kernel_init()函数,而原履行序列会调用cpu_idle(),等候调度。

作为中心线程的kernel_init()函数持续完结一些设置,并在最终调用同文件下的init_post()函数,而该函数挂在根文件体系,翻开/dev/console设备,重定向stdin、stdout和stderr到操控台。之后,它查找文件体系中的init程序(也能够由“init=”指令行参数指定init程序),并运用run_init_process()函数履行init程序。(事实上,run_init_process()函数又调用了kernel_execve()来实践履行程序)。查找init程序的次序为/sbin/init、/etc/init、/bin/init、和/bin/sh。在嵌入式体系中,大都状况下,能够给内核传入一个简略的shell脚原本发动必需的嵌入式应用程序。

至此,绵长的Linux内核引导和发动进程就完毕了,而kernel_init()对应的由rest_init(void)函数创立的榜首个线程也进入用户形式。

 

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/changshang/peixun/89560.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部