《ucore lab1 exercise1》實驗報告


資源

  1. ucore在線實驗指導書
  2. 我的ucore實驗代碼

題目:理解通過make生成執行文件的過程

  1. 列出本實驗各練習中對應的OS原理的知識點,並說明本實驗中的實現部分如何對應和體現了原理中的基本概念和關鍵知識點。

  2. 操作系統鏡像文件ucore.img是如何一步一步生成的?(需要比較詳細地解釋Makefile中每一條相關命令和命令參數的含義,以及說明命令導致的結果)

  3. 一個被系統認為是符合規范的硬盤主引導扇區的特征是什么?

解答

題目1的解答

等完成lab1后再回頭總結。

題目2的解答

首先具體分析Makefile的執行流程,然后再回答題目所問的ucore.img的生成過程。

設置環境變量

第1~139行大部分是設置環境變量、編譯選項等,其中關鍵是第117行和第136行,分別設置了libs和kern目錄下的obj文件名,兩者合並即為$(KOBJS)。

第117行:生成libs目錄下的obj文件名
  1. 第117行語句是$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,),可見是調用了add_files_cc函數,輸入參數有2個,第2個是libs(目錄名),第1個是調用另一個函數listf_cc的返回值

  2. listf_cc函數的定義為listf_cc = $(call listf,$(1),$(CTYPE)),可見listf_cc又調用了listf函數,調用時傳入的第1個參數為$(1) = $(LIBDIR) = libs,第2個參數為$(CTYPE) = c S

  3. listf函數的定義為listf = $(filter $(if $(2),$(addprefix %.,$(2)),%), $(wildcard $(addsuffix $(SLASH)*,$(1)))),將輸入參數代入得:listf = $(filter %.c %.S, libs/*),可見此處調用listf的返回結果為libs目錄下的所有.c和.S文件。由於lab1的libs目錄下只有.h和.c文件,因此最終返回.c文件。

  4. 這時,第117行語句可化簡為add_files_cc(libs/*.c, libs)

  5. add_files_cc的定義為add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4)),結合4可化簡為add_files(libs/*.c, gcc, $(CFLAGS), libs)

  6. add_files的定義為add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5))),而do_add_files_to_packet的定義為:

define do_add_files_to_packet
__temp_packet__ := $(call packetname,$(4))
ifeq ($$(origin $$(__temp_packet__)),undefined)
$$(__temp_packet__) :=
endif
__temp_objs__ := $(call toobj,$(1),$(5))
$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(5))))
$$(__temp_packet__) += $$(__temp_objs__)
endef
  1. packetname的定義為packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX)),其中$(OBJPREFIX)=__objs_,而$(1)=libs,因此__temp_packet_ = __objs_libs

  2. toobj的定義為toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)), $(addsuffix .o,$(basename $(1)))),其中$(OBJDIR)=obj, $(SLASH)=/,而輸入參數為$(1)=libs/*.c, $(5)='',因此__temp_objs_ = obj/libs/*.o

  3. 綜上,第117行的最終效果是__objs_libs = obj/libs/**/*.o

第136行:生成kern目錄下的obj文件名

生成過程與第117行類似,不再贅述。第136行的實際效果是__objs_kernel = obj/kern/**/*.o

生成kernel文件

第140~151行是生成kernel文件。由於腳本中的語句往往會引用到前面定義的變量,而前面定義的變量又可能引用到其他文件的變量,為便於分析,下面會將所有相關的語句集中放在一起。

  1. 第141行設置生成的kernel目標名為bin/kernel
kernel = $(call totarget,kernel)
totarget = $(addprefix $(BINDIR)$(SLASH),$(1))
BINDIR	:= bin
SLASH	:= /
  1. 第143行指出kernel目標文件需要依賴tools/kernel.ld文件,而kernel.ld文件是一個鏈接腳本,其中設置了輸出的目標文件的入口地址及各個段的一些屬性,包括各個段是由輸入文件的哪些段組成、各個段的起始地址等。
$(kernel): tools/kernel.ld
  1. 第145行指出kernel目標文件依賴的obj文件。最終效果為KOBJS=obj/libs/*.o obj/kern/**/*.o
$(kernel): $(KOBJS)
KOBJS   = $(call read_packet,kernel libs)
read_packet = $(foreach p,$(call packetname,$(1)),$($(p)))
packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX))
OBJPREFIX	:= __objs_
  1. 第146行打印kernel目標文件名
