主頁 > 後端開發 > linux arm32 mmu 啟動代碼分析(匯編部分)

linux arm32 mmu 啟動代碼分析(匯編部分)

2020-10-13 15:21:49 後端開發

linux arm32啟動代碼分析

首先將 linux kernel 代碼編譯好以后,在目錄 arch/arm/kernel 下生成鏈接腳本檔案 vmlinux.lds (vmlinux.lds由vmlinux.lds.S編譯而來),首先分析此腳本來熟悉 linux kernel 二進制代碼分布結構,

vmlinux.lds.S

ENTRY(stext)

指明了linux內核入口,入口為stext,符號stext定義在 arch/arm/kernel/head.S 檔案中:

	.arm

	__HEAD
ENTRY(stext)
 ARM_BE8(setend	be )			@ ensure we are in BE8 mode

 THUMB(	adr	r9, BSYM(1f)	)	@ Kernel is always entered in ARM.
 THUMB(	bx	r9		)	@ If this is a Thumb-2 kernel,
 THUMB(	.thumb			)	@ switch to Thumb now.
 THUMB(1:			)

ENTRY在 include/linux/linkage.h 中定義

#ifndef ENTRY
#define ENTRY(name) \
	.globl name ASM_NL \
	ALIGN ASM_NL \
	name:
#endif

通過代碼可以看到 ENTRY 宏只是對全域符號的匯出起到包裝作用,
下面分析ENTRY陳述句下的五條指令

1、ARM_BE8(setend be )
ARM_BE8在 arch/arm/include/asm/assembler.h 中定義

/* Select code for any configuration running in BE8 mode */
#ifdef CONFIG_CPU_ENDIAN_BE8
#define ARM_BE8(code...) code
#else
#define ARM_BE8(code...)
#endif

如果開啟 CONFIG_CPU_ENDIAN_BE8 選項,code(setend be)則會被原封不動編譯到內核中,如果沒有開啟此選項,code(setend be)就不會被編譯到內核中,

(extension) #define后的省略號的意義如下(以PDEBUG為例):

#define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: " fmt, ## args)
// example
PDEBUG("a=%d, b=%d", a, b);
// 展開后
printk( KERN_DEBUG "scull: " "a=%d, b=%d", a, b);

(1)arm BE8 mode 是什么
根據博客 https://blog.richliu.com/2010/04/08/907/arm11-be8-and-be32 所述:
BE8 和 BE32 是位元組序相關的概念,舉例:
假設需要將 0x11223344 存入記憶體中:
little endian:

memory address0123
data0x440x330x220x11

big endian:

memory address0123
data0x110x220x330x44

現假設對0x11223344記憶體存盤分布如下:

memory address0123
data0x110x220x330x44

在BE32 mode下:

LDR r0, [0]
# r0 = 0x44332211
LDRB r0, [0]
# r0 = 0x00000044
LDRB r0, [3]
# r0 = 0x00000011

在BE8 mode下:

LDR r0, [0]
# r0 = 0x11223344
LDRB r0, [0]
# r0 = 0x00000011
LDRB r0, [3]
# r0 = 0x00000044

關于 byte invariant endianness 的概念可以參照博客 https://blog.csdn.net/moreaction/article/details/5280067/
(2)setend be 指令的含義
setend 指令選擇資料訪問的位元組序,在百度文庫 https://wenku.baidu.com/view/6f83c4c2951ea76e58fafab069dc5022aaea46e5.html 解釋了這個指令,在linux內核代碼中,如果開啟了 CONFIG_CPU_ENDIAN_BE8 選項,則 setend be 指令會被編譯到內核中并執行,此指令的執行代表選擇資料訪問的位元組序為 BE8 模式,BE8 模式的資料訪問效果如上文所述,

2、THUMB( adr r9, BSYM(1f) )
3、THUMB( bx r9 )
4、THUMB( .thumb )
5、THUMB(1: )
2\3\4\5 中,THUMB、BSYM都定義在 arch/arm/include/asm/unified.h 中:

