RISC-V Linux 어셈블리 시작 프로세스 분석

풀어 주다: 2023-08-01 15:40:40
앞으로
1651명이 탐색했습니다.

RISC-V Linux의 조립 시작 부분은 비교적 간단하고 그리 복잡하지 않습니다. 페이지 테이블 생성과 리디렉션이라는 두 가지 핵심 부분이 있습니다. 페이지 테이블 생성은 C 언어로 작성됩니다. 오늘은 먼저 어셈블리 부분을 분석하고 전체 어셈블리 시작 프로세스를 분석한 다음 리디렉션을 분석하겠습니다.

참고: 이 문서는 linux5.10.111 커널

어셈블리 시작 프로세스

먼저 어셈블리가 수행하는 작업에 대한 전반적인 분석부터 시작해 보겠습니다. 일반적인 프레임워크가 있습니다.

경로: arch /riscv/kernel/head.S, 입구는 ENTRY(_start_kernel)arch/riscv/kernel/head.S,入口是ENTRY(_start_kernel)

RISC-V Linux 어셈블리 시작 프로세스 분석

ENTRY(_start_kernel)

RISC-V Linux 어셈블리 시작 프로세스 분석

ENTRY(_start_kernel)시작하기 전에 일부 초기화를 시작하고 페이지 테이블을 설정하기 전에 주요 작업을 시작합니다. 🎜
  • 모든 인터럽트 끄기
/* 关闭所有中断 */
    csrw CSR_IE, zero
    csrw CSR_IP, zero
로그인 후 복사
  • 전역 포인터 로드
/* 加载全局指针gp */
.option push
.option norelax
    la gp, __global_pointer$
.option pop
로그인 후 복사
  • FPU 비활성화
/* 禁用 FPU 以检测内核空间中浮点的非法使用*/
    li t0, SR_FS
    csrc CSR_STATUS, t0
로그인 후 복사
  • 시작하려면 코어를 선택하세요
/* 选择一个核启动 */
    la a3, hart_lottery
    li a2, 1
    amoadd.w a3, a2, (a3)
    bnez a3, .Lsecondary_start
로그인 후 복사
  • BSS 삭제 세그먼트
/* 清除bss */
    la a3, __bss_start
    la a4, __bss_stop
    ble a4, a3, clear_bss_done
로그인 후 복사
  • 하트 아이디와 dtb 주소를 저장
/* 保存hatr id和dtb地址,hart id保存到a0,dtb地址保存到a1 */
    mv s0, a0
    mv s1, a1
    la a2, boot_cpu_hartid
로그인 후 복사
  • sp 포인터 설정
    la sp, init_thread_union + THREAD_SIZE
로그인 후 복사
  • 위 작업이 완료되면 임시 페이지 테이블 생성이 시작되는데, 그리고 C 함수 setup_vm으로 점프하여 임시 페이지 테이블을 생성합니다
    mv a0, s1
    call setup_vm // 跳转到C函数setup_vm,setup_vm会创建临时页表
로그인 후 복사
  • Redirect
#ifdef CONFIG_MMU
    la a0, early_pg_dir
    call relocate	//重定向,实际就是开启MMU
#endif
로그인 후 복사
  • 예외 벡터 주소를 설정하고 C 환경을 다시 로드합니다
    call setup_trap_vector
/* 重载C环境 */
    la tp, init_task
    sw zero, TASK_TI_CPU(tp)
    la sp, init_thread_union + THREAD_SIZE
로그인 후 복사
  • 마지막으로 C로 점프합니다. start_kernel 함수, C 언어 부분 초기화 시작 및 어셈블리 부분이 실행됩니다
tail start_kernel
로그인 후 복사

완전한 _start_kernel 어셈블리 코드:

ENTRY(_start_kernel)
	/* 关闭所有中断 */
	csrw CSR_IE, zero
	csrw CSR_IP, zero

	/* 在源码中,这里有一个M模式处理的宏,这里没有用到,直接跳过*/

	/* 加载全局指针gp */
