免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 10042 | 回复: 2

[内存管理] amd64 arch启动代码早期paging相关的疑问 [复制链接]

论坛徽章:
3
15-16赛季CBA联赛之山东
日期:2016-10-30 08:47:3015-16赛季CBA联赛之佛山
日期:2016-12-17 00:06:31CU十四周年纪念徽章
日期:2017-12-03 01:04:02
发表于 2020-03-31 18:11 |显示全部楼层
本帖最后由 captivated 于 2020-03-31 18:44 编辑

x86 arch 的启动代码比较复杂,为了说清楚我的具体问题,所以先做一个简述(免得说我自己没把基本问题搞清楚,省点抬杠时间)。
此外以下简述主要针对 x86_64 arch 而不是 i386.


1. 启动映像生成:

x86 arch 的启动映像为 $OUT/arch/x86/boot/bzImage. $OUT 表示 kernel 编译输出目录(如果编译时不指定 -o 输出目录,则和编译源码是同一个目录)。
bzImage 构成如下。为了方便,$OUT/arch/x86/boot 目录简称为 $(boot) 路径.


1) 最终的 bzImage 是由 $(boot)/setup.bin 和 $(boot)/vmlinux.bin 组装成的.
    $(boot)/vmlinux.bin 又由 $(boot)/compressed/vmlinux 通过 objcopy 剥离掉 .comment 和 .note 段而成.
    而 $(boot)/compressed/vmlinux 则由 $(boot)/compressed 下面的各个目标文件组成, 里面最重要的是
    $(boot)/compressed/piggy.o 这个文件.

  1. bzImage <-+ $(boot)/setup.bin
  2. #######-+ $(boot)/vmlinux.bin <-+ $(boot)/compressed/vmlinux <-+ $(boot)/compressed/head_64.o
  3. ############################################### $(boot)/compressed/misc.o
  4. ############################################### ...
  5. ############################################### $(boot)/compressed/piggy.o
复制代码


2) 为什么说 piggy.o 重要, 因为它是由压缩了的 vmlinux 构成的. 这里默认选 gzip 压缩.
而 vmlinux.bin.gz 又是由 compressed 同目录下的 vmlinux.bin 和 vmlinux.relocs 构成的.
vmlinux.bin 和 vmlinux.relocs, 则来自于 $OUT 目录下的 VMLINUX(实际文件名就是 $OUT/vmlinux. 大写的意思是表示它其实就是编译最终的链接文件, 从它到最终的 bzImage 不过是一堆二进制工具为了启动加载协议做的一堆变换而已).

  1. $(boot)/compressed/piggy.o <+ $(boot)/compressed/vmlinux.bin.gz <-+ $(boot)/compressed/vmlinux.bin
  2. ###############################################  $(boot)/compressed/vmlinux.relocs
复制代码


2. x86 arch 启动问题


x86 相关的 boot protocal 有几种. 不管哪一种,总之 bzImage 得要 bootloader 加载到内存里面然后去运行才行.
前面的映像构建过程为什么是那样子, 估计也和 x86 arch “历史悠久”, boot protocal 复杂相关.
但大概来说, 有这么几种 boot protocal:

1) 实模式 boot protocal.
这种情况下, bootloader 是运行在实模式的. 就算 bootloader 自己切换了保护模式, 它加载完 kernel(bzImage)并跳转过去执行前, 也得切回实模式, 因为 protocal 就是协议嘛, 大家要有个商量. 这时 bootloader 切换过去后, 最先从 setup.bin 开始运行(bzImage 是由 setup.bin + vmlinux.bin 包的), 也就是走
arch/x86/boot/header.S -> _start -> calll main
arch/x86/boot/main.c -> main -> go_to_protected_mode
arch/x86/boot/pm.c -> go_to_protected_mode -> protected_mode_jump
arch/x86/boot/pmjump.S -> GLOBAL(protected_mode_jump) -> jmpl *%eax

经过这些步骤, 就会开始执行到 vmlinux.bin 里面的东西了.


2) 32 位 boot protocal
现在 bootloader 已经很强大了, 所以通常 bootloader 自己已经切到 32 位 protected mode, 然后就不走 setup.bin 那一套了.
前面说了 bzImage 是由 setup.bin + vmlinux.bin(都在 $(boot) 目录下)组装而成, 跳过 setup.bin 的话, 当然就直接从 vmlinux.bin 开始执行了.
bootloader 为什么可以这样做, 因为 bzImage 就是 bootloader 加载的, 它当然知道 setup.bin vmlinux.bin 分别在什么内存位置.
一般来说, setup.bin 必须要运行在实模式, 因此其加载地址是物理内存 1MB 以下的, 而 vmlinux.bin 会刚刚好从 1MB 位置开始放置.
anyway, 这时候走的是这个:
arch/x86/boot/compressed/head_64.S -> ENTRY(startup_32) -> ENTRY(startup_64) -> jmp *%eax

这里还要说明一下. 前面映像构建过程说得清楚, 真正的 kernel 是 piggy.o(由 $(OUT)/vmlinux 压缩, 然后生成的目标文件).
startup_32 -> startup_64 主要是从 32 位保护模式切换到 64 bit long mode. 切换之前会建立一个 4GB 简单 identity map(因为切换到 64 bit long mode 的条件之一就是, paging 必须是开的, 即 CR0.PG == 1).
而 startup_64 呢, 则会调用一个解压缩函数将 vmlinux.bin.gz 解压, 然后跳转到解压后的 kernel 入口去执行. 这里面可能还会有重定位处理等等.
如果关掉 KASLR, 那么解压后的 kernel, 也就是真正的 $(OUT)/vmlinux, 默认会放在 16MB 的位置.



3) 64 位 boot protocal

顾名思义, 就是 bootloader 有点过分强大(或者讨厌)了, 跳转到 kernel 之前, 它会自己先切到 64 bit long mode.
也就是直接执行 arch/x86/boot/compressed/head_64.S 中的 startup_64 了.
这时最初的 paging 是 bootloader 建立的.


3. vmlinux entry

好了,前面这些复杂的过程略过. 不管是先从 arch/x86/boot/setup.S 开始, 还是先从 arch/x86/boot/compressed/head_64.S 开始,
最终解压缩后的 kernel 入口点是:
arch/x86/kernel/head_64.S 里面的 startup_64 函数(or 一个全局符号).
有人可能会问这不和 compressed/head_64.S 里面的 startup_64 重名吗, 答案是它们根本没链接在一起, 各管各的, 不会有重定义错误放心.

啊, 约了不知道多少次电影请喝了多少次奶茶爬了多少次山, 终于可以约爬床了, 终于宽衣解带了, 兴奋之情溢于言表啊...

结果才看了特么几行, 我就被郁闷到了...

...

具体问题叙述还有点小麻烦, 呆会贴上来.

...


论坛徽章:
3
15-16赛季CBA联赛之山东
日期:2016-10-30 08:47:3015-16赛季CBA联赛之佛山
日期:2016-12-17 00:06:31CU十四周年纪念徽章
日期:2017-12-03 01:04:02
发表于 2020-03-31 18:31 |显示全部楼层
回来了

在线代码见

https://elixir.bootlin.com/linux ... 86/kernel/head_64.S

  1. .Ljump_to_C_code:
  2.         /*
  3.          * Jump to run C code and to be on a real kernel address.
  4.          * Since we are running on identity-mapped space we have to jump
  5.          * to the full 64bit address, this is only possible as indirect
  6.          * jump.  In addition we need to ensure %cs is set so we make this
  7.          * a far return.
  8.          *
  9.          * Note: do not change to far jump indirect with 64bit offset.
  10.          *
  11.          * AMD does not support far jump indirect with 64bit offset.
  12.          * AMD64 Architecture Programmer's Manual, Volume 3: states only
  13.          *        JMP FAR mem16:16 FF /5 Far jump indirect,
  14.          *                with the target specified by a far pointer in memory.
  15.          *        JMP FAR mem16:32 FF /5 Far jump indirect,
  16.          *                with the target specified by a far pointer in memory.
  17.          *
  18.          * Intel64 does support 64bit offset.
  19.          * Software Developer Manual Vol 2: states:
  20.          *        FF /5 JMP m16:16 Jump far, absolute indirect,
  21.          *                address given in m16:16
  22.          *        FF /5 JMP m16:32 Jump far, absolute indirect,
  23.          *                address given in m16:32.
  24.          *        REX.W + FF /5 JMP m16:64 Jump far, absolute indirect,
  25.          *                address given in m16:64.
  26.          */
  27.         pushq        $.Lafter_lret        # put return address on stack for unwinder
  28.         xorl        %ebp, %ebp        # clear frame pointer
  29.         movq        initial_code(%rip), %rax
  30.         pushq        $__KERNEL_CS        # set correct cs
  31.         pushq        %rax                # target address in negative space
  32.         lretq
复制代码
这里跳转到 x86_64_start_kernel@head64.c, 在线代码 https://elixir.bootlin.com/linux ... x86/kernel/head64.c

代码
  1. asmlinkage __visible void __init x86_64_start_kernel(char * real_mode_data)
  2. {
  3.         ...

  4.         cr4_init_shadow();

  5.         /* Kill off the identity-map trampoline */
  6.         reset_early_page_tables();

  7.         clear_bss();

  8.         clear_page(init_top_pgt);

  9.         /*
  10.          * SME support may update early_pmd_flags to include the memory
  11.          * encryption mask, so it needs to be called before anything
  12.          * that may generate a page fault.
  13.          */
  14.         sme_early_init();

  15.         kasan_early_init();

  16.         idt_setup_early_handler();
复制代码

这里面, reset_early_page_tables 反手就把 pgt 给清了!
  1. /* Wipe all early page tables except for the kernel symbol map */
  2. static void __init reset_early_page_tables(void)
  3. {
  4.         memset(early_top_pgt, 0, sizeof(pgd_t)*(PTRS_PER_PGD-1));
  5.         next_early_pgt = 0;
  6.         write_cr3(__sme_pa_nodebug(early_top_pgt));
  7. }
复制代码

但这时候,中断处理还没设立,%cr3 里面就是 early_top_pgt,这么把 PGT 一清,不会 paging 异常吗???!!!



论坛徽章:
3
15-16赛季CBA联赛之山东
日期:2016-10-30 08:47:3015-16赛季CBA联赛之佛山
日期:2016-12-17 00:06:31CU十四周年纪念徽章
日期:2017-12-03 01:04:02
发表于 2020-03-31 21:22 |显示全部楼层
哦,我知道了。

没注意
memset(early_top_pgt, 0, sizeof(pgt_t) * (PTRS_PER_PGD - 1));
而不是
memset(early_top_pgt, 0, sizeof(pgt_t) * PTRS_PER_PGD);

并没有全清, 也就是说 early_top_gpt[511] 是没有清掉的, 那么 l2_kernel_pgt 映射的 512MB 还在, 但是 head64.c 里面 __startup_64 建立的 identity mapping 被清掉了.

艹, 把我困惑了好两天, 原来是自己看代码太不仔细了
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

DTCC2020中国数据库技术大会

【架构革新 高效可控】2020年12月21日-23日第十一届中国数据库技术大会将在北京隆重召开。

大会以“架构革新 高效可控”为主题,设置2大主会场,20+技术专场,将邀请超百位行业专家,重点围绕数据架构、AI与大数据、传统企业数据库实践和国产开源数据库等内容展开分享和探讨,为广大数据领域从业人士提供一场年度盛会和交流平台。
http://dtcc.it168.com


大会官网>>
  

北京盛拓优讯信息技术有限公司. 版权所有 16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP