Ucore-1 准备环境 Linux环境:VMBox上建一个Ubuntu的虚拟机
qemu硬件环境模拟器:
1 sudo apt-get install qemu-system
下载lab代码:
1 git clone https://github.com/kiukotsu/ucore
labcodes中包含了各lab的原始代码,labcodes_answer中包含了对应lab的答案。
初次运行ucore 在lab1中执行make命令:
生成bootblock,kernel,sign和ucore.img文件
执行make qemu即可运行ucore
BIOS bios,基本输入输出系统,是一个固化在主板上的软件,计算机加电后,会先执行BIOS处的代码,完成最基础硬件的自检和初始化。在这之后,BIOS会选择一个启动设备(例如软盘、硬盘、光盘等),并且读取该设备的第一扇区(即主引导扇区或启动扇区)到内存一个特定的地址0x7c00处,然后CPU控制权会转移到那个地址继续执行。至此进一步的工作交给了ucore的bootloader。
bootloader bootloader需要完成以下几项工作:
切换到保护模式,启用分段机制
读磁盘中ELF执行文件格式的ucore操作系统到内存
显示字符串信息
把控制权交给ucore操作系统
了解处理器硬件 ucore目前支持的硬件环境是基于Intel 80386以上的计算机系统
实模式:这是个人计算机早期的8086处理器采用的一种简单运行模式。80386加电启动后处于实模式运行状态,在这种状态下软件可访问的物理内存空间不能超过1MB,且无法发挥Intel 80386以上级别的32位CPU的4GB内存管理能力。实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,操作系统和用户程序并没有区别对待,而且每一个指针都是指向实际的物理地址。这样用户程序的一个指针如果指向了操作系统区域或其他用户程序区域,并修改了内容,那么其后果就很可能是灾难性的。
保护模式:保护模式的一个主要目标是确保应用程序无法对操作系统进行破坏。实际上,80386就是通过在实模式下初始化控制寄存器(如GDTR,LDTR,IDTR与TR等管理寄存器)以及页表,然后再通过设置CR0寄存器使其中的保护模式使能位置位,从而进入到80386的保护模式。当80386工作在保护模式下的时候,其所有的32根地址线都可供寻址,物理寻址空间高达4GB。在保护模式下,支持内存分页机制,提供了对虚拟内存的良好支持。保护模式下80386支持多任务,还支持优先级机制,不同的程序可以运行在不同的特权级上。特权级一共分0~3四个级别,操作系统运行在最高的特权级0上,应用程序则运行在比较低的级别上;配合良好的检查机制后,既可以在任务间实现数据的安全共享也可以很好地隔离各个任务。
保护模式和分段机制 下面将makefile代码逐行讲解:
定义变量:
1 2 3 4 5 PROJ := challenge EMPTY := SPACE := $(EMPTY) $(EMPTY) SLASH := / V := @
GCCPREFIX:定义交叉编译器工具链的前缀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 ifndef GCCPREFIXGCCPREFIX := $(shell if i386-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \ then echo 'i386-elf-'; \ elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \ then echo ''; \ else echo "***" 1>&2; \ echo "*** Error: Couldn't find an i386-elf version of GCC/binutils." 1>&2; \ echo "*** Is the directory with i386-elf-gcc in your PATH?" 1>&2; \ echo "*** If your i386-elf toolchain is installed with a command" 1>&2; \ echo "*** prefix other than 'i386-elf-', set your GCCPREFIX" 1>&2; \ echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ echo "*** To turn off this error , run 'gmake GCCPREFIX= ...'." 1>&2; \ echo "***" 1>&2; exit 1; fi) endif ifndef QEMUQEMU := $(shell if which qemu-system-i386 > /dev/null; \ then echo 'qemu-system-i386'; exit; \ elif which i386-elf-qemu > /dev/null; \ then echo 'i386-elf-qemu'; exit; \ elif which qemu > /dev/null; \ then echo 'qemu'; exit; \ else \ echo "***" 1>&2; \ echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ echo "***" 1>&2; exit 1; fi) endif .SUFFIXES: .c .S .h .DELETE_ON_ERROR:
根据是否指定USELLVM确定编译器和相关选项:CC指定编译器(gcc/clang)
CFLAGS指定选项:
-fno-builtin:不要识别标准库函数,使用用户自定义的函数
-Wall:启用所有编译器警告
-ggdb:可执行文件包含调试信息
-m32:目标架构设置为32位模式
-gstabs:指定调试信息的格式
-nostdinc:编译器编译过程不要包含标准系统头文件
$(DEFS):DEFS是一个变量,这里并没有定义,用户可以将自己想加的选项放到里面
$(shell xxx):以shell命令执行xxx的内容,并将结果作为字符串返回。
-fno-stack-protector:禁止堆栈保护机制
-E:对源代码进行预处理
-x c:指定输入文件类型为c语言
/dev/null:一个特殊的设备文件,所有写入它的数据都会被丢弃
>/dev/null:将标准输出重定向到dev/null,即会被立即丢弃
2>&1:将标准错误重定向到标准输出,这样错误信息也被丢弃了
&& echo -fno-stack-protector:若上述被成功执行了则输出-fno-stack-protector(短路求值)
llvm的部分同理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ifndef USELLVMHOSTCC := gcc HOSTCFLAGS := -g -Wall -O2 CC := $(GCCPREFIX) gcc CFLAGS := -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc $(DEFS) CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) else HOSTCC := clang HOSTCFLAGS := -g -Wall -O2 CC := clang CFLAGS := -fno-builtin -Wall -g -m32 -mno-sse -nostdinc $(DEFS) CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) endif
变量CTYPE存储“c S”
链接器LD:$(GCCPREFIX)ld
LDFLAGS:链接器的选项
定义OBJCOPY,OBJDUMP,COPY等常用命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 CTYPE := c S LD := $(GCCPREFIX) ld LDFLAGS := -m $(shell $(LD) -V | grep elf_i386 2>/dev/null) LDFLAGS += -nostdlib OBJCOPY := $(GCCPREFIX) objcopy OBJDUMP := $(GCCPREFIX) objdump COPY := cp MKDIR := mkdir -p MV := mv RM := rm -f AWK := awk SED := sed SH := sh TR := tr TOUCH := touch -c OBJDIR := obj BINDIR := bin ALLOBJS := ALLDEPS := TARGETS :=
引入function.mk,里面编写了很多函数
1 include tools/function.mk
listf_cc:调用listf函数,$(1)传入文件夹列表,CTYPE指定了%.c和%.S,即返回这个文件夹下所有的.c和.S文件
add_files_cc:将一组C语言源文件编译成目标文件,并返回最终的目标文件列表。
create_target_cc:传入目标文件名、包含源代码文件的packet数量和要编译的源代码文件数量,生成目标文件到对应的packet
add_files_host和create_target_host同理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 listf_cc = $(call listf,$(1) ,$(CTYPE) ) add_files_cc = $(call add_files,$(1) ,$(CC) ,$(CFLAGS) $(3),$(2),$(4)) create_target_cc = $(call create_target,$(1) ,$(2),$(3),$(CC) ,$(CFLAGS) ) add_files_host = $(call add_files,$(1) ,$(HOSTCC) ,$(HOSTCFLAGS) ,$(2),$(3)) create_target_host = $(call create_target,$(1) ,$(2),$(3),$(HOSTCC) ,$(HOSTCFLAGS) ) cgtype = $(patsubst %.$(2) ,%.$(3),$(1)) objfile = $(call toobj,$(1) ) asmfile = $(call cgtype,$(call toobj,$(1) ),o,asm) outfile = $(call cgtype,$(call toobj,$(1) ),o,out) symfile = $(call cgtype,$(call toobj,$(1) ),o,sym) match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)" ,"^" $$(i) "$$" )){exit 1;}}}'; echo $$? )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> # include kernel/user INCLUDE += libs/ CFLAGS += $(addprefix -I,$(INCLUDE)) LIBDIR += libs $(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,) # ------------------------------------------------------------------- # kernel KINCLUDE += kern/debug/ \ kern/driver/ \ kern/trap/ \ kern/mm/ KSRCDIR += kern/init \ kern/libs \ kern/debug \ kern/driver \ kern/trap \ kern/mm KCFLAGS += $(addprefix -I,$(KINCLUDE)) $(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS)) KOBJS = $(call read_packet,kernel libs) # create kernel target kernel = $(call totarget,kernel) $(kernel): tools/kernel.ld $(kernel): $(KOBJS) @echo + ld $@ $(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS) @$(OBJDUMP) -S $@ > $(call asmfile,kernel) @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel) $(call create_target,kernel) # ------------------------------------------------------------------- # create bootblock bootfiles = $(call listf_cc,boot) $(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)) bootblock = $(call totarget,bootblock) $(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign) @echo + ld $@ $(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock) @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) @$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock) @$(call totarget,sign) $(call outfile,bootblock) $(bootblock) $(call create_target,bootblock) # ------------------------------------------------------------------- # create 'sign' tools $(call add_files_host,tools/sign.c,sign,sign) $(call create_target_host,sign,sign) # ------------------------------------------------------------------- # create ucore.img UCOREIMG := $(call totarget,ucore.img) $(UCOREIMG): $(kernel) $(bootblock) $(V)dd if=/dev/zero of=$@ count=10000 $(V)dd if=$(bootblock) of=$@ conv=notrunc $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc $(call create_target,ucore.img) # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> $(call finish_all) IGNORE_ALLDEPS = clean \ dist-clean \ grade \ touch \ print-.+ \ handin ifeq ($(call match,$(MAKECMDGOALS),$(IGNORE_ALLDEPS)),0) -include $(ALLDEPS) endif # files for grade script TARGETS: $(TARGETS) .DEFAULT_GOAL := TARGETS .PHONY: qemu qemu-nox debug debug-nox qemu-mon: $(UCOREIMG) $(V)$(QEMU) -no-reboot -monitor stdio -hda $< -serial null qemu: $(UCOREIMG) $(V)$(QEMU) -no-reboot -parallel stdio -hda $< -serial null log: $(UCOREIMG) $(V)$(QEMU) -no-reboot -d int,cpu_reset -D q.log -parallel stdio -hda $< -serial null qemu-nox: $(UCOREIMG) $(V)$(QEMU) -no-reboot -serial mon:stdio -hda $< -nographic TERMINAL :=gnome-terminal debug: $(UCOREIMG) $(V)$(QEMU) -S -s -parallel stdio -hda $< -serial null & $(V)sleep 2 $(V)$(TERMINAL) -e "gdb -q -tui -x tools/gdbinit" debug-nox: $(UCOREIMG) $(V)$(QEMU) -S -s -serial mon:stdio -hda $< -nographic & $(V)sleep 2 $(V)$(TERMINAL) -e "gdb -q -x tools/gdbinit" .PHONY: grade touch GRADE_GDB_IN := .gdb.in GRADE_QEMU_OUT := .qemu.out HANDIN := proj$(PROJ)-handin.tar.gz TOUCH_FILES := kern/trap/trap.c MAKEOPTS := --quiet --no-print-directory grade: $(V)$(MAKE) $(MAKEOPTS) clean $(V)$(SH) tools/grade.sh touch: $(V)$(foreach f,$(TOUCH_FILES),$(TOUCH) $(f)) print-%: @echo $($(shell echo $(patsubst print-%,%,$@) | $(TR) [a-z] [A-Z])) .PHONY: clean dist-clean handin packall tags clean: $(V)$(RM) $(GRADE_GDB_IN) $(GRADE_QEMU_OUT) cscope* tags -$(RM) -r $(OBJDIR) $(BINDIR) dist-clean: clean -$(RM) $(HANDIN) handin: packall @echo Please visit http://learn.tsinghua.edu.cn and upload $(HANDIN). Thanks! packall: clean @$(RM) -f $(HANDIN) @tar -czf $(HANDIN) `find . -type f -o -type d | grep -v '^\.*$$' | grep -vF '$(HANDIN)'` tags: @echo TAGS ALL $(V)rm -f cscope.files cscope.in.out cscope.out cscope.po.out tags $(V)find . -type f -name "*.[chS]" >cscope.files $(V)cscope -bq $(V)ctags -L cscope.files