.option push
.option norelax
	la gp, __global_pointer$
.option pop

	/* 禁用 FPU 以检测内核空间中浮点的非法使用*/
	li t0, SR_FS
	csrc CSR_STATUS, t0

#ifdef CONFIG_SMP
	li t0, CONFIG_NR_CPUS
	blt a0, t0, .Lgood_cores
	tail .Lsecondary_park
.Lgood_cores:
#endif

	/* 选择一个核启动 */
	la a3, hart_lottery
	li a2, 1
	amoadd.w a3, a2, (a3)
	bnez a3, .Lsecondary_start

	/* 清除bss */
	la a3, __bss_start
	la a4, __bss_stop
	ble a4, a3, clear_bss_done
clear_bss:
	REG_S zero, (a3)
	add a3, a3, RISCV_SZPTR
	blt a3, a4, clear_bss
clear_bss_done:

	/* 保存hatr id和dtb地址,hart id保存到a0,dtb地址保存到a1 */
	mv s0, a0
	mv s1, a1
	la a2, boot_cpu_hartid
	REG_S a0, (a2)

	/* 初始化页表,然后重定向到虚拟地址 */
	la sp, init_thread_union + THREAD_SIZE
	mv a0, s1
	call setup_vm // 跳转到C函数setup_vm,setup_vm会创建临时页表
#ifdef CONFIG_MMU
	la a0, early_pg_dir
	call relocate	//重定向,实际就是开启MMU
#endif /* CONFIG_MMU */

	call setup_trap_vector
	/* 重载C环境 */
	la tp, init_task
	sw zero, TASK_TI_CPU(tp)
	la sp, init_thread_union + THREAD_SIZE

#ifdef CONFIG_KASAN
	call kasan_early_init
#endif
	/* Start the kernel */
	call soc_early_init
	tail start_kernel	//跳转到C函数start_kernel,开始C语言部分初始化
로그인 후 복사

어셈블리에서 매우 중요한 부분은 후속 프로그램이 실행될지 여부를 결정하는 페이지 테이블 생성입니다. 계속 실행할 수 있습니다. setup_vm이 페이지 테이블을 생성한 후 재배치 리디렉션을 실행하기 시작합니다. 이 리디렉션은 주로 mmu를 활성화합니다.

relocate

재배치 리디렉션은 mmu를 활성화하는 것입니다. mmu를 켜는 작업은 첫 번째 수준 페이지 테이블의 주소와 권한을 satp 레지스터에 쓰는 것입니다. 이는 mmu를 켜는 것으로 간주됩니다.

#ifdef CONFIG_MMU
    la a0, early_pg_dir //跳转到relocate前,先把第一级页表early_pg_dir的地址存入a0
    call relocate		//跳转到relocate,开启MMU
#endif
로그인 후 복사

relocate有两次开启mmu的操作,第一次开启mmu使用的是setup_vm()建立的trampoline_gd_dir页表,这页表保存的是kernel的前2M内存。第二次开启MMU使用的是early_pg_dir页表,这个页表映射了整个kernel内存以及dtb的4M空间。

如果trampoline_pg_dir或者early_pg_dir这两个页表的映射没弄好的话,开启MMU的时候就会失败,所以页表的建立十分关键。页表创建后续再深究,下面分析relocate汇编代码。

  • 计算返回地址

    返回地址就是ra加上虚拟地址和物理地址之间的偏移量,这个是固定偏移量。PAGE_OFFSETkernel入口地址对应的虚拟地址,_start就是kernel入口地址的虚拟地址,PAGE_OFFSET - _start就得到它们之间的偏移,然后再和ra相加,就是返回地址。

/* Relocate return address */
	li a1, PAGE_OFFSET
	la a2, _start
	sub a1, a1, a2
	add ra, ra, a1
로그인 후 복사
  • 将异常入口1f的虚拟地址写入stvec寄存器

    因为一旦开启MMU,地址都变成了虚拟地址,原来访问的都是物理地址,开启MMU时,地址发生了改变,VA != PA,从而进入异常,所以要先设置异常入口地址,此时的异常入口为1f

/* Point stvec to virtual address of intruction after satp write */
	la a2, 1f
	add a2, a2, a1
	csrw CSR_TVEC, a2
로그인 후 복사
  • 提前计算切换到early_pg_dir页表要写入satp的值

再进入relocate之前,就已经把early_pg_dir赋值给a0了,所以a0是early_pg_dir。srl是逻辑右移,mmu使用的是sv39,虚拟地址39位,物理地址56位:

RISC-V Linux 어셈블리 시작 프로세스 분석低12位是偏移量,所以PAGE_SHIFT等于12,将early_pg_dir地址右移12位存到a2。根据satp寄存器定义:

RISC-V Linux 어셈블리 시작 프로세스 분석

MODE等于0x8代表使用sv39 mmu0x0代表不进行地址翻译,即不开启MMU。这里STAP_MODEsv39,即0x8。将early_pg_dir地址和SATP_MODE进行或运算后,即可得到写入satp寄存器的值,最后保存到a2

/* Compute satp for kernel page tables, but don't load it yet */
	srl a2, a0, PAGE_SHIFT
	li a1, SATP_MODE	//sv39 mmu
	or a2, a2, a1
로그인 후 복사
  • 第一次开启MMU,使用trampoline_pg_dir页表

satp值的计算和上述是一样的。开启MMU之前,通过sfence.vma命令先刷新TLB。此时开启MMU,就会进入下面的标号为1的汇编段

	la a0, trampoline_pg_dir
	srl a0, a0, PAGE_SHIFT
	or a0, a0, a1
	sfence.vma	
	csrw CSR_SATP, a0
로그인 후 복사

进入异常1f段,重新设置异常入口为.Lsecondary_park,然后切换到early_pg_dir页表,相当于第二次开启MMU。此时,如果之前建立的early_pg_dir页表不对,则会就进入.Lsecondary_park.Lsecondary_park里面是个wfi指令,是个死循环。

完整relocate汇编代码:

relocate:
	/* Relocate return address */
	li a1, PAGE_OFFSET
	la a2, _start
	sub a1, a1, a2
	add ra, ra, a1

	/* Point stvec to virtual address of intruction after satp write */
	la a2, 1f
	add a2, a2, a1
	csrw CSR_TVEC, a2

	/* Compute satp for kernel page tables, but don't load it yet */
	srl a2, a0, PAGE_SHIFT
	li a1, SATP_MODE
	or a2, a2, a1

	/*
	 * Load trampoline page directory, which will cause us to trap to
	 * stvec if VA != PA, or simply fall through if VA == PA.  We need a
	 * full fence here because setup_vm() just wrote these PTEs and we need
	 * to ensure the new translations are in use.
	 */
	la a0, trampoline_pg_dir
	srl a0, a0, PAGE_SHIFT
	or a0, a0, a1
	sfence.vma
	csrw CSR_SATP, a0
.align 2
1:
	/* Set trap vector to spin forever to help debug */
	la a0, .Lsecondary_park
	csrw CSR_TVEC, a0

	/* Reload the global pointer */
.option push
.option norelax
	la gp, __global_pointer$
.option pop

	/*
	 * Switch to kernel page tables.  A full fence is necessary in order to
	 * avoid using the trampoline translations, which are only correct for
	 * the first superpage.  Fetching the fence is guarnteed to work
	 * because that first superpage is translated the same way.
	 */
	csrw CSR_SATP, a2
	sfence.vma

	ret
로그인 후 복사

总结

以上就是RISC-V Linux的汇编启动流程,虽说RISC-V的指令不复杂,但要理解这个汇编启动的部分,还是需要一点基础和时间。另外,大多数人工作中基本用不上汇编,只有真正用上了理解才会比较深。希望本文能够帮助到有需要的人。

위 내용은 RISC-V Linux 어셈블리 시작 프로세스 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:嵌入式Linux充电站
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