必答题-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.c程序是怎么被编译链接成一个可执行文件的
首先我们是在nanos-lite文件中执行命令make ARCH=x86-nemu run
,make程序进入nanos-lite/Makefile文件中寻找run伪目标执行,nanos-lite/Makefile中并没有run伪目标,但是由于nanos-lite/Makefile通过命令:
include $(AM_HOME)/Makefile.app)
将nexus-am/Makefile.app文件包含在了nanos-lite/Makefile文件中,所以我们可以在nexus-am/Makefile中找到run伪目标.
Note! 这里只是将nexus-am/Makefile.app包含在nanos-lite/Makefile文件中而不是跳转到nexus-am/Makefile文件中执行!所以当前目录还是在nanos-lite
现在让我们把目光转向nexus-am/Makefil.app文件
在nexus-am/Makefile.app中我们找到run伪目标
default: image
$(OBJS): $(PREBUILD)
image: $(OBJS) am $(LIBS) prompt
prompt: $(OBJS) am $(LIBS)
run: default
prompt:
@echo \# Creating binary image [$(ARCH))]
但这里的run伪目标并不是完整的,nexus-am/Makefile.app中通过命令
include $(AM_HOME)/Makefile.check)
将nexus-am/Makefile.check文件包含到nanos-lite/Makefile中,而nexus-am/Makefile.check又通过命令
#ARCH=x86-nemu,这是我们使用make命令传入的参数
include $(AM_HOME)/am/arch/$(ARCH).mk
将nexus-am/am/arch/x86-nemu.mk文件包含到nanos-lite/Makefile中,同样nexus-am/am/arch/x86-nemu.mk也通过命令
include $(AM_HOME)/am/arch/platform/nemu.mk
将nexus-am/am/arch/platform/nemu.mk包含到nanos-lite/Makefile中而nexus-am/am/arch/platform/nemu.mk中也包含着run伪命令.
run:
$(MAKE) -C $(NEMU_HOME) ISA=$(ISA) run ARGS="$(NEMU_ARGS))"
所以完整的run伪目标可以看成这样
run: default
$(MAKE) -C $(NEMU_HOME) ISA=$(ISA) run ARGS="$(NEMU_ARGS))"
default也是一个伪目标,所以defalut也会被决议执行由于defalut: image
,image同样也是伪目标所以image会被决议执行.image: $(OBJS) am $(LIBS) prompt
,和run一样image也不是完整的伪目标,而另一部分的image同样被存在nexus-am/am/arch/platform/nemu.mk中.
所以完整的image伪目标可以被看作:
image: $(OBJS) am $(LIBS) prompt
@echo + LD "->" $(BINARY_REL).elf
@$(LD) $(LDFLAGS) --gc-sections -T $(LD_SCRIPT) -e _start -o $(BINARY).elf $(LINK_FILES)
@$(OBJDUMP) -d $(BINARY).elf > $(BINARY).txt
@echo + OBJCOPY "->" $(BINARY_REL).bin
@$(OBJCOPY) -S --set-section-flags .bss=alloc,contents -O binary $(BINARY).elf $(BINARY).bin
make命令会依次执行$(OBJS),am,$(LIBS),prompt等伪目标然后在执行image伪目标下面的shell指令.
让我们稍微整理一下make执行的顺序
make ARCH=x86-nemu run命令下达后,make会在先找到run伪目标
run: default
$(MAKE) -C $(NEMU_HOME) ISA=$(ISA) run ARGS="$(NEMU_ARGS))"
执行defalut伪目标
default: image
执行image伪目标
image: $(OBJS) am $(LIBS) prompt
@echo + LD "->" $(BINARY_REL).elf
@$(LD) $(LDFLAGS) --gc-sections -T $(LD_SCRIPT) -e _start -o $(BINARY).elf $(LINK_FILES)
@$(OBJDUMP) -d $(BINARY).elf > $(BINARY).txt
@echo + OBJCOPY "->" $(BINARY_REL).bin
@$(OBJCOPY) -S --set-section-flags .bss=alloc,contents -O binary $(BINARY).elf $(BINARY).bin
依次执行$(OBJS) am $(LIBS) prompt,会生成后缀为.o和后缀为.a的一系列文件$(LINK_FILES),这里就不再深入展开,具体可以看源代码
执行完毕后会执行image下的shell命令,将这系列$(LINK_FILES)链接成$(BINARY).elf文件,最后通过$(OBJCOPY)命令将$(BINARY).elf转化为$(BINARY).bin文件.
最后执行run伪目标下的shell命令
$(MAKE) -C $(NEMU_HOME) ISA=$(ISA) run ARGS="$(NEMU_ARGS)"
这条命令表示进入$(NEMU_HOME)目录执行make run命令.Note!这里是进入$(NEMU_HOME)执行make
在nemu/Makefile文件中执行编译链接完成之后会执行一条命令
$(NEMU_EXEC)
这条命令实际就是
#这里$(IMG)为空忽略,$(ARGS)为我们上面run伪目标shell指令中传入的参数$(NEMU_ARGS)
nemu/build/x86-nemu $(ARGS) $(IMG)
NEMU_ARGS = -b $(MAINARGS) -l $(shell dirname $(BINARY))/nemu-log.txt $(BINARY).bin)
这里NEMU_ARGS就相当与int main(int argc, char *argv[])
的argv[]参数.
NEMU_ARGS会被nemu中的parse_args(argc, argv);
函数解析,最后$(BINARY).bin会被long img_size = load_img();
函数装入模拟器内存中,最后函数init_isa();
更新pc指针,最后模拟器取指执行
hello.c中的每一个字符是怎么被输出到终端上的?
printf()->write()->write_r ()->_write()
->_syscall\(SYSwrite,fd,buf,count)->am_vecsys
->am_asm_trap-> call __am_irq_handle->user_handler()user_handler是函数指针,指向do_event()
->do_syscall()->sys_write()->fs_write()->invalid_write()->_put()->outb(SERIAL_PORT, ch)
->asm volatile (“outb %%al, %%dx” : : “a”(data), “d”((uint16_t)port))->调用nemu中的out指令输出到屏幕上
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!