多思考,多帮助他人
- 推荐:gitlab issue、github issue
- elearning?
由于资金的原因slack可能要功成身退了
- 因为这一阶段的初始化可能就会用到动态内存分配了,需要在之前就实现动态内存的分配(尽量早实现这个功能,最早就只能放在early_init中了)
阶段 | 可能会用到的功能 | 需要实现的功能 |
---|---|---|
early_init | 静态变量 | 动态内存分配 |
init | 申请内存 | 进程树和调度器的初始化 |
rest_init | / | / |
以下说明可能不准确
- 内核也使用页表:对现代操作系统而言,进程页表是一定需要的;而对于内核而言,是否有内核页表影响不大,因为确实可以直接访问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使用完后请记得先 Ctrl+A
,然后 x
将其关掉
在 build
文件夹下输入命令 make qemu-debug
就可以让 qemu 在debug模式下运行,启动新的窗口(或者终端),在 build 目录下运行 make debug
即可进入 gdb 调试命令行。
因为在lab1中, main函数带有 NO_RETURN 标识,而此时 if 条件判断一旦为否 main函数就会返回,可能就是为了不让 main 函数返回,编译器会把其中的 if 判断语句优化掉
- bss是指链接器产生的未初始化数据段,链接器将程序的未初始化的数据和初始化为0的数据放在这里,运行时再由(操作)系统初始化并分配实际的内存空间,这样可以节约空间。而我们运行的就是操作系统,此前没有其它程序会帮我们初始化,所以需要我们自己初始化,因为初始化为0的数据也会再这里,所以我们的系统运行时需要先把bss段清零,防止相关的变量的初始值与定义时不符。
- 如果这么做的话,后续如果 early_init 中的函数使用了初始化为0的变量可能就会出错,因为同在early_init 中的初始化函数的运行顺序是没有保证的(顺序由链接器决定,不建议关注)
- NO_BSS 指定变量不会出现在 bss 段中
- 在开启内核页表之后:高位为 0xffff 的地址会在 MMU (Memory Management Unit)的翻译后再取地址(比如 0xffff000000080000会映射到0x80000),因此这时的代码和链接器指定的地址正好相吻合;
- 在内核页表开启之前:因为内核页表是在 _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满足要求,但是不能保证以后加了东西还满足要求
符号代表那个位置的地址,符号的指针才是内容