嵌入式系统的启动过程

嵌入式Linux启动分为两个部分,系统引导Linux启动。系统引导将完成Linux装入内存前,初始化CPU和相关IO设备,并将Linux调入内存的工作。系统引导主要由BootLoader实现。在BootLoaderLinux内核调入内存之后,将权力交给LinuxKernel,进入Linux的启动部分。以下详细分析启动的过程与使用的文件。

一、系统引导与BootLoader
        BootLoader
因嵌入式系统的不同与PC机有很大不同,这里将以Hyper250(Inter Xscale GDPXA250)的启动为例来分析。由于没有BIOS驱动主板,EnbeddedOS必须由bootloader驱动所有的硬件,并完成硬件的初始化工作。
        
所有的初始化文件在hyper250/Bootloader目录下。
        
        
首先分析开机运行的分件:
                hyper250/Bootloader/X-Hyper250R1.1-Boot/src/start_xscale.S
        
文件包含两个库文件:
                hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/config.h
                hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/start_xscale.h
        
文件config.h主要完成系统各硬件的宏定义与设定,xscale.h主要完成对系统芯片的及系统操作的设定。

        
以下分析config.h文件:
        (1)
存储总线设备的宏定义:定义Flash的大小、字长等信息,定义SRAM的基址、大小和块大小。
        (2)
动态内存设定:定义DRAM的大小、基址。
        (3)
软件包信息:包名称、版本号。
        (4)
设定BOOT LOADER的位置:在DRAMSRAM的最大值、DRAM装入位置、栈的基址。
        (5)
设定kernel的位置:在DRAMSRAM的基址、KERNEL的最大值、KERNEL中块的数量。
        (6)
设定文件系统的位置:根目录在DRAMSRAM的基址、文件系统的最大值、文件系统中块的数量。
        (7)
设定LOADER程序:LOADER程序的静态内存基址、LOADER程序的最大值、块的数量。
        (8)
网络设定
        
        
以下分析start_xcalse.h文件:
        (1)
定义内存基址(A0000000)
        (2)
定义中断基址(40D00000)和中断保护栈的偏移量
        (3)
定义时钟管理基址(41300000)和寄存器偏移及其初始值
        (4)
定义GPIO接口寄存器基址(40E00000)及各寄存器的偏移
        (5)
定义GPIO接口各寄存器的初始值
        (6)
定义内存控制寄存器基址(48000000)和各寄存器的偏移
        (7)
定义内存控制寄存器的初始值
        (8)
定义电源管理寄存器的参数
        (9)
定义FFUART寄存器的基址(40100000)和各寄存器的偏移
        (10)
定义FFUART各寄存器的初始值
        
        
以下分析start_xcalse.S文件:
        (1)
设定中断基址(40D00000),完成中断保护栈的初始化
        (2)
初始化GPIO接口
        (3)
初始化内存SDRAM
        (4)
BootloaderFlash拷贝到SDRAM
        (5)
装入Linux内核镜像,将内核从Flash(000C 0000)装入SDRAM(A0008000).
        (6)
设定保护栈
        (7)
调用main.c的主函数c_main()        
        
        
以上start_xcalse.S通过APCS的编程标准书写的汇编文件初始化了系统相关的硬件,并且完成了BootLoader的装入内存和Linux内核的装入,最后将权力转交给main.c
        
以下将分析main.c文件:
                hyper250/Bootloader/X-Hyper250R1.1-Boot/src/main.c
        
以及两个库文件
                hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/main.h
                hyper250/Bootloader/X-Hyper250R1.1-Boot/src/include/scc.h

#2  

二、Linux启动过程分析

        1.Makefile
分析:
        
在分析arch/arm/boot/compressed目录下的文件的时候,对于Makefile的分析是很重要的,因为内核将在这个目录相产生。这里主要工作是对内核的压缩和解压工作。本目录在编译完成后将产生vmlinuxhead.omisc.ohead-xscale.opiggy.o这几个文件。其中vmlinux没有压缩过的内核。head.o是内核的头部文件,负责初始设置。misc.o将主要负责内核的解压工作,它在head.o之后。head-xscale.o文件主要针对Xscale的初始化,将在链接时与head.o合并。piggy.o是一个中间文件,其实是一个压缩的内核,只不过没有和初始化文件及解压文件链接而已。
        
        2.Decompress