#ifdef CONFIG_THUMB2_KERNEL

#if __GNUC__ < 4
#error Thumb-2 kernel requires gcc >= 4
#endif

/* The CPSR bit describing the instruction set (Thumb) */
#define PSR_ISETSTATE	PSR_T_BIT

#define ARM(x...)
#define THUMB(x...)	x
#ifdef __ASSEMBLY__
#define W(instr)	instr.w
#define BSYM(sym)	sym + 1
#else
#define WASM(instr)	#instr ".w"
#endif

#else	/* !CONFIG_THUMB2_KERNEL */

/* The CPSR bit describing the instruction set (ARM) */
#define PSR_ISETSTATE	0

#define ARM(x...)	x
#define THUMB(x...)
#ifdef __ASSEMBLY__
#define W(instr)	instr
#define BSYM(sym)	sym
#else
#define WASM(instr)	#instr
#endif

#endif	/* CONFIG_THUMB2_KERNEL */

若沒有開啟 CONFIG_THUMB2_KERNEL 選項,則這四條陳述句都不會被編譯進內核中,且開啟此選項需要 gcc 版本高于 4

#if __GNUC__ < 4
#error Thumb-2 kernel requires gcc >= 4
#endif

如果 CONFIG_THUMB2_KERNEL 選項開啟且定義了 __ASSEMBLY__,則這四條陳述句會被預編譯為:

adr	r9, 1f + 1
bx	r9
	.thumb
1:	

至于為什么需要 1f+1 原因在于分支指令 bx:
bx 為帶狀態切換的跳轉指令,其語法格式為 bx 其中 RM 只能是暫存器,并且當 RM 的bit[0]為1時,切換到 Thumb 指令,為0時切換到 ARM 指令,
以下是猜測:由于指令對齊,所以帶狀態分支跳轉地址一定為偶數,所以CPU在收到條件分支指令時不會將bit[0]當作地址的一部分,而是用來做bx的狀態選擇,并且由于地址為偶數的原因, adr r9, 1f 指令執行后,r9的bit[0]一定為0,所以需要加上1來進行狀態選擇,以達到跳轉后開始執行Thumb指令集,并且,.thumb 也告知編譯器,其下的匯編代碼需要編譯為Thumb指令

至此分析完這五條指令,


next,如果開啟了 CONFIG_ARM_VIRT_EXT 開關,則會跳轉到 __hyp_stub_install 繼續執行,具體代碼如下:

#ifdef CONFIG_ARM_VIRT_EXT
	bl	__hyp_stub_install
#endif

__hyp_stub_install 定義在 arch/arm/kernel/hyp-stub.S 中,根據源代碼中的注釋解釋:

/*
 * Hypervisor stub installation functions.
 *
 * These must be called with the MMU and D-cache off.
 * They are not ABI compliant and are only intended to be called from the kernel
 * entry points in head.S.
 */

目前只知道此代碼與arm虛擬化功能有關


next, linux 內核將會開始檢測所運行的硬體是否支持此內核映像,由于 arm 內核種類多,并且互相之間有無法兼容的區別,所以當開發者對內核進行開發或者使用者編譯內核時,都會在原始碼級別上給出對硬體的某些定義,就如下文所提到的 __proc_info 結構,內核在編譯期即確定了自己所支持的硬體種類,例如在 s3c2440 平臺下即會去指定支持 arm920t 架構,由于會有一些與硬體互動的操作無法互相兼容,所以內核會通過下文的一些操作來檢查硬體的屬性是否與編譯到內核里對硬體的定義資訊相互匹配,如果發現不匹配則會立刻讓內核陷入死回圈并告知啟動失敗,
檢查 processor id

safe_svcmode_maskall r9