@echo + ld $@
// output: `+ ld bin/kernel`
  1. 第147行是鏈接所有生成的obj文件得到kernel文件
$(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS)
V       := @
LD      := $(GCCPREFIX)ld
// GCCPREFIX = 'i386-elf-' or ''
// output: ld -m    elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel  obj/kern/init/init.o obj/kern/libs/stdio.o obj/kern/libs/readline.o obj/kern/debug/panic.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/picirq.o obj/kern/driver/intr.o obj/kern/trap/trap.o obj/kern/trap/vectors.o obj/kern/trap/trapentry.o obj/kern/mm/pmm.o  obj/libs/string.o obj/libs/printfmt.o
  1. 第148行是使用objdump工具對kernel目標文件反匯編,以便后續調試。首先toobj返回obj/kernel.o,然后cgtype返回obj/kernel.asm,所以第148行相當於執行objdump -S bin/kernel > obj/kernel.asm,objdump的-S選項是交替顯示將C源碼和匯編代碼。
@$(OBJDUMP) -S $@ > $(call asmfile,kernel)
OBJDUMP := $(GCCPREFIX)objdump
// GCCPREFIX = 'i386-elf-' or ''
asmfile = $(call cgtype,$(call toobj,$(1)),o,asm)
cgtype = $(patsubst %.$(2),%.$(3),$(1))
toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)),\
		$(addsuffix .o,$(basename $(1))))
OBJDIR	:= obj
SLASH	:= /
  1. 第149行是使用objdump工具來解析kernel目標文件得到符號表。如果不關注格式處理,實際執行語句等效於objdump -t bin/kernel > obj/kernel.sym
@$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call sy    mfile,kernel)
OBJDUMP := $(GCCPREFIX)objdump
SED		:= sed
symfile = $(call cgtype,$(call toobj,$(1)),o,sym)
  1. 第151行是調用create_target函數:$(call create_target,kernel),而create_target的定義為create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5))),可見create_target只是進一步調用了do_create_target的函數:do_create_target(kernel)

  2. do_create_target的定義如下。由於只有一個輸入參數,temp_objs為空字符串,並且走的是else分支,因此感覺這里的函數調用是直接返回,啥也沒干?

// add packets and objs to target (target, #packes, #objs[, cc, flags])
define do_create_target
__temp_target__ = $(call totarget,$(1))
__temp_objs__ = $$(foreach p,$(call packetname,$(2)),$$($$(p))) $(3)
TARGETS += $$(__temp_target__)
ifneq ($(4),)
$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@)
	$(V)$(4) $(5) $$^ -o $$@
else
$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@)
endif
endef

生成bootblock

  1. 第156行:bootfiles = $(call listf_cc,boot),前面已經知道listf_cc函數是過濾出對應目錄下的.c和.S文件,因此bootfiles=boot/\*.c boot/\*.S

  2. 第157行:從字面含義也可以看出是編譯bootfiles生成.o文件。

$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))
cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4)))
define do_cc_compile
$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(4))))
endef
  1. cc_template的定義為
// cc compile template, generate rule for dep, obj: (file, cc[, flags, dir])
define cc_template
$$(call todep,$(1),$(4)): $(1) | $$$$(dir $$$$@)
	@$(2) -I$$(dir $(1)) $(3) -MM $$< -MT "$$(patsubst %.d,%.o,$$@) $$@"> $$@
$$(call toobj,$(1),$(4)): $(1) | $$$$(dir $$$$@)
	@echo + cc $$<
	$(V)$(2) -I$$(dir $(1)) $(3) -c $$< -o $$@