分析:
        
BootLoader完成系统的引导以后并将Linux内核调入内存之后,调用bootLinux(),这个函数将跳转到kernel的起始位置。如果kernel没有压缩,就可以启动了。如果kernel压缩过,则要进行解压,在压缩过的kernel头部有解压程序。压缩过得kernel入口第一个文件源码位置在arch/arm/boot/compressed/head.S。它将调用函数decompress_kernel(),这个函数在文件arch/arm/boot/compressed/misc.c中,decompress_kernel()又调用proc_decomp_setup(),arch_decomp_setup()进行设置,然后使用在打印出信息“Uncompressing Linux...”后,调用gunzip()。将内核放于指定的位置。
        
启动首先运行的文件有:
                arch/arm/boot/compressed/head.S
                arch/arm/boot/compressed/head-xscale.S
                arch/arm/boot/compressed/misc.c
        
这些文件主要用于解压内核和以及启动内核映象。一旦内核启动,则这些文件所占内存空间将被释放。而且,一旦系统通过reset重起,当BootLoader将压缩过的内核放入内存中,首先执行的必然是这些代码。
        
        
以下分析head.S文件:
        (1)
对于各种Arm CPUDEBUG输出设定,通过定义宏来统一操作。
        (2)
设置kernel开始和结束地址,保存architecture ID
        (3)
如果在ARM2以上的CPU中,用的是普通用户模式,则升到超级用户模式,然后关中断。
        (4)
分析LC0结构delta offset,判断是否需要重载内核地址(r0存入偏移量,判断r0是否为零)
        
这里是否需要重载内核地址,我以为主要分析arch/arm/boot/Makefilearch/arm/boot/compressed/Makefilearch/arm/boot/compressed/vmlinux.lds.in三个文件,主要看vmlinux.lds.in链接文件的主要段的位置,LOAD_ADDR(_load_addr)0xA0008000,而对于TEXT_START(_text_start)的位置只设为0BSS_START(__bss_start)ALIGN(4)。对于这样的结果依赖于,对内核解压的运行方式,也就是说,内核解压前是在内存(RAM)中还是在FLASH上,因为这里,我们的BOOTLOADER将压缩内核(zImage)移到了RAM0xA0008000位置,我们的压缩内核是在内存(RAM)0xA0008000地址开始顺序排列,因此我们的r0获得的偏移量是载入地址(0xA0008000)。接下来的工作是要把内核镜像的相对地址转化为内存的物理地址,即重载内核地址。
        (5)
需要重载内核地址,将r0的偏移量加到BSS regionGOT table中。
        (6)
清空bss堆栈空间r2r3
        (7)
建立C程序运行需要的缓存,并赋于64K的栈空间。
        (8)
这时r2是缓存的结束地址,r4kernel的最后执行地址,r5kernel境象文件的开始地址。检查是否地址有冲突。
                
r5等于r2,使decompress后的kernel地址就在64K的栈之后。
        (9)
调用文件misc.c的函数decompress_kernel(),解压内核于缓存结束的地方(r2地址之后)。此时各寄存器值有如下变化:
                r0
为解压后kernel的大小
                r4
kernel执行时的地址
                r5
为解压后kernel的起始地址
                r6
CPU类型值(processor ID)
                r7
为系统类型值(architecture ID)
        (10)
reloc_start代码拷贝之kernel之后(r5+r0之后),首先清除缓存,而后执行reloc_start
        (11)reloc_start
r5开始的kernel重载于r4地址处。
        (12)
清除cache内容,关闭cache,将r7architecture ID赋于r1,执行r4开始的kernel代码。
        
        
关于head-xscale.S文件,它定义了xcale处理器的64kcache缓存的实现代码和关闭MMU及缓存的代码,这些代码将在链接过程中与head.S的合并。
        
        
关于misc.c文件,它引入了以下几个文件:
                include/linux/kernel.h
                include/asm-arm/arch-pxa/uncompress.h
                include/asm-arm/proc-armv/uncompress.h
                include/asm-arm/uaccess.h
                lib/inflate.c
        
