必答题-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指令输出到屏幕上



计算机组成原理   PA      PA 计算机组成原理

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!