ALLOBJS += $$(call toobj,$(1),$(4))
endef
  1. 第159行:bootblock = $(call totarget,bootblock),前面已經知道totarget函數是給輸入參數增加前綴"bin/",因此bootblock="bin/bootblock"

  2. 第161行聲明bin/bootblock依賴於obj/boot/*.o 和bin/sign文件:$(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign)。注意toobj函數的作用是給輸入參數增加前綴obj/,並將文件后綴名改為.o

  3. 第163行鏈接所有.o文件以生成obj/bootblock.o:$(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock)。這里要注意鏈接選項中的-e start -Ttext 0x7C00,大致意思是設置bootblock的入口地址為start標簽,而且start標簽的地址為0x7C00.(未理解-Ttext的含義)

  4. 第164行反匯編obj/bootblock.o文件得到obj/bootblock.asm文件:@$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock)

  5. 第165行使用objcopy將obj/bootblock.o轉換生成obj/bootblock.out文件,其中-S表示轉換時去掉重定位和符號信息:@$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock)

  6. 第166行使用bin/sign工具將obj/bootblock.out轉換生成bin/bootblock目標文件:@$(call totarget,sign) $(call outfile,bootblock) $(bootblock),從tools/sign.c代碼中可知sign工具其實只做了一件事情:將輸入文件拷貝到輸出文件,控制輸出文件的大小為512字節,並將最后兩個字節設置為0x55AA(也就是ELF文件的magic number)

  7. 第168行調用了create_target函數$(call create_target,bootblock),根據上文的分析,由於只有一個輸入參數,此處函數調用應該也是直接返回,啥也沒干。

生成sign工具

  1. 第173行調用了add_files_host函數:$(call add_files_host,tools/sign.c,sign,sign)

  2. add_files_host的定義為add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3)),可見是調用了add_files函數:add_files(tools/sign.c, gcc, $(HOSTCFLAGS), sign, sign)

  3. add_files的定義為add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5))),根據前面的分析,do_add_files_to_packet的作用是生成obj文件,因此這里調用add_files的作用是設置\_\_objs\_sign = obj/sign/tools/sign.o

  4. 第174行調用了create_target_host函數:$(call create_target_host,sign,sign)

  5. create_target_host的定義為create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS)),可見是調用了create_target函數:create_target(sign, sign, gcc, $(HOSTCFLAGS))

  6. create_target的定義為create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5)))。根據前面的分析,do_create_target的作用是生成目標文件,因此這里調用create_target的作用是生成obj/sign/tools/sign.o

生成ucore.img

  1. 第179行設置了ucore.img的目標名:UCOREIMG := $(call totarget,ucore.img),前面已經知道totarget的作用是添加bin/前綴,因此UCOREIMG = bin/ucore.img

  2. 第181行指出bin/ucore.img依賴於bin/kernel和bin/bootblock: $(UCOREIMG): $(kernel) $(bootblock)

  3. 第182行:$(V)dd if=/dev/zero of=$@ count=10000。這里為bin/ucore.img分配10000個block的內存空間,並全部初始化為0。由於沒指定block的大小,因此為默認值512字節,則總大小為5000M,約5G。

備注:在類UNIX 操作系統中, /dev/zero 是一個特殊的文件,當你讀它的時候,它會提供無限的空字符(NULL, ASCII NUL, 0x00)。其中的一個典型用法是用它提供的字符流來覆蓋信息,另一個常見用法是產生一個特定大小的空白文件。BSD就是通過mmap把/dev/zero映射到虛地址空間實現共享內存的。可以使用mmap將/dev/zero映射到一個虛擬的內存空間,這個操作的效果等同於使用一段匿名的內存(沒有和任何文件相關)。

  1. 第183行:$(V)dd if=$(bootblock) of=$@ conv=notrunc。這里將bin/bootblock復制到bin/ucore.img

  2. 第184行:$(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc。繼續將bin/kernel復制到bin/ucore.img,這里使用了選項seek=1,意思是:復制時跳過bin/ucore.img的第一個block,從第2個block也就是第512個字節后面開始拷貝bin/kernel的內容。原因是顯然的:ucore.img的第1個block已經用來保存bootblock的內容了。

  3. 第186行:$(call create_target,ucore.img),由於只有一個輸入參數,因此這里會直接返回。

總結ucore.img的生成過程

  1. 編譯libs和kern目錄下所有的.c和.S文件,生成.o文件,並鏈接得到bin/kernel文件

  2. 編譯boot目錄下所有的.c和.S文件,生成.o文件,並鏈接得到bin/bootblock.out文件

  3. 編譯tools/sign.c文件,得到bin/sign文件

  4. 利用bin/sign工具將bin/bootblock.out文件轉化為512字節的bin/bootblock文件,並將bin/bootblock的最后兩個字節設置為0x55AA

  5. 為bin/ucore.img分配5000MB的內存空間,並將bin/bootblock復制到bin/ucore.img的第一個block,緊接着將bin/kernel復制到bin/ucore.img第二個block開始的位置

題目3的解答

問題: 一個被系統認為是符合規范的硬盤主引導扇區的特征是什么?
答:

  1. 大小為512字節
  2. 最后兩個字節為0x55AA


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM