zoukankan      html  css  js  c++  java
  • vmlinux虚拟地址和物理地址的确定

    (1) 下面是确定内核的虚拟地址、物理地址的关键信息, 感兴趣的同学可以自己看:
    vmlinux虚拟地址的确定:
    内核源码:

    .config :
         CONFIG_PAGE_OFFSET=0xC0000000
         
    arch/arm/include/asm/memory.h
        #define PAGE_OFFSET     UL(CONFIG_PAGE_OFFSET)
    
    arch/arm/Makefile
        textofs-y       := 0x00008000
        TEXT_OFFSET := $(textofs-y)
    
    arch/arm/kernel/vmlinux.lds.S:
        . = PAGE_OFFSET + TEXT_OFFSET;   // // 即0xC0000000+0x00008000 = 0xC0008000, vmlinux的虚拟地址为0xC0008000
    
    arch/arm/kernel/head.S
        #define KERNEL_RAM_VADDR       (PAGE_OFFSET + TEXT_OFFSET)  // 即0xC0000000+0x00008000 = 0xC0008000

    vmlinux物理地址的确定:
    内核源码:

    arch/arm/mach-s3c24xx/Makefile.boot :
        zreladdr-y      += 0x30008000   // zImage自解压后得到vmlinux, vmlinux的存放位置
        params_phys-y   := 0x30000100   // tag参数的存放位置, 使用dtb时不再需要tag
    
    arch/arm/boot/Makefile:
        ZRELADDR    := $(zreladdr-y)
    
    arch/arm/boot/Makefile:
        UIMAGE_LOADADDR=$(ZRELADDR)
    
    scripts/Makefile.lib:
        UIMAGE_ENTRYADDR ?= $(UIMAGE_LOADADDR)  
    
        // 制作uImage的命令, uImage = 64字节的头部 + zImage,  头部信息中含有内核的入口地址(就是vmlinux的物理地址)
        cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A $(UIMAGE_ARCH) -O linux \
                             -C $(UIMAGE_COMPRESSION) $(UIMAGE_OPTS-y) \
                             -T $(UIMAGE_TYPE) \
                             -a $(UIMAGE_LOADADDR) -e $(UIMAGE_ENTRYADDR) \
                             -n $(UIMAGE_NAME) -d $(UIMAGE_IN) $(UIMAGE_OUT)

    KERNEL_RAM_PADDR 0x30008000

    在arm平台下,zImage.bin压缩镜像是由bootloader加载到物理内存,然后跳到zImage.bin里一段程序,它专门于将被压缩的kernel解压缩到KERNEL_RAM_PADDR开始的一段内存中,接着跳进真正的kernel去执行。该kernel的执行起点是stext函数, stext函数定义在Arch/arm/kernel/head.S,它的功能是获取处理器类型和机器类型信息,并创建临时的页表,然后开启MMU功能,并跳进第一个C语言函数start_kernel。

    在分析stext函数前,先介绍此时内存的布局如下图所示

     

    在开发板tqs3c2440中,SDRAM连接到内存控制器的Bank6中,它的开始内存地址是0x30000000,大小为64M,即0x20000000。 ARM Linux kernel将SDRAM的开始地址定义为PHYS_OFFSET。经bootloader加载kernel并由自解压部分代码运行后,最终kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET + TEXT_OFFSET,即0x30008000)地址上的一段内存,经此放置后,kernel代码以后均不会被移动。

    在进入kernel代码前,即bootloader和自解压缩阶段,ARM未开启MMU功能。因此kernel启动代码一个重要功能是设置好相应的页表,并开启MMU功能。为了支持MMU功能,kernel镜像中的所有符号,包括代码段和数据段的符号,在链接时都生成了它在开启MMU时,所在物理内存地址映射到的虚拟内存地址

    以arm kernel第一个符号(函数)stext为例,在编译链接,它生成的虚拟地址是0xc0008000,而放置它的物理地址为0x30008000(还记得这是PHYS_OFFSET+TEXT_OFFSET吗?)。实际上这个变换可以利用简单的公式进行表示:va = pa – PHYS_OFFSET + PAGE_OFFSET。Arm linux最终的kernel空间的页表,就是按照这个关系来建立。

    之所以较早提及arm linux 的内存映射,原因是在进入kernel代码,里面所有符号地址值为清一色的0xCXXXXXXX地址,而此时ARM未开启MMU功能,故在执行stext函数第一条执行时,它的PC值就是stext所在的内存地址(即物理地址,0x30008000)。因此,下面有些代码,需要使用地址无关技术(相对寻址)

     kernel建立临时页表

    前面提及到,kernel里面的所有符号在链接时,都使用了虚拟地址值。在完成基本的初始化后,kernel代码将跳到第一个C语言函数start_kernl来执行,在哪个时候,这些虚拟地址必须能够对它所存放在真正内存位置,否则运行将为出错。为此,CPU必须开启MMU,但在开启MMU前,必须为虚拟地址到物理地址的映射建立相应的面表。在开启MMU后,kernel指并不马上将PC值指向start_kernl,而是要做一些C语言运行期的设置,如堆栈,重定义等工作后才跳到start_kernel去执行。在此过程中,PC值还是物理地址,因此还需要为这段内存空间建立va = pa的内存映射关系。当然,本函数建立的所有页表都会在将来paging_init销毁再重建,这是临时过度性的映射关系和页表。
    在介绍__create_table_pages前,先认识一个macro pgtbl,它将KERNL_RAM_PADDR – 0x4000的值赋给rd寄存器,从下面的使用中可以看它,该值是页表在物理内存的基础,也即页表放在kernel开始地址下的16K的地方。

     .macro pgtbl, rd
     ldr \rd, =(KERNEL_RAM_PADDR - 0x4000)
     .endm

     地址无关

    比如_lookup_processor_type

    1. #       /* adr 是相对寻址,它的寻计算结果是将当前PC值加上3f符号与PC的偏移量,   
    2. #        * 而PC是物理地址,因此r3的结果也是3f符号的物理地址 */    
    3. #     
    4. #        adr  r3, 3f   

     ENTRY(stext)

    kernel的链接脚本并不是直接提供的,而是提供了一个汇编文件vmlinux.lds.S,然后在编译的时候再去编译这个汇编文件得到真正的链接脚本vmlinux.lds。

    为什么linux kernel不直接提供vmlinux.lds而要提供一个vmlinux.lds.S然后在编译时才去动态生成vmlinux.lds呢?
    .lds文件中只能写死,不能用条件编译。但是我们在kernel中链接脚本确实有条件编译的需求(但是lds格式又不支持),于是乎kernel工作者找了个投机取巧的方法,就是把vmlinux.lds写成一个汇编格式,然后汇编器处理的时候顺便条件编译给处理了,得到一个不需要条件编译的vmlinux.lds。

     

    从vmlinux.lds.S中 ENTRY(stext) 可以知道入口符号是stext,在SI中搜索这个符号,发现arch/arm/kernel/目录下的head.S和head-nommu.S中都有。
    head.S是启用了MMU情况下的kernel启动文件,相当于uboot中的start.S。head-nommu.S是未使用mmu情况下的kernel启动文件。

    内核启动文件head.S(汇编阶段)

    内核运行的物理地址与虚拟地址(29-30)

    在这里插入图片描述
    KERNEL_RAM_VADDR(VADDR就是virtual address),这个宏定义了内核运行时的虚拟地址。值为0xC0008000
    KERNEL_RAM_PADDR(PADDR就是physical address),这个宏定义内核运行时的物理地址。值为0x30008000
    总结:内核运行的物理地址是0x30008000,对应的虚拟地址是0xC0008000。

    内核运行硬件条件备注(59-76)

    内核启动不是无条件的,而是有一定的先决条件,这个条件由启动内核的bootloader(我们这里就是uboot)来构建保证。
    在这里插入图片描述
    (1)内核的起始部分代码是被解压代码调用的。回忆之前讲zImage的时候,uboot启动内核后实际调用运行的是zImage前面的那段未经压缩的解压代码,解压代码运行时先将zImage后段的内核解压开,然后再去调用运行真正的内核入口。并在开始时MMU和D-cache是关闭的,I-cache任意,并且寄存器r0,r1,r2传的参数与uboot阶段时最后的theKernel函数传参对应。所以uboot中最后theKernel (0, machid, bd->bi_boot_params);执行内核时,运行时实际把0放入r0中,machid放入到了r1中,bd->bi_boot_params放入到了r2中。ARM的这种处理技巧刚好满足了kernel启动的条件和要求。
    (2)kernel启动时MMU是关闭的,因此硬件上需要的是物理地址。但是内核是一个整体(zImage)只能被连接到一个地址(不能分散加载),这个连接地址肯定是虚拟地址。因此内核运行时前段head.S中尚未开启MMU之前的这段代码必须是位置无关码(pc使用的是物理地址),而且其中涉及到操作硬件寄存器等时必须使用物理地址。
    (3)通过linux/arch/arm/tools/mach-types目录中查找对应的机器码。
    (4)不要添加没有用的代码在这里,这里的代码只是用来boot loader的。

  • 相关阅读:
    教你如何让数据库支持emoji表情符存储
    PHP下载远程图片的3个方法
    CentOS7安装使用MySQL
    记录几个项目管理软件
    C++和C在linux下 和在windows下有什么区别?
    nginx配置location总结及rewrite规则写法
    A记录和CNAME记录的区别
    Nginx配置https的wordpress站点,wp-content目录下资源404解决方案
    清除chrome浏览器HSTS缓存
    面向对象基础----封装数据库操作类
  • 原文地址:https://www.cnblogs.com/dream397/p/15629694.html
Copyright © 2011-2022 走看看