以下分析misc.c文件的decompress_kernel()函数:
        (1)
首先传入参数:解压后内核地址,缓存开始地址,缓存结束地址,arch id。这些参数通过寄存器r0(r5),r1,r2,r3(r7)传入。
        (2)
接着执行proc_decomp_setup(),它在include/asm-arm/proc-armv/uncompress.h文件中。主要刷新并起用i cache,锁住交换缓存,这是一段嵌入的arm汇编代码。
        (3)
接着执行arch_decomp_setup(),它在include/asm-arm/arch-pxa/uncompress.h文件中,是一个空函数,用于扩展。
        (4)
然后执行makecrc(),它在lib/inflate.c中,主要将产生CRC-32 table,进行循环冗余校验。
        (5)
调用gunzip()解压kernel,它也在lib/inflate.c中。
        (6)
返回head.S,解压后kernel的长度传给r0,解压后的内核地址预先在r5中定义了。

#3  

3.kernel
进入文件分析:
        
随后系统将调入文件: arch/arm/kernel/head_armv.S arch/arm/kernel/head_armo.S 。对于 arm kernel 而言,有两套 .S 文件: _armv.S   _armo.S. 选择 _armv.S  还是 _armo.S  依赖于处理器。 ARM version 1, version 2,  都只支持 26 位的地址空间。 version 3 开始支持 32 位的地址空间,同时还向后兼容 26 位的地址空间。 version 4 开始不再向后兼容 26 位的地址空间。这里由于 Hyper250 使用的是 version7 ,故只涉及文件 head_armv.S

       
 head_armv.S 是内核的入口点,在内核被解压到预定位置后,它将运行。 这里简要说明其主要工作:
        (1)
首先,关中断并进入保护模式,这里将建立虚拟地址到物理地址的映射。 ( 见第二章内存分析 )

        (2)
调用 lookup_processor_type ,查询 CUP 和其 ID 是否在 .proc.info 表中,如果存在,则令 r10 指向此结构,在 CPU 的内核入口文件中。如果不是则提示 error p 并挂起。关于 r10 指向的结构,他所属的内核入口文件,以 Hyper250 为例: arch/arm/mm/proc-xcale.S
        
这里要要注意的是,此处操作的对象是由 vmlinux-armv.lds.in 链接文件定位的段 .proc.info 中,这个段定义在 proc-xcale.S 文件末尾,这里要注意,上面并没有使系统进入保护模式,所以在这里对 .proc.info 寻址的时候,为了得到相对地址,做了一个相对寻址的变换。这里好象只用了这个结构的前 3 位:处理器类型值 (value) ,处理器值掩码 (mask) MMU 标志值 (mmuflags) 。这 3 个值在分别放在寄存器 r5(0x69052100) r6(0xfffff7f0) r8(0x00000c0e) 中, r5 r6 只是用于和获得的处理器的 ID 相比较,而 r8 则有两个可能的值,分别表示 MMU 的状态:如果 MMU 开启,即 CACHE_WRITE_THROUGH ,则 r8=0x00000c0a ,否则 r8=0x00000c0e 。这里 r8 的值将会保持到初始页表时使用。
        r10
此时指向段 .proc.info 的开始地址。

        (3)
寄存器 r1 中的系统类型值 (unique architecture number) ,这个系统类型值的定义,并且由 bootloader 传入。在文件 arch/arm/tools/mach-types 中:
        machine_is_xxx        CONFIG_xxxx                        MACH_TYPE_xxx                number
        xhyper250R1        ARCH_PXA_XHYPER250R1                PXA_XHYPER250R1                200

        (4)
调用 lookup_architecture_type ,将以 r1 的值检查 .arch.info 表,这是个 struct machine_desc 由文件 arch/arm/mach-pxa/xhyper250R1.c 中的 MACHINE_START() 创建。假如没有此结构则提示 error a 并挂起。
        
