`
rl784rl
  • 浏览: 12584 次
最近访客 更多访客>>
社区版块
存档分类

Android arm linux kernel启动流程(二)

 
阅读更多

Android arm linux kernel启动流程(二)
2010年07月14日
  写这个总结的时候咱的心情是沉重的,因为还有好多东西没弄明白。。。感叹自己的知识还是浅薄得很,前途钱途漫漫阿~~不过基本脉络是清楚的,具体的细节只能留在以后有时间再啃了。这里的第二部分启动流程指的是解压后kernel开始执行的一部分代码,这部分代码和ARM体系结构是紧密联系在一起的,所以最好是将ARM ARCHITECTURE REFERENCE MANUL仔细读读,尤其里面关于控制寄存器啊,MMU方面的内容~ 
  前面说过解压以后,代码会跳到解压完成以后的vmlinux开始执行,具体从什么地方开始执行我们可以看看生成的vmlinux.lds(arch/arm/kernel/)这个文件:       OUTPUT_ARCH(arm) ENTRY(stext) jiffies = jiffies_64; SECTIONS { . = 0x80000000 + 0x00008000; .text.head : { _stext = .; _sinittext = .; *(.text.h       很明显我们的vmlinx最开头的section是.text.head,这里我们不能看ENTRY的内容,以为这时候我们没有操作系统,根本不知道如何来解析这里的入口地址,我们只能来分析他的section(不过一般来说这里的ENTRY和我们从seciton分析的结果是一样的),这里的.text.head section我们很容易就能在arch/arm/kernel/head.S里面找到,而且它里面的第一个符号就是我们的stext:        .section ".text.head", "ax" ENTRY(stext) msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode @ and irqs disabled mrc p15, 0, r9, c0, c0 @ get processor id bl __lookup_processor_type @ r5=procinfo r9=cpuid         这里的ENTRY这个宏实际我们可以在include/linux/linkage.h里面找到,可以看到他实际上就是声明一个GLOBAL Symbol,后面的ENDPROC和END唯一的区别是前面的声明了一个函数,可以在c里面被调用。       #ifndef ENTRY #define ENTRY(name) \ .globl name; \ ALIGN; \ name: #endif #ifndef WEAK #define WEAK(name) \ .weak name; \ name: #endif #ifndef END #define END(name) \ .size name, .-name #endif /* If symbol 'name' is treated as a subroutine (gets called, and returns) * then please use ENDPROC to mark 'name' as STT_FUNC for the benefit of * static analysis tools such as stack depth analyzer. */ #ifndef ENDPROC #define ENDPROC(name) \ .type name, @function; \ END(name) #endif        找到了vmlinux的起始代码我们就来进行分析了,先总体概括一下这部分代码所完成的功能,head.S会首先检查proc和arch以及atag的有效性,然后会建立初始化页表,并进行CPU必要的处理以后打开MMU,并跳转到start_kernel这个symbol开始执行后面的C代码。这里有很多变量都是我们进行kernel移植时需要特别注意的,下面会一一讲到。
  在这里我们首先看看这段汇编开始跑的时候的寄存器信息,这里的寄存器内容实际上是同bootloader跳转到解压代码是一样的,就是r1=arch  r2=atag addr。下面我们就具体来看看这个head.S跑的过程:        msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode @ and irqs disabled mrc p15, 0, r9, c0, c0 @ get processor id         首先进入SVC模式并关闭所有中断,并从arm协处理器里面读到CPU ID,这里的CPU主要是指arm架构相关的CPU型号,比如ARM9,ARM11等等。        然后跳转到__lookup_processor_type,这个函数定义在head-common.S里面,这里的bl指令会保存当前的pc在lr里面,最后__lookup_processor_type会从这个函数返回,我们具体看看这个函数:              __lookup_processor_type: adr r3, 3f ldmda r3, {r5 - r7} 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)          他这里的执行过程其实比较简单就是在__proc_info_begin和__proc_info_end这个段里面里面去读取我们注册在里面的proc_info_list这个结构体,这个结构体的定义在arch/arm/include/asm/procinfo.h,具体实现根据你使用的cpu的架构在arch/arm/mm/里面找到具体的实现,这里我们使用的ARM11是proc-v6.S,我们可以看看这个结构体:         .section ".proc.info.init", #alloc, #execinstr /* * Match any ARMv6 processor core. */ .type __v6_proc_info, #object __v6_proc_info: .long 0x0007b000 .long 0x0007f000 .long PMD_TYPE_SECT | \ PMD_SECT_BUFFERABLE | \ PMD_SECT_CACHEABLE | \ PMD_SECT_AP_WRITE | \ PMD_SECT_AP_READ .long PMD_TYPE_SECT | \ PMD_SECT_XN | \ PMD_SECT_AP_WRITE | \ PMD_SECT_AP_READ b __v6_setup .long cpu_arch_name .long cpu_elf_name .long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|H WCAP_EDSP|HWCAP_JAVA .long cpu_v6_name .long v6_processor_functions .long v6wbi_tlb_fns .long v6_user_fns .long v6_cache_fns .size __v6_proc_info, . - __v6_proc_info         对着.h我们就知道各个成员变量的含义了,他这里lookup的过程实际上是先求出这个proc_info_list的实际物理地址,并将其内容读出,然后将其中的mask也就是我们这里的0x007f000与寄存器与之后与0x007b00进行比较,如果一样的话呢就校验成功了,如果不一样呢就会读下一个proc_info的信息,因为proc一般都是只有一个的,所以这里一般不会循环,如果检测正确寄存器就会将正确的proc_info_list的物理地址赋给寄存器,如果检测不到就会将寄存器值赋0,然后通过LR返回。          bl __lookup_machine_type @ r5=machinfo movs r8, r5 @ invalid machine (r5=0)? beq __error_a @ yes, error 'a'         检测完proc_info_list以后就开始检测machine_type了,这个函数的实现也在head-common.S里面,我们看看它具体的实现:         __lookup_machine_type: adr r3, 3b 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)           这里的过程基本上是同proc的检查是一样的,这里主要检查芯片的类型,比如我们现在的芯片是MSM7X27FFA,这也是一个结构体,它的头文件在arch/arm/include/asm/arch/arch.h里面(machine_desc),它具体的实现根据你对芯片类型的选择而不同,这里我们使用的是高通的7x27,具体实现在arch/arm/mach-msm/board-msm7x27.c里面,这些结构体最后都会注册到_arch_info_begin和_arch_info_end段里面,具体的大家可以看看vmlinux.lds或者system.map,这里的lookup会根据bootloader传过来的nr来在__arch_info里面的相匹配的类型,没有的话就寻找下一个machin_desk结构体,直到找到相应的结构体,并会将结构体的地址赋值给寄存器,如果没有的话就会赋值为0的。一般来说这里的machine_type会有好几个,因为不同的芯片类型可能使用的都是同一个cpu架构。
  对processor和machine的检查完以后就会检查atags parameter的有效性,关于这个atag具体的定义我们可以在./include/asm/setup.h里面看到,它实际是一个结构体和一个联合体构成的结合体,里面的size都是以字来计算的。这里的atags param是bootloader创建的,里面包含了ramdisk以及其他memory分配的一些信息,存储在boot.img头部结构体定义的地址中,具体的大家可以看咱以后对bootloader的分析~         __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)          这里对atag的检查主要检查其是不是以ATAG_CORE开头,size对不对,基本没什么好分析的,代码也比较好看~ 下面我们来看后面一个重头戏,就是创建初始化页表,说实话这段内容我没弄清楚,它需要对ARM VIRT MMU具有相当的理解,这里我没有太多的时间去分析spec,只是粗略了翻了ARM V7的manu,知道这里建立的页表是arm的secition页表,完成内存开始1m内存的映射,这个页表建立在kernel和atag paramert之间,一般是4000-8000之间~具体的代码和过程我这里就不贴了,大家可以看看参考的链接,看看其他大虾的分析,我还没怎么看明白,等以后仔细研究ARM MMU的时候再回头来仔细研究了,不过代码虽然不分析,这里有几个重要的地址需要特别分析下~
  这几个地址都定义在arch/arm/include/asm/memory.h,我们来稍微分析下这个头文件,首先它包含了arch/memory.h,我们来看看arch/arm/mach-msm/include/mach/memory.h,在这个里面定义了#define PHYS_OFFSET     UL(0x00200000) 这个实际上是memory的物理内存初始地址,这个地址和我们以前在boardconfig.h里面定义的是一致的。然后我们再看asm/memory.h,他里面定义了我们的memory虚拟地址的首地址#define PAGE_OFFSET     UL(CONFIG_PAGE_OFFSET)。      
  另外我们在head.S里面看到kernel的物理或者虚拟地址的定义都有一个偏移,这个偏移又是从哪来的呢,实际我们可以从arch/arm/Makefile里面找到:textofs-y   := 0x00008000     TEXT_OFFSET := $(textofs-y) 这样我们再看kernel启动时候的物理地址和链接地址,实际上它和我们前面在boardconfig.h和Makefile.boot里面定义的都是一致的~
  建立初始化页表以后,会首先将__switch_data这个symbol的链接地址放在sp里面,然后获得__enable_mmu的物理地址,然后会跳到__proc_info_list里面的INITFUNC执行,这个偏移是定义在arch/arm/kernel/asm-offset.c里面,实际上就是取得__proc_info_list里面的__cpu_flush这个函数执行。        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         这个__cpu_flush在这里就是我们proc-v6.S里面的__v6_setup函数了,具体它的实现我就不分析了,都是对arm控制寄存器的操作,这里转一下它对这部分操作的注释,看完之后就基本知道它完成的功能了。
  /*
  *  __v6_setup
  *
  *  Initialise TLB, Caches, and MMU state ready to switch the MMU
  *  on.  Return in r0 the new CP15 C1 control register setting.
  *
  *  We automatically detect if we have a Harvard cache, and use the
  *  Harvard cache control instructions insead of the unified cache
  *  control instructions.
  *
  *  This should be able to cover all ARMv6 cores.
  *
  *  It is assumed that:       
  *  - cache type register is implemented
  */    
  完成这部分关于CPU的操作以后,下面就是打开MMU了,这部分内容也没什么好说的,也是对arm控制寄存器的操作,打开MMU以后我们就可以使用虚拟地址了,而不需要我们自己来进行地址的重定位,ARM硬件会完成这部分的工作。打开MMU以后,会将SP的值赋给PC,这样代码就会跳到__switch_data来运行,这个__switch_data是一个定义在head-common.S里面的结构体,我们实际上是跳到它地一个函数指针__mmap_switched处执行的。
  这个switch的执行过程我们只是简单看一下,前面的copy data_loc段以及清空.bss段就不用说了,它后面会将proc的信息和machine的信息保存在__switch_data这个结构体里面,而这个结构体将来会在start_kernel的setup_arch里面被使用到。这个在后面的对start_kernel的详细分析中会讲到。另外这个switch还涉及到控制寄存器的一些操作,这里我不没仔细研究spec,不懂也就不说了~
  好啦,switch操作完成以后就会b start_kernel了~ 这样就进入了c代码的运行了,下一篇文章仔细研究这个start_kernel的函数~~
  Ref:
  http://linux.chinaunix.net/bbs/thread-1021226-1-1. html
  http://blog.csdn.net/yhmhappy2006/archive/2008/08/ 06/2775239.aspx
  http://blog.csdn.net/sustzombie/archive/2010/06/12 /5667607.aspx
分享到:
评论

相关推荐

    64位Linux的ARM交叉编译器

    以前将android的kernel拿出来单独编译,交叉编译器也是从android代码中拿出来的。 但自从装了Ubuntu 12.04 64位系统之后,原来在32位系统上可以使用的arm-linux交叉编译器不能用了。 按照网上的方法安装 sudo apt-...

    模拟器专用android kernel2.6 (支持netfilter,ko文件加载与卸载)

    $ANDROID_SDK/tools/emulator -avd avd2.1 -kernel ~/android-kernel/goldfish/arch/arm/boot/zImage -show-kernel 2. 如何使用config文件,作为配置,重新编译内核 下载android kernel 2.6 (goldfish) 然后将...

    linux内核编译最完整过程成功案例

    这是本人编译内核的整个过程,本过程经多次在不同机器上验证都可以通过,但每台机器的安装配置、环境等都不一样,故出现错误是很正常的事。。去百度一下就OK了。。。现在本人将它共享出来。。希望对大家有所帮助!~

    基于ARM的11种嵌入式操作系统大盘点

    其整个系统由应用程序,应用程序框架,应用程序库,Android运行库,Linux内核(Linux Kernel)五个部分组成。Android操作系统内置了一部分应用程序, 包括电子邮件客户端、SMS程序、日历、地图、浏览器、通讯录以及...

    嵌入式系统/ARM技术中的Android移植到FS2410开发板分析

    一、目前进展  1.Android已经可以... 1、下载linux-2.6.25-android-1.0_r1.tar.gz  2、将内核解压到用户目录,如/home/wangan/kernel.git  3、进入kernel.git文件夹,如cd ~/kernel.git/  4、修改arch/arm/plat

    aquaris-M5:设备bq Aquaris M5Linux内核源代码-linux kernel source code

    这是什么? 设备Linux内核源代码: ... $ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8 创建KERNEL_OUT目录: $ mkdir KERNEL_OUT 您的目录树应如下所示:

    Android内核源码 在Ubuntu上下载,编译,安装

    从源代码树下载下来的最新Android源代码,是不包括内核代码的,也就是Android源代码工程默认不包含Linux Kernel代码,而是使用预先编译好的内核,也就是prebuilt/android-arm/kernel/kernel-qemu文件。那么,如何...

    aquaris-E6:设备bq Aquaris E6Linux内核源代码-linux kernel source code

    这是什么? 设备Linux内核源代码:... $ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8 创建KERNEL_OUT目录: $ mkdir KERNEL_OUT 您的目录树应如下所示: 核心 a

    aquaris-X5:设备bq Aquaris X5Linux内核源代码-linux kernel source code

    这是什么? 设备Linux内核源代码: ... $ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8 创建KERNEL_OUT目录: $ mkdir KERNEL_OUT 您的目录树应如下所示:

    aquaris-X5-plus:设备bq Aquaris X5 PlusLinux内核源代码-linux kernel source code

    这是什么? 设备Linux内核源代码: bq水瓶座X5 Plus ... $ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8 创建KERNEL_OUT目录: $ mkdir KERNEL

    android_kernel_xiaomi_sm8350

    补丁不得破坏用于arm,arm64,x86,x86_64体系结构的gki_defconfig或allmodconfig构建(请参阅 ) 如果未从上游分支合并修补程序,则必须使用修补程序的类型对主题进行标记: UPSTREAM: , BACKPORT: ,

    aquaris-E5FHD:设备bq Aquaris E5 FHDLinux内核源代码-linux kernel source code

    这是什么? 设备Linux内核源代码: bq水瓶座E5 FHD 内置说明?... $ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8 创建KERNEL_OUT目录: $ mkdir KER

    新版Android开发教程.rar

    � Android 更像一款桌面环境为 Java 的 Linux 操作系统。有助于 Google 实现其 " 随时随地为每个人提供信 息 " 的企业战略。 HTC HTC HTC HTC Dream/G1 Dream/G1 Dream/G1 Dream/G1 具体配置 硬件 3.17 英寸 HVGA ...

    Android4.4.4_r1:Android原始注解-android

    Android原始学习这是一个个人学习Android 4.4.4 r1源码的记录仓库文件按编译出来的模块存放: bootable编译成aboot.img文件(实际是一个bootloader ELF文件) kernel编程成boot.img的前半部,也就是Linux kernel...

    bpi_a64_android初步编译为LCD显示20170309_1442.7z

    /home/wwt/a64/bpi_a64_android/lichee/brandy/u-boot-2014.07/../gcc-linaro/bin/arm-linux-gnueabi-ld /home/wwt/a64/bpi_a64_android/lichee/brandy/u-boot-2014.07/arch/arm/cpu/armv7/sun50iw1p1/dram/...

    fota.tar.gz_Fota Wildl_android fota_apk_fota.tar_linux 分区

    基于android2.2的fota功能。 升 级 整 个 流 程 概 述 : 从 用 户 点 击 APK , 然 后 上 层 将 命 令 写 入 recovery/command,然后 kernel 检测到信号重启,将开机原因写入共享内存, 开机进入 appboot 检测到 arm9 ...

    android_kernel_brcm_rpi

    常见内核补丁要求所有补丁必须符合Linux内核编码标准,并通过script/checkpatch.pl 补丁不得破坏用于arm,arm64,x86,x86_64体系结构的gki_defconfig或allmodconfig构建(请参阅 ) 如果未从上游分支合并修补程序,...

    linux全志R16的linux系统编译的资料_20170502_1655.7z

    全志R16平台编译linux系统V1.0.txt 2017/4/11 13:36 (编译请使用编译android的lichee的选项编译生成的.config文件,不然直接编译会报错!!!!) rootroot@cm-System-Product-Name:/home/wwt/linux_r16$ tar...

    RTL8723BU_WiFi_linux_v4.4.5_20171101.7z

    RTL8723BU_WiFi_linux_v4.4.5_20171101 驱动程序,测试可以使用, Hardware Supported: ... Linux (kernel 2.6.24 ~ 4.4.3)  Android 1.6 ~ 2.3, 4.0 ~ 5.1, 6.0 CPU supported:  x86  ARM  MIPS

    android_kernel_lenovo_p70

    Lenovo P70-A牛轧糖内核。 基于Zormax来源。...make ARCH=arm64 CROSS_COMPILE=/home/YourSelf/p70/aarch64-linux-android-4.9/bin/aarch64-linux-android- -j8 干净的 make clean && make mrproper

Global site tag (gtag.js) - Google Analytics