mrc	p15, 0, r9, c0, c0		@ get processor id
bl	__lookup_processor_type		@ r5=procinfo r9=cpuid
movs	r10, r5				@ invalid processor (r5=0)?
 THUMB( it	eq )		@ force fixup-able long branch encoding
beq	__error_p			@ yes, error 'p'

safe_svcmode_maskall r9 用來確保 CPU 在SVC模式下,并且所有中斷都被屏蔽,其中 safe_svcmode_maskall 是一個宏,他定義在 arch/arm/include/asm/assembler.h 中,原始碼中的注釋對其解釋為:

/*
 * Helper macro to enter SVC mode cleanly and mask interrupts. reg is
 * a scratch register for the macro to overwrite.
 *
 * This macro is intended for forcing the CPU into SVC mode at boot time.
 * you cannot return to the original mode.
 */

mrc 指令將協處理器的暫存器中數值傳送到ARM處理器的暫存器中,其中 mrc p15, 0, r9, c0, c0 用來獲取處理器id(processor id)并將此值傳遞給r9
在指令 bl __lookup_processor_type 中,__lookup_processor_type 符號定義在 arch/arm/kernel/head-common.S 中:

__lookup_processor_type:
	adr	r3, __lookup_processor_type_data
	ldmia	r3, {r4 - 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:	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:	ret	lr
ENDPROC(__lookup_processor_type)

在這段代碼中涉及到 __lookup_processor_type_data 定義為:

/*
 * Look in <asm/procinfo.h> for information about the __proc_info structure.
 */
	.align	2
	.type	__lookup_processor_type_data, %object
__lookup_processor_type_data:
	.long	.
	.long	__proc_info_begin
	.long	__proc_info_end
	.size	__lookup_processor_type_data, . - __lookup_processor_type_data

可以理解為在 __proc_info_begin 表示的記憶體地址與 __proc_info_end 表示的記憶體地址之間包含了一個或多個 __proc_info 結構體,這個結構體用C語言的定義如下:

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 資訊與通過 mrc 指令獲取的處理器id進行對比,并通過 r5 暫存器回傳結構的地址,或者回傳 0 代表匹配失敗,add r5, r5, #PROC_INFO_SZ 指令就是將指標指向下一個結構體,ldmia r5, {r3, r4} 指令就是將 cpu_valcpu_mask 的值分別裝載到 r3 r4 中,cmp r5, r6 指令來判斷是否已經遍歷到了陣列的末尾,即已經沒有未檢查過的 __proc_info 結構體了,如果r5 != r6 則 blo 1b,若相等則給 r5 賦值為0并回傳,指令 and r4, r4, r9 與 teq r3, r4 用于檢查processor_id,如果通過檢查則 beq 2f 并 通過指令 ret lr 回傳,回傳時暫存器 r5 存盤的值為此相匹配的 __proc_info 結構的地址,

movs r10, r5r5 的值轉存到 r10 中,并且由于使用 movs 指令,會影響 CPSR 暫存器的值,若 r5 為0則會使得 Z 位置1,而 beq 指令通過判斷 Z 位是否為1來決定是否跳轉,最后,如果 r5 為0,即沒有匹配的 __proc_info 則會導致 beq __error_p 執行,若執行此陳述句,最終便會跳轉到 __error 代碼段 最后系統會 halt up,其中 __error_p__error 皆定義在 arch/arm/kernel/head-common.S 中,

通過查看鏈接腳本,我們可以清晰的看到 __proc_info 結構的存盤位置:

#define PROC_INFO							\
	. = ALIGN(4);							\
	VMLINUX_SYMBOL(__proc_info_begin) = .;				\
	*(.proc.info.init)						\
	VMLINUX_SYMBOL(__proc_info_end) = .;

.text : {			/* Real text segment		*/
		_stext = .;		/* Text and read-only data	*/
			IDMAP_TEXT
			__exception_text_start = .;
			*(.exception.text)
			__exception_text_end = .;
			IRQENTRY_TEXT
			TEXT_TEXT
			SCHED_TEXT
			LOCK_TEXT
			KPROBES_TEXT
			*(.gnu.warning)
			*(.glue_7)
			*(.glue_7t)
		. = ALIGN(4);
		*(.got)			/* Global offset table		*/
			ARM_CPU_KEEP(PROC_INFO)
	}

可以看到在 __proc_info_begin__proc_info_end 之間的是 .proc.info.init 段,這樣就可以通過檢索 .proc.info.init 段的定義位置來找到這些 __proc_info 結構的定義位置了,通過檢索發現 .proc.info.init 段的定義位置在 arch/arm/mm/ 目錄下的多個 proc-x.S 中,下邊舉例:
在 arch/arm/mm/proc-arm9tdmi.S 中

		.section ".proc.info.init", #alloc

.macro arm9tdmi_proc_info name:req, cpu_val:req, cpu_mask:req, cpu_name:req
		.type	__\name\()_proc_info, #object
__\name\()_proc_info:
		.long	\cpu_val
		.long	\cpu_mask
		.long	0
		.long	0
		initfn	__arm9tdmi_setup, __\name\()_proc_info
		.long	cpu_arch_name
		.long	cpu_elf_name
		.long	HWCAP_SWP | HWCAP_THUMB | HWCAP_26BIT
		.long	\cpu_name
		.long	arm9tdmi_processor_functions
		.long	0
		.long	0
		.long	v4_cache_fns
		.size	__\name\()_proc_info, . - __\name\()_proc_info
.endm

	arm9tdmi_proc_info arm9tdmi, 0x41009900, 0xfff8ff00, cpu_arm9tdmi_name
	arm9tdmi_proc_info p2001, 0x41029000, 0xffffffff, cpu_p2001_name

在 arch/arm/mm/proc-arm920.S 中

	.section ".proc.info.init", #alloc

	.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
	initfn	__arm920_setup, __arm920_proc_info
	.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

可以看到這些結構都是通過匯編語言進行定義的,雖然在 linux 源代碼中有對 __proc_info 結構的C語言定義(如上文),但是 linux 內核代碼并沒有使用這個C語言定義的結構,由于此時系統仍然處于底層初始化階段,并沒有進行C語言執行環境初始化作業(例如初始堆疊空間等),所以無法執行C代碼,也就無法參考C語言定義的結構體,只能通過匯編語言直接定義特定記憶體位置的值來還原結構體的資料分布,

至此,linux 內核對 processor id 的檢查流程分析完畢


下邊將分析 linux 內核在內核管理方面的初始化作業,首先解釋 linux 內核對記憶體地址相關定義的幾個值:
1、PAGE_OFFSET
2、TEXT_OFFSET
3、KERNAL_RAM_VADDR
: 在分析頁表建立之前,必須先搞清楚 linux 內核對地址的這幾個特殊定義才行
這些值出現在 arch/arm/kernel/head.S 中:

#define KERNEL_RAM_VADDR	(PAGE_OFFSET + TEXT_OFFSET)
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif

其中 TEXT_OFFSETPAGE_OFFSET 在鏈接腳本中已經被用來定義當前虛擬地址

#ifdef CONFIG_XIP_KERNEL
	. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
	. = PAGE_OFFSET + TEXT_OFFSET;
#endif
	.head.text : {
		_text = .;
		HEAD_TEXT
	}

查看 arch/arm/kernel/head.S 所包含的頭檔案,發現了與記憶體管理相關的頭檔案 asm/memory.h ,進而在此頭檔案中找到了 PAGE_OFFSET 的定義:

/* PAGE_OFFSET - the virtual address of the start of the kernel image */
#define PAGE_OFFSET		UL(CONFIG_PAGE_OFFSET)

并在 arm 架構的 defconfig 檔案中找到了 CONFIG_PAGE_OFFSET 選項(以 arch\arm\configs\imx_alientek_emmc_defconfig 為例):

CONFIG_PAGE_OFFSET=0x80000000

而展開 UL 宏:

/*
 * Allow for constants defined here to be used from assembly code
 * by prepending the UL suffix only with actual C code compilation.
 */
#define UL(x) _AC(x, UL) //arch/arm/include/asm/memory.h

TEXT_OFFSET 的定義位置比較隱蔽,通過查看 MAKEFILE 輸出,我們可以看到(或者也可以查看 cmd 檔案 arch/arm/kernel/.head.o.cmd,其中的變數 cmd_arch/arm/kernel/head.o := arm-linux-gnueabihf-gcc …):

arm-linux-gnueabihf-gcc -Wp -WD,arch/arm/kernel/.head.o.d -nostdinc -isystem /home/ace/kernels/alpha/toolchain/gcc-linaro-4.9.4 ... (省略無關緊要的部分) -D__ASSEMBLY__ -D__KERNEL__ -D__LINUX_ARM_ARCH__=6 -DTEXT_OFFSET=0x00008000 ... (省略無關緊要的部分)

通過上述命令看到通過 gcc-D 選項將 TEXT_OFFSET 宏注入到代碼中,進而可以查到這個宏的值的定義位置在 arch/arm/Makefile 中:

TEXT_OFFSET := $(textofs-y)

同在一個檔案中,使用這可以通過 config 選擇 textofs-y 的值:

# Text offset. This list is sorted numerically by address in order to
# provide a means to avoid/resolve conflicts in multi-arch kernels.
textofs-y	:= 0x00008000
textofs-$(CONFIG_ARCH_CLPS711X) := 0x00028000
# We don't want the htc bootloader to corrupt kernel during resume
textofs-$(CONFIG_PM_H1940)      := 0x00108000
# SA1111 DMA bug: we don't want the kernel to live in precious DMA-able memory
ifeq ($(CONFIG_ARCH_SA1100),y)
textofs-$(CONFIG_SA1111) := 0x00208000
endif
textofs-$(CONFIG_ARCH_MSM8X60) := 0x00208000
textofs-$(CONFIG_ARCH_MSM8960) := 0x00208000
textofs-$(CONFIG_ARCH_AXXIA) := 0x00308000

若不在 config 中開啟這些奇怪的選項,則 textofs-y 默認的值即為 0x00008000,并且通過上文查看到的 arm-linux-gcc 命令可以看到,我在此次編譯時并沒有開啟這些選項,使用了 textofs-y 的默認值 0x00008000,這樣上文提到的宏 KERNAL_RAM_VADDR 便可以計算得:PAGE_OFFSET + TEXT_OFFSET = 0x80008000
現在已經找到了他們的定義位置及其值,下邊將分析這些值的含義
linux 內核原始碼中的注釋如下:

/*
 * swapper_pg_dir is the virtual address of the initial page table.
 * We place the page tables 16K below KERNEL_RAM_VADDR.  Therefore, we must
 * make sure that KERNEL_RAM_VADDR is correctly set.  Currently, we expect
 * the least significant 16 bits to be 0x8000, but we could probably
 * relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
 */
 
 /* PAGE_OFFSET - the virtual address of the start of the kernel image */

意思是說,PAGE_OFFSET 是內核映像的起始 虛擬地址,而 swapper_pg_dir 是初始頁表的 虛擬地址,由于我在編譯程序中并沒有打開 LPAE 開關,所以得到:

#define PG_DIR_SIZE	0x4000

進而通過:

.globl	swapper_pg_dir
.equ	swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE

得到 swapper_pg_dir = 0x80008000 - 0x00004000 = 0x80004000
查看一下鏈接腳本,可以看到這么一個定義:

. = PAGE_OFFSET + TEXT_OFFSET;
.head.text : {
	_text = .;
	HEAD_TEXT
}

也就是說,KERNAL_RAM_VADDR 表示的地址正好是 .head.text 段的起始地址,這就需要搞清楚 .head.text 段中包含的內容是什么,鏈接腳本說,.head.text 段中包含了一個符號 _text,這個符號的值為當前地址(PAGE_OFFSET + TEXT_OFFSET = KERNAL_RAM_VADDR),我們需要記下來這個符號,可能之后會用到;然后就是 HEAD_TEXT 宏定義,必須找到 HEAD_TEXT 的定義才能知道聯結器究竟把目標檔案中的哪些段塞進了這個 .head.text 段中,在 include/asm-generic/vmlinux.lds.h 頭檔案中找到了對 HEAD_TEXT 的定義:

/* Section used for early init (in .S files) */
#define HEAD_TEXT  *(.head.text)

意思就是將所有目標檔案中的 .head.text 段聚集在了一起,別忘了我們分析的前幾行指令:

.arm
__HEAD
ENTRY(stext)
ARM_BE8(setend	be )			
THUMB(	adr	r9, BSYM(1f)	)	
THUMB(	bx	r9		)	
THUMB(	.thumb			)	
THUMB(1:			)

這里有一個 __HEAD 宏定義,在 include/linux/init.h 中可以找到其定義:

#define __HEAD		.section	".head.text","ax"

原來目前分析的代碼都在 .head.text 段下,這樣就完全清楚了:PAGE_OFFSET(0x80000000) 是整個內核映像的起始 虛擬地址,而 KERNAL_RAM_VADDR(0x80008000) 為內核映像中早期初始化可執行代碼段的起始 虛擬地址,在這兩個地址之間有一個初始頁表的起始虛擬地址,swapper_pg_dir(0x80004000) 這樣 TEXT_OFFSET 名字的由來也清楚了: TEXT 的偏移,即對 PAGE_OFFSET 的偏移,偏移了 TEXT_OFFSET 大小后即到了 代碼段(text) ,其由 KERNAL_RAM_VADDR 進行標識,最后確認一下我們分析的記憶體分布是否正確,打開 System.map 檔案,查看一下 0x80008000 虛擬地址處究竟是不是 stext 符號:

80008000 T _text
80008000 T stext
8000808c t __create_page_tables

看來是了,目前的分析是正確的,并且 _text 符號確實被編譯到可執行檔案中,位置在 0x80008000,與 stext 符號享有同樣的地址,
但是目標檔案中不僅僅在 head.S 中定義了 .head.text ,其他目標檔案中也會包含此段啊,為什么 head.S 中的 .head.text 段在所有 .head.text 段之上呢?這是因為在鏈接腳本中,定義了:

ENTRY(stext)

這就意味著 stext 符號需要當作入口在最前面,所以 head.S 中的包含 stext 符號的 .head.text 段理所當然地充當了內核映像中的 .head.text 段之首,

當清楚了這些值的含義后,可以繼續分析 linux 內核的啟動代碼,下一步就是 建立頁表
分析到這里,需要回過頭看一下 linux 內核原始碼注釋對此時硬體狀態的定義:

/*
* This is normally called from the decompressor code.  The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags or dtb pointer.*/

/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/

這里 ldr r8, =PLAT_PHYS_OFFSET 指令無需多看,因為 PLAT_PHYS_OFFSET 只用在沒有 MMU 或者使用 XIP 技術的情況下,在一般情況下 CONFIG_PHYS_OFFSET 都會是未定義的宏,而編譯時通過一些 gcc 選項可以屏蔽未定義宏的報錯,使其通過編譯, 即在一般情況下 r8 的值是無意義的, PLAT_PHYS_OFFSET 的定義如下:

 /*
 * PLAT_PHYS_OFFSET is the offset (from zero) of the start of physical
 * memory.  This is used for XIP and NoMMU kernels, and on platforms that don't
 * have CONFIG_ARM_PATCH_PHYS_VIRT. Assembly code must always use
 * PLAT_PHYS_OFFSET and not PHYS_OFFSET.
 */
#define PLAT_PHYS_OFFSET	UL(CONFIG_PHYS_OFFSET)

開始執行建立頁表的函式: __create_page_tables
__create_page_tables 定義在 arch/arm/kernel/head.S 中:

__create_page_tables:
	pgtbl	r4, r8				@ page table address

	/*
	 * Clear the swapper page table
	 */
	mov	r0, r4
	mov	r3, #0
	add	r6, r0, #PG_DIR_SIZE
1:	str	r3, [r0], #4
	str	r3, [r0], #4
	str	r3, [r0], #4
	str	r3, [r0], #4
	teq	r0, r6
	bne	1b

#ifdef CONFIG_ARM_LPAE
	/*
	 * Build the PGD table (first level) to point to the PMD table. A PGD
	 * entry is 64-bit wide.
	 */
	mov	r0, r4
	add	r3, r4, #0x1000			@ first PMD table address
	orr	r3, r3, #3			@ PGD block type
	mov	r6, #4				@ PTRS_PER_PGD
	mov	r7, #1 << (55 - 32)		@ L_PGD_SWAPPER
1:
#ifdef CONFIG_CPU_ENDIAN_BE8
	str	r7, [r0], #4			@ set top PGD entry bits
	str	r3, [r0], #4			@ set bottom PGD entry bits
#else
	str	r3, [r0], #4			@ set bottom PGD entry bits
	str	r7, [r0], #4			@ set top PGD entry bits
#endif
	add	r3, r3, #0x1000			@ next PMD table
	subs	r6, r6, #1
	bne	1b

	add	r4, r4, #0x1000			@ point to the PMD tables
#ifdef CONFIG_CPU_ENDIAN_BE8
	add	r4, r4, #4			@ we only write the bottom word
#endif
#endif

	ldr	r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

	/*
	 * Create identity mapping to cater for __enable_mmu.
	 * This identity mapping will be removed by paging_init().
	 */
	adr	r0, __turn_mmu_on_loc
	ldmia	r0, {r3, r5, r6}
	sub	r0, r0, r3			@ virt->phys offset
	add	r5, r5, r0			@ phys __turn_mmu_on
	add	r6, r6, r0			@ phys __turn_mmu_on_end
	mov	r5, r5, lsr #SECTION_SHIFT
	mov	r6, r6, lsr #SECTION_SHIFT

1:	orr	r3, r7, r5, lsl #SECTION_SHIFT	@ flags + kernel base
	str	r3, [r4, r5, lsl #PMD_ORDER]	@ identity mapping
	cmp	r5, r6
	addlo	r5, r5, #1			@ next section
	blo	1b

	/*
	 * Map our RAM from the start to the end of the kernel .bss section.
	 */
	add	r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
	ldr	r6, =(_end - 1)
	orr	r3, r8, r7
	add	r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1:	str	r3, [r0], #1 << PMD_ORDER
	add	r3, r3, #1 << SECTION_SHIFT
	cmp	r0, r6
	bls	1b

#ifdef CONFIG_XIP_KERNEL
	/*
	 * Map the kernel image separately as it is not located in RAM.
	 */
#define XIP_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
	mov	r3, pc
	mov	r3, r3, lsr #SECTION_SHIFT
	orr	r3, r7, r3, lsl #SECTION_SHIFT
	add	r0, r4,  #(XIP_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)
	str	r3, [r0, #((XIP_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!
	ldr	r6, =(_edata_loc - 1)
	add	r0, r0, #1 << PMD_ORDER
	add	r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1:	cmp	r0, r6
	add	r3, r3, #1 << SECTION_SHIFT
	strls	r3, [r0], #1 << PMD_ORDER
	bls	1b
#endif

	/*
	 * Then map boot params address in r2 if specified.
	 * We map 2 sections in case the ATAGs/DTB crosses a section boundary.
	 */
	mov	r0, r2, lsr #SECTION_SHIFT
	movs	r0, r0, lsl #SECTION_SHIFT
	subne	r3, r0, r8
	addne	r3, r3, #PAGE_OFFSET
	addne	r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
	orrne	r6, r7, r0
	strne	r6, [r3], #1 << PMD_ORDER
	addne	r6, r6, #1 << SECTION_SHIFT
	strne	r6, [r3]

#if defined(CONFIG_ARM_LPAE) && defined(CONFIG_CPU_ENDIAN_BE8)
	sub	r4, r4, #4			@ Fixup page table pointer
						@ for 64-bit descriptors
#endif

#ifdef CONFIG_DEBUG_LL
#if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)
	/*
	 * Map in IO space for serial debugging.
	 * This allows debug messages to be output
	 * via a serial console before paging_init.
	 */
	addruart r7, r3, r0

	mov	r3, r3, lsr #SECTION_SHIFT
	mov	r3, r3, lsl #PMD_ORDER

	add	r0, r4, r3
	mov	r3, r7, lsr #SECTION_SHIFT
	ldr	r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
	orr	r3, r7, r3, lsl #SECTION_SHIFT
#ifdef CONFIG_ARM_LPAE
	mov	r7, #1 << (54 - 32)		@ XN
#ifdef CONFIG_CPU_ENDIAN_BE8
	str	r7, [r0], #4
	str	r3, [r0], #4
#else
	str	r3, [r0], #4
	str	r7, [r0], #4
#endif
#else
	orr	r3, r3, #PMD_SECT_XN
	str	r3, [r0], #4
#endif

#else /* CONFIG_DEBUG_ICEDCC || CONFIG_DEBUG_SEMIHOSTING */
	/* we don't need any serial debugging mappings */
	ldr	r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
#endif

#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
	/*
	 * If we're using the NetWinder or CATS, we also need to map
	 * in the 16550-type serial port for the debug messages
	 */
	add	r0, r4, #0xff000000 >> (SECTION_SHIFT - PMD_ORDER)
	orr	r3, r7, #0x7c000000
	str	r3, [r0]
#endif
#ifdef CONFIG_ARCH_RPC
	/*
	 * Map in screen at 0x02000000 & SCREEN2_BASE
	 * Similar reasons here - for debug.  This is
	 * only for Acorn RiscPC architectures.
	 */
	add	r0, r4, #0x02000000 >> (SECTION_SHIFT - PMD_ORDER)
	orr	r3, r7, #0x02000000
	str	r3, [r0]
	add	r0, r4, #0xd8000000 >> (SECTION_SHIFT - PMD_ORDER)
	str	r3, [r0]
#endif
#endif
#ifdef CONFIG_ARM_LPAE
	sub	r4, r4, #0x1000		@ point to the PGD table
	mov	r4, r4, lsr #ARCH_PGD_SHIFT
#endif
	ret	lr
ENDPROC(__create_page_tables)

其中 pgtbl 宏的定義為(在 arch/arm/kernel/head.S 中):

.macro	pgtbl, rd, phys
add	\rd, \phys, #TEXT_OFFSET
sub	\rd, \rd, #PG_DIR_SIZE
.endm

swapper_pg_dir 的定義為(在 arch/arm/kernel/head.S 中):

.globl	swapper_pg_dir
.equ	swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE

PG_DIR_SIZE 的定義為(在 arch/arm/kernel/head.S 中):

#ifdef CONFIG_ARM_LPAE
	/* LPAE requires an additional page for the PGD */
#define PG_DIR_SIZE	0x5000
#define PMD_ORDER	3
#else
#define PG_DIR_SIZE	0x4000
#define PMD_ORDER	2
#endif

未完待續

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/170955.html

標籤:python

上一篇:坐標上海,我看見這群開發者用熱愛改變世界

下一篇:深入Linux內核(記憶體篇)—頁表映射分頁

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more