这里要注意的是,段 .arch.info 的定位在 vmlinux-armv.lds.in 文件中紧接 .proc.info ,这个段定义在 include/asm-arm/mach/arch.h 文件中,使用了宏定义 MACHINE_START() 。文件首先定义了一个结构体 machine_desc ,段 .arch.info 主体部分使用了宏定义 MACHINE_START() 其中嵌入这个结构体。
        
通常来讲 MACHINE_START() 的实现应该在文件 arch/arm/kernel/arch.c 中,而这里 hyper250 的源码中, MACHINE_START() 宏定义在 arch/arm/mach-pxa/xhyper250R1.c 中完成了定义,下面详细分析这个结构:
        (A)MACHINE_START
        MACHINE_START(_type,_name)
这宏开始处嵌入一个静态结构 machine_desc ,并且立即声明段 .arch.info
        _type
MACH_TYPE(PXA_XHYPER250R1) ,用以赋值给 machine_desc 中的 nr ,这就是系统类型值 number(200)
        _name
是描述系统类型的字符串,用以赋值给 machine_desc 中的 name char*
        
以下几个宏定义均在包含在 machine_desc 的赋值中,也在段 .arch.info 中。
        (B)MAINTAINER
        MAINTAINER(n)
,这个 n 并没有赋值给 machine_desc 结构, n "Hybus Co,. ltd." 字符串,公司名字罢了。
        (C)BOOT_MEM
        BOOT_MEM(_pram,_pio,_vio)
,这里面很关键,又 3 个变量:
        _pram
,传值给 phys_ram :物理内存的开始地址,程序中赋值为: 0xa0000000
        _pio
,传值给 phys_io :物理 io 的开始地址,程序中赋值为: 0x40000000
        _vio
,传值给 io_pg_offst io 页表的偏移,程序中赋值为: _vio=0xfc000000 ,不过要进行转换: ((_vio)>>18)&0xfffc=0x3f00
        (D)BOOT_PARAMS
        BOOT_PARAMS(_params)
这个宏定义了启动参数页表的偏移: param_offset ,程序中赋值为: 0xa0000100         
        (E)FIXUP(
接下来三个宏定义分别是三个函数指针:这些函数都在 machine_desc 结构中定义并且在 xhyper250R1.c 中实现。 )
        FIXUP(fixup_xhyper250R1)
宏指向 fixup_xhyper250R1 函数,这个函数有 4 个参数:
        fixup_xhyper250R1(struct machine_desc *desc, struct param_struct *params, char **cmdline, struct meminfo *mi)        
        struct machine_desc
:这个结构体前面已经提过了。
        param_struct
:这个结构体定义在 include/asm/setup.h 中,这是一个向 kernel 传递参数的结构体。
        char **cmdline
:好像用于定义输出窗口行数。
        struct meminfo
:这个结构体定义在 include/asm/setup.h 中,这是一个对物理内存区间描述的结构体,它将整个地址空间分为 8 个区间,通常一个区必须是连续的地址并且是同一类型的设备,而用于特殊目的的地址将划分为一个独立的区。首先定义 nr_banks: 块号,然后是结构体 bank[NR_BANKS] NR_BANKS 8 。结构体 bank[NR_BANKS] 中有: start size node
        
下面分析这个函数 fixup_xhyper250R1 的工作,
        
首先,调用宏 SET_BANK 并赋值为 SET_BANK(0, 0xa0000000, 64*1024*1024) ,这个宏定义在 arch/arm/mach-pxa/generic.h 文件中。 SET_BANK 主要完成设置结构体 meminfo bank[_nr] start size node 。以上为例,则完成了 bank[0] 区间中的 start=0xa0000000 size=64*1024*1024=64M node=(__start) - PHYS_OFFSET) >> 27=0
        
接着,使 mi nr_banks=1 ,好象设定了这个结构只有一个区。要注意的是 meminfo 将在 page_init() 中用于初始化页面。         
        (F)MAPIO
        MAPIO(xhyper250R1_map_io)
