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的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM