主頁 > 區塊鏈 > linux arm32 mmu 啟動代碼分析(匯編部分)

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

2020-10-13 19:02:36 區塊鏈

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
*/

其中 r1 r2bootloader(uboot grub等) 進行設定,r2dtb 結構的地址,r9 是通過之前 mrc 指令讀取到的,r10 是通過 __lookup_processor_type 獲取到的 __proc_info 結構的地址,r8 由如下操作獲取(我在編譯時并沒有開啟 CONFIG_XIP_KERNEL 選項):

#ifndef CONFIG_XIP_KERNEL
adr	r3, 2f
ldmia	r3, {r4, r8}
sub	r4, r3, r4			
add	r8, r8, r4			
#else
ldr	r8, =PLAT_PHYS_OFFSET		
#endif

/*標號2如下*/
#ifndef CONFIG_XIP_KERNEL
2:	.long	.
	.long	PAGE_OFFSET
#endif

執行 adr r3 2f 獲取了標號 2 的實際地址,并將 2 下定義的兩個 .long 型的資料裝在到 r4 r8 中,其中 r4 中裝載的是 .long . 定義的資料,即當前虛擬地址(也即標號 2 在映像中的虛擬地址),而 r3 則是標號 2 的實際物理地址,執行 sub r4, r3, r4 即 r4 = r3 - r4 后,即為 映像虛擬地址與實際物理地址之間的偏移,設為 OFFSET,再執行 add r8, r8, r4 即 r8 = r8 + r4 后,r8 中的值即為 PAGE_OFFSET + OFFSET,之前提到 PAGE_OFFSET 是 linux 內核映像的起始 虛擬地址 ,這回加上計算得到的 OFFSET(虛擬地址與物理地址的偏移) 后,即得到了 linux 內核映像在物理記憶體中的實際起始地址了,即,r8 存盤的值目前是 linux 內核映像在物理記憶體中的實際起始地址,搞清楚這些暫存器中的值后,就可以開始分析初始頁表建立的程序了,

開始執行建立頁表的函式: __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/qukuanlian/171180.html

標籤:區塊鏈

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

下一篇:深入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)

熱門瀏覽
  • JAVA使用 web3j 進行token轉賬

    最近新學習了下區塊鏈這方面的知識,所學不多,給大家分享下。 # 1. 關于web3j web3j是一個高度模塊化,反應性,型別安全的Java和Android庫,用于與智能合約配合并與以太坊網路上的客戶端(節點)集成。 # 2. 準備作業 jdk版本1.8 引入maven <dependency> < ......

    uj5u.com 2020-09-10 03:03:06 more
  • 以太坊智能合約開發框架Truffle

    前言 部署智能合約有多種方式,命令列的瀏覽器的渠道都有,但往往跟我們程式員的風格不太相符,因為我們習慣了在IDE里寫了代碼然后打包運行看效果。 雖然現在IDE中已經存在了Solidity插件,可以撰寫智能合約,但是部署智能合約卻要另走他路,沒辦法進行一個快捷的部署與測驗。 如果團隊管理的區塊節點多、 ......

    uj5u.com 2020-09-10 03:03:12 more
  • 谷歌二次驗證碼成為區塊鏈專用安全碼,你怎么看?

    前言 谷歌身份驗證器,前些年大家都比較陌生,但隨著國內互聯網安全的加強,它越來越多地出現在大家的視野中。 比較廣泛接觸的人群是國際3A游戲愛好者,游戲盜號現象嚴重+國外賬號安全應用廣泛,這類游戲一般都會要求用戶系結名為“兩步驗證”、“雙重驗證”等,平臺一般都推薦用谷歌身份驗證器。 后來區塊鏈業務風靡 ......

    uj5u.com 2020-09-10 03:03:17 more
  • 密碼學DAY1

    目錄 ##1.1 密碼學基本概念 密碼在我們的生活中有著重要的作用,那么密碼究竟來自何方,為何會產生呢? 密碼學是網路安全、資訊安全、區塊鏈等產品的基礎,常見的非對稱加密、對稱加密、散列函式等,都屬于密碼學范疇。 密碼學有數千年的歷史,從最開始的替換法到如今的非對稱加密演算法,經歷了古典密碼學,近代密 ......

    uj5u.com 2020-09-10 03:03:50 more
  • 密碼學DAY1_02

    目錄 ##1.1 ASCII編碼 ASCII(American Standard Code for Information Interchange,美國資訊交換標準代碼)是基于拉丁字母的一套電腦編碼系統,主要用于顯示現代英語和其他西歐語言。它是現今最通用的單位元組編碼系統,并等同于國際標準ISO/IE ......

    uj5u.com 2020-09-10 03:04:50 more
  • 密碼學DAY2

    ##1.1 加密模式 加密模式:https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html ECB ECB : Electronic codebook, 電子密碼本. 需要加密的訊息按照塊密碼的塊大小被分為數個塊,并對每個塊進 ......

    uj5u.com 2020-09-10 03:05:42 more
  • NTP時鐘服務器的特點(京準電子)

    NTP時鐘服務器的特點(京準電子) NTP時鐘服務器的特點(京準電子) 京準電子官V——ahjzsz 首先對時間同步進行了背景介紹,然后討論了不同的時間同步網路技術,最后指出了建立全球或區域時間同步網存在的問題。 一、概 述 在通信領域,“同步”概念是指頻率的同步,即網路各個節點的時鐘頻率和相位同步 ......

    uj5u.com 2020-09-10 03:05:47 more
  • 標準化考場時鐘同步系統推進智能化校園建設

    標準化考場時鐘同步系統推進智能化校園建設 標準化考場時鐘同步系統推進智能化校園建設 安徽京準電子科技官微——ahjzsz 一、背景概述隨著教育事業的快速發展,學校建設如雨后春筍,隨之而來的學校教育、管理、安全方面的問題成了學校管理人員面臨的最大的挑戰,這些問題同時也是學生家長所擔心的。為了讓學生有更 ......

    uj5u.com 2020-09-10 03:05:51 more
  • 位元幣入門

    引言 位元幣基本結構 位元幣基礎知識 1)哈希演算法 2)非對稱加密技術 3)數字簽名 4)MerkleTree 5)哪有位元幣,有的是UTXO 6)位元幣挖礦與共識 7)區塊驗證(共識) 總結 引言 上一篇我們已經知道了什么是區塊鏈,此篇說一下區塊鏈的第一個應用——位元幣。其實先有位元幣,后有的區塊 ......

    uj5u.com 2020-09-10 03:06:15 more
  • 北斗對時服務器(北斗對時設備)電力系統應用

    北斗對時服務器(北斗對時設備)電力系統應用 北斗對時服務器(北斗對時設備)電力系統應用 京準電子科技官微(ahjzsz) 中國北斗衛星導航系統(英文名稱:BeiDou Navigation Satellite System,簡稱BDS),因為是目前世界范圍內唯一可以大面積提供免費定位服務的系統,所以 ......

    uj5u.com 2020-09-10 03:06:20 more
最新发布
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:46:47 more
  • Hyperledger Fabric 使用 CouchDB 和復雜智能合約開發

    在上個實驗中,我們已經實作了簡單智能合約實作及客戶端開發,但該實驗中智能合約只有基礎的增刪改查功能,且其中的資料管理功能與傳統 MySQL 比相差甚遠。本文將在前面實驗的基礎上,將 Hyperledger Fabric 的默認資料庫支持 LevelDB 改為 CouchDB 模式,以實作更復雜的資料... ......

    uj5u.com 2023-04-16 07:28:31 more
  • .NET Core 波場鏈離線簽名、廣播交易(發送 TRX和USDT)筆記

    Get Started NuGet You can run the following command to install the Tron.Wallet.Net in your project. PM> Install-Package Tron.Wallet.Net 配置 public reco ......

    uj5u.com 2023-04-14 08:08:00 more
  • DKP 黑客分析——不正確的代幣對比率計算

    概述: 2023 年 2 月 8 日,針對 DKP 協議的閃電貸攻擊導致該協議的用戶損失了 8 萬美元,因為 execute() 函式取決于 USDT-DKP 對中兩種代幣的余額比率。 智能合約黑客概述: 攻擊者的交易:0x0c850f,0x2d31 攻擊者地址:0xF38 利用合同:0xf34ad ......

    uj5u.com 2023-04-07 07:46:09 more
  • Defi開發簡介

    Defi開發簡介 介紹 Defi是去中心化金融的縮寫, 是一項旨在利用區塊鏈技術和智能合約創建更加開放,可訪問和透明的金融體系的運動. 這與傳統金融形成鮮明對比,傳統金融通常由少數大型銀行和金融機構控制 在Defi的世界里,用戶可以直接從他們的電腦或移動設備上訪問廣泛的金融服務,而不需要像銀行或者信 ......

    uj5u.com 2023-04-05 08:01:34 more
  • solidity簡單的ERC20代幣實作

    // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; import "hardhat/console.sol"; //ERC20 同質化代幣,每個代幣的本質或性質都是相同 //ETH 是原生代幣,它不是ERC20代幣, ......

    uj5u.com 2023-03-21 07:56:29 more
  • solidity 參考型別修飾符memory、calldata與storage 常量修飾符C

    在solidity語言中 參考型別修飾符(參考型別為存盤空間不固定的數值型別) memory、calldata與storage,它們只能修飾參考型別變數,比如字串、陣列、位元組等... memory 適用于方法傳參、返參或在方法體內使用,使用完就會清除掉,釋放記憶體 calldata 僅適用于方法傳參 ......

    uj5u.com 2023-03-08 07:57:54 more
  • solidity注解標簽

    在solidity語言中 注釋符為// 注解符為/* 內容*/ 或者 是 ///內容 注解中含有這幾個標簽給予我們使用 @title 一個應該描述合約/介面的標題 contract, library, interface @author 作者的名字 contract, library, interf ......

    uj5u.com 2023-03-08 07:57:49 more
  • 評價指標:相似度、GAS消耗

    【代碼注釋自動生成方法綜述】 這些評測指標主要來自機器翻譯和文本總結等研究領域,可以評估候選文本(即基于代碼注釋自動方法而生成)和參考文本(即基于手工方式而生成)的相似度. BLEU指標^[^?88^^?^]^:其全稱是bilingual evaluation understudy.該指標是最早用于 ......

    uj5u.com 2023-02-23 07:27:39 more
  • 基于NOSTR協議的“公有制”版本的Twitter,去中心化社交軟體Damus

    最近,一個幽靈,Web3的幽靈,在網路游蕩,它叫Damus,這玩意詮釋了什么叫做病毒式營銷,滑稽的是,一個Web3產品卻在Web2的產品鏈上瘋狂傳銷,各方大佬紛紛為其背書,到底發生了什么?Damus的葫蘆里,賣的是什么藥? 注冊和簡單實用 很少有什么產品在用戶注冊環節會有什么噱頭,但Damus確實出 ......

    uj5u.com 2023-02-05 06:48:39 more