宏指向 xhyper250R1_map_io 函数,这个函数没有参数,主要用于 io 地址从虚拟地址到物理地址的映射关系。
        
这个函数调用了 pxa_map_io() iotable_init(xhyper250R1_io_desc)
        pxa_map_io()
函数定义在 arch/arm/mach-pxa/generic.h 文件中,实现在 arch/arm/mach-pxa/generic.c 中,主要调用了 iotable_init() 函数来进行 io 地址的区间映象。 iotable_init(struct map_desc *) 函数中,参数 map_desc 结构体定义在文件 include/asm-arm/map.h 中,主要有: virtual physical length 和一些标志位: domain read write cache buffer 等。 iotable_init() 函数在文件 arch/arm/mm/mm-armv.c 中,循环调用 create_mapping() 来处理 map_desc 的映射关系。 create_mapping 函数主要工作就是将 io 的虚拟地址到物理地址的映射关系按照 PAGE_SIZE(4K) 的页来进行映射,同时还有段的映射关系。(关于内存的映射将在第 2 章中详细分析)         
        (G)INITIRQ
        INITIRQ(xhyper250R1_init_irq)
指向了 xhyper250R1_init_irq 函数,这个函数将主要完成中断的初始化,这里主要调用了函数  pxa_init_irq() ,这个函数实现在 arch/arm/mach-pxa/irq.c 中。接着调用了 set_GPIO_IRQ_edge() 函数,这个函数也在 irq.c 中。(关于中断的分析将在以后进行)

        
我们以上通过分析宏 MACHINE_START 而分析了结构体 machine_desc 的一个实例的赋值,我们这里其实只用这个结构体很少一部分信息,主要有三个参数内存物理内存的开始地址、物理 io 的开始地址、 io 页表的偏移,分别存于寄存器 r5(phys_ram=0xa0000000) r6(phys_io=0x40000000) r7(io_pg_offst=0x3f00) 中,并返回。

        (5)
初始化页表,映射了 4M RAM ,以使内核运行。
        
这里值得注意的是: r5 此时为物理内存开始地址 (0xa0000000) ,程序利用宏定义 pgtbl ,将 r4 成为页表首地址 0xC0004000 。然后清空内核目录 swapper_pg_dir 开始的 16K 空间。
        
        (6)
设置 lr 为返回地址 __ret ,以使下面的程序得以跳转返回。
        (7)
使 pc=[r10+12] ,也就是跳转到 _xscale_proc_init 结构中的 b __xscale_setup 位置,这个结构在 arch/arm/mm/proc-xcale.S 中。我们来看看这个结构:
        __pxa250_proc_info:                <--r10
指向这个地址
                .long   0x69052100
                .long   0xfffff7f0
        #if CACHE_WRITE_THROUGH
                .long   0x00000c0a
        #else
                .long   0x00000c0e        <--
这个参数传入了 r8
        #endif
                b       __xscale_setup    <--[r10+12]
                .long   cpu_arch_name
                .long   cpu_elf_name
                .long   HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP
                .long   cpu_pxa250_info
                .long   xscale_processor_functions
                .size   __pxa250_proc_info, . - __pxa250_proc_info
        
        __xscale_setup
相关的程序多是对协处理器 cp15 的操作,之中用到了宏 F_BIT|I_BIT|SVC_MODE         
        
相关的宏定义在文件 include/asm-arm/proc-armv/ptrace.h 中。
        #define SVC_MODE        0x13
        #define T_BIT           0x20
        #define F_BIT           0x40
        #define I_BIT           0x80
        
        (8)
通过 proc-xcale.S __xscale_setup 设置 MMU ,并通过 __ret 返回 head_armv.S
        (9)
__ret 返回处设置 lr 通过 __switch_data 返回到 __mmap_switched
        (10)
打开 MMU ,将 pipeline 清空,以使所有的内存得以正确的访问。并返回到 __mmap_switched
        (11)__mmap_switched
通过 __switch_data 获得数据,并设置了 stack pointer
        (12)
清空 BSS ,并保存 CPU 类型值 (processor ID) 以及系统类型 (machine type) 等。
        (13)
跳转到 start_kernel
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值