Son aktif 1756095581

Revizyon 1dcb153aeb67e8c972c68637a9780870b6307fc4

PA3.2.md Ham

必答题(需要在实验报告中回答) - hello程序是什么, 它从而何来, 要到哪里去

到此为止, PA中的所有组件已经全部亮相, 整个计算机系统也开始趋于完整. 你也已经在这个自己创造的计算机系统上跑起了hello这个第一个还说得过去的用户程序 (dummy是给大家热身用的, 不算), 好消息是, 我们已经距离运行仙剑奇侠传不远了(下一个阶段就是啦).

不过按照PA的传统, 光是跑起来还是不够的, 你还要明白它究竟怎么跑起来才行. 于是来回答这道必答题吧:

我们知道navy-apps/tests/hello/hello.c只是一个C源文件, 它会被编译链接成一个ELF文件. 那么, hello程序一开始在哪里? 它是怎么出现内存中的? 为什么会出现在目前的内存位置? 它的第一条指令在哪里? 究竟是怎么执行到它的第一条指令的? hello程序在不断地打印字符串, 每一个字符又是经历了什么才会最终出现在终端上?

上面一口气问了很多问题, 我们想说的是, 这其中蕴含着非常多需要你理解的细节. 我们希望你能够认真整理其中涉及的每一行代码, 然后用自己的语言融会贯通地把这个过程的理解描述清楚, 而不是机械地分点回答这几个问题.

同样地, 上一阶段的必答题"理解穿越时空的旅程"也已经涵盖了一部分内容, 你可以把它的回答包含进来, 但需要描述清楚有差异的地方. 另外, C库中printf()write()的过程比较繁琐, 而且也不属于PA的主线内容, 这一部分不必展开回答. 而且你也已经在PA2中实现了自己的printf()了, 相信你也不难理解字符串格式化的过程. 如果你对Newlib的实现感兴趣, 你也可以RTFSC.

总之, 扣除C库中printf()write()转换的部分, 剩下的代码就是你应该理解透彻的了. 于是, 努力去理解每一行代码吧!

答:

要理解hello从何而来,自然是从它的makefile开始。hello的makefile include了navy-apps下的大makefile, 其中

## 4. ISA-Specific Configurations

### Paste in ISA-specific configurations (e.g., from `scripts/x86.mk`)
-include $(NAVY_HOME)/scripts/$(ISA).mk

又include了ISA相关的makefile, 继续找到$(NAVY_HOME)/scripts/riscv32.mk以及$(NAVY_HOME)/scripts/riscv/common.mk可以看到这几行

LNK_ADDR = $(if $(VME), 0x40000000, 0x83000000)
CFLAGS  += -fno-pic -march=rv64g -mcmodel=medany
LDFLAGS += --no-relax -Ttext-segment $(LNK_ADDR)

目前还没做到VME, 所以LNK_ADDR为0x83000000,LDFLAGS是传递给链接器(ld)的参数,-Ttext-segment指定程序代码段(.text)运行时的地址。在编译出的elf文件中,可以看到相应的地址:

$ riscv64-linux-gnu-readelf -a build/hello-riscv32
ELF Header: (omitted)

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        830000b4 0000b4 005e00 00  AX  0   0  4
  [ 2] .rodata           PROGBITS        83005eb4 005eb4 0003fd 00   A  0   0  4
  [ 3] .eh_frame         PROGBITS        830062b4 0062b4 000fd0 00   A  0   0  4
  [ 4] .data             PROGBITS        83008000 008000 000830 00  WA  0   0  8
  [ 5] .sdata            PROGBITS        83008830 008830 000068 00  WA  0   0  4
  [ 6] .sbss             NOBITS          83008898 008898 000018 00  WA  0   0  4
  [ 7] .bss              NOBITS          830088b0 008898 000028 00  WA  0   0  4
  [ 8] .comment          PROGBITS        00000000 008898 000012 01  MS  0   0  1
  [ 9] .riscv.attributes RISCV_ATTRIBUTE 00000000 0088aa 000061 00      0   0  1
  [10] .symtab           SYMTAB          00000000 00890c 001010 10     11 126  4
  [11] .strtab           STRTAB          00000000 00991c 0007ae 00      0   0  1
  [12] .shstrtab         STRTAB          00000000 00a0ca 000066 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  RISCV_ATTRIBUT 0x0088aa 0x00000000 0x00000000 0x00061 0x00000 R   0x1
  LOAD           0x000000 0x83000000 0x83000000 0x07284 0x07284 R E 0x1000
  LOAD           0x008000 0x83008000 0x83008000 0x00898 0x008d8 RW  0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10

编译出elf以后我把它复制到nanos下,命名为ramdisk.img

cp $NAVY_HOME/tests/hello/build/hello-riscv32 ~/ics2024/nanos-lite/build/ramdisk.img

nanos-like/src/resources.S中把这个ramdisk.img二进制包括进来,最后一起打包到编译出的nanos二进制文件中,并且定义了全局符号ramdisk_start, ramdisk_end用于记录img的起始和末尾地址。

nanos启动时先调用init_irq然后经过一通调用最后在am的cte中执行csrw指令把cte的__am_asm_trap函数地址写入中断向量寄存器。CPU(nemu)执行到syscall指令时即跳转到这个函数,该函数保存上下文后最终会调用nanos注册的do_event函数。

启动过程继续调用init_proc再调用naive_uload, 解析ramdisk.img的elf头,按Program Headers把要执行的二进制复制到0x83000000开始的位置,然后跳转到entry开始执行hello。

hello打印字符串时,调用libc(newlib)提供的printf函数,newlib内部最终会调用libos的syscall.c里面的_write函数,然后调用syscall也即ecall指令,并用寄存器传递参数。如上文所说最终跳转到do_event函数然后调用do_syscall函数,根据syscall number执行对应write操作,调用am提供的putch函数打印到终端。

由上文分析可见,navy-apps与nanos交互的interface是syscall, 相关支持库是newlib和libos. 而nanos和CPU(nemu)交互的interface是ISA, 相关支持库是abstract-machine及其组件。