RISC-V Linuxアセンブリ起動プロセス解析

リリース: 2023-08-01 15:40:40
転載
1618 人が閲覧しました

RISC-V Linux のアセンブリ起動部分は比較的単純で、それほど複雑ではありません。ページ テーブルの作成とリダイレクトという 2 つのコア部分があります。ページテーブルの作成はC言語で書かれています、今日はまずアセンブリ部分を解析していきます、まずアセンブリの起動プロセス全体を解析してから、リダイレクトの解析をしていきます。

注: この記事は、linux5.10.111 カーネルに基づいています

アセンブリ起動プロセス

コンパイルが何を行うのか全体的な分析から始めて、一般的なフレームワークを取得しましょう。

パス: arch/riscv/kernel/head.S、入り口は ENTRY(_start_kernel)

RISC-V Linuxアセンブリ起動プロセス解析

ENTRY(_start_kernel) から開始して、起動前の初期化と、ページ テーブルを確立する前の主な作業を実行します。

  • #すべての割り込みを閉じる
    #
    /* 关闭所有中断 */
        csrw CSR_IE, zero
        csrw CSR_IP, zero
    ログイン後にコピー
#グローバル ポインタ gp をロード
  • #
    /* 加载全局指针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
    ログイン後にコピー
ハート ID と 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会创建临时页表
    ログイン後にコピー
リダイレクト
  • #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 がページ テーブルを作成した後、relocate リダイレクトの実行が開始されます。このリダイレクトは主に mmu を有効にします。relocate のアセンブリは以下で分析されます。

relocate リダイレクトを再配置します。これにより、mmu が有効になります。 mmu をオンにする操作は、第 1 レベルのページ テーブルのアドレスと権限を 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アセンブリ起動プロセス解析

MODE0x8 に等しい場合は、sv39 mmu を使用することを意味し、0x0 はアドレス変換がないことを意味します。つまり、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 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:嵌入式Linux充电站
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!