Skip to content

Latest commit

 

History

History
96 lines (58 loc) · 6.54 KB

Q&As.md

File metadata and controls

96 lines (58 loc) · 6.54 KB

常见问题

多思考,多帮助他人

关于答疑方式的说明

为什么内存分配的初始化函数不可以在 define_init 中(init阶段初始化)?

  • 因为这一阶段的初始化可能就会用到动态内存分配了,需要在之前就实现动态内存的分配(尽量早实现这个功能,最早就只能放在early_init中了)
阶段 可能会用到的功能 需要实现的功能
early_init 静态变量 动态内存分配
init 申请内存 进程树和调度器的初始化
rest_init / /

为什么处理器要要求内核也使用页表?但一般而言仅仅只是相差offset?

以下说明可能不准确

  • 内核也使用页表:对现代操作系统而言,进程页表是一定需要的;而对于内核而言,是否有内核页表影响不大,因为确实可以直接访问pa(physical address,物理地址) (比如bootloader,比如操作系统在开启页表前的一段时间),但是有了页表会更好,而且相关的代码有很大一部分可以复用进程页表(linux的vmalloc,kmap等)
  • 仅仅相差一个offset:历史原因,x86 和 OS ABI 设定,x86 规定要放在低 pa的东西在通常的 ABI 里都被 userspace 数据占用了;硬件驱动,比如DMA需要CPU给硬件一个pa,而内核只能给出一个va,但因为只差一个offset,就不需要再遍历页表查pa,进行一个加减法就可以获pa,加快DMA的速度。

分配的内存块地址必须与大小对齐

即:如果需要分配的内存大小是2的倍数,则地址也要是2的倍数;如果大小是4的倍数,地址也要是4的倍数;如果大小是8的倍数,则地址也要是8的倍数。和内存页的分配不同,一般的分配器不需要16字节以上的对齐,如大小是16的倍数,则地址只需是8的倍数

  • 指令集限制,比如 STRH 指令,保存16位2字节的数据,该指令会把给定的地址对齐后再写入存储器;倘若未对齐就写入可能会导致cache跨行写等硬件层面的复杂的特殊情况。而 STR/LDR指令支持8字节的数据的读写(64位架构),因此最多只需要8字节的对齐。
  • 简单说就是由于硬件的限制,不对齐的写入会导致额外的控制部件的开销,吃力不讨好,因此指令集设计就干脆不提供这样的指令。

内存分配不用锁可以吗?

  • 因为还没有介绍其它的处理并发的机制,目前不用锁相当于CPU之间没有共享资源。
  • 那么CPU之间没有共享内存可行吗,这好像从根源上避免了并发问题,但是这是本末倒置,因为有共享资源的需要所以才有并发问题,不能因为有问题所以把需求给砍了。
  • 在CPU之间没有共享内存资源的情况下,可能会导致一个CPU资源内存用完了,而另一个CPU还有很多内存这样“饿死”的情况发生,除非特别说明,否则所有资源都要求 CPU 之间共享。

关于qemu的说明

qemu使用完后请记得先 Ctrl+A,然后 x 将其关掉

如何开启 debug 调试?

build 文件夹下输入命令 make qemu-debug 就可以让 qemu 在debug模式下运行,启动新的窗口(或者终端),在 build 目录下运行 make debug 即可进入 gdb 调试命令行。

为什么去除掉 arch_stop_cpu 会输出4个a?(lab1)

因为在lab1中, main函数带有 NO_RETURN 标识,而此时 if 条件判断一旦为否 main函数就会返回,可能就是为了不让 main 函数返回,编译器会把其中的 if 判断语句优化掉

为啥要将 bss 段初始化为0?能不能在define early init做这个,为什么?NO_BSS段是做什么用的?

  1. bss是指链接器产生的未初始化数据段,链接器将程序的未初始化的数据和初始化为0的数据放在这里,运行时再由(操作)系统初始化并分配实际的内存空间,这样可以节约空间。而我们运行的就是操作系统,此前没有其它程序会帮我们初始化,所以需要我们自己初始化,因为初始化为0的数据也会再这里,所以我们的系统运行时需要先把bss段清零,防止相关的变量的初始值与定义时不符。
  2. 如果这么做的话,后续如果 early_init 中的函数使用了初始化为0的变量可能就会出错,因为同在early_init 中的初始化函数的运行顺序是没有保证的(顺序由链接器决定,不建议关注)
  3. NO_BSS 指定变量不会出现在 bss 段中

_start依旧后续的代码运行的实际物理地址是 0x80000,而链接器指定这块的地址是 0xffff000000080000,为什么仍能运行?(lab1)

  1. 在开启内核页表之后:高位为 0xffff 的地址会在 MMU (Memory Management Unit)的翻译后再取地址(比如 0xffff000000080000会映射到0x80000),因此这时的代码和链接器指定的地址正好相吻合;
  2. 在内核页表开启之前:因为内核页表是在 _start 中开启的,那么在内核页表开启之前是怎么运行的呢?

通过debug,利用 display /10i $pc 打印10条汇编指令,我们发现包括 main 在内的各种地址标识的绝对地址都是 0xffff开头,如果在开启页表前就直接跳转到这些函数入口肯定是无效的。

ldr    x1, =<name>   #x1 存入 name 的链接时地址
ldr    x2, =12345    #x2 存入 12345
_start: 0xffff000000080000
entry: 0xffff00000008003c
main: 0xffff000000080118

那么是怎么跳转到对应的地址的呢?

写start.S的时候刻意在开启虚拟地址之前把代码都写成PIE(Position Independent Executables),所以虽然实际PC不是链接器中声明的PC,仍然能执行;

adr    x0, <name>  #x0存入 name 相对pc的实际地址
_start: 0x80000
entry: 0x8003c

等一系列初始化完成后,跳转到main的地址处(0xffff000000080118),该地址会被 MMU 翻译成0x80118物理地址处,然后开始取值运行。

关于字节对齐

*(u64*)p=0 这样的语法要求p是8字节对齐,虽然lab1中的edata和end满足要求,但是不能保证以后加了东西还满足要求

关于链接器符号的说明

符号代表那个位置的地址,符号的指针才是内容