# 運行 xv6 ## Makefile 解析 ```c= OBJS = \ bio.o\ console.o\ exec.o\ file.o\ fs.o\ ide.o\ ioapic.o\ kalloc.o\ kbd.o\ lapic.o\ main.o\ mp.o\ picirq.o\ pipe.o\ proc.o\ spinlock.o\ string.o\ swtch.o\ syscall.o\ sysfile.o\ sysproc.o\ timer.o\ trapasm.o\ trap.o\ uart.o\ vectors.o\ # Cross-compiling (e.g., on Mac OS X) TOOLPREFIX = /usr/bin/i386-jos-elf- # Using native tools (e.g., on X86 Linux) # TOOLPREFIX = CC = $(TOOLPREFIX)gcc AS = $(TOOLPREFIX)gas LD = $(TOOLPREFIX)ld OBJCOPY = $(TOOLPREFIX)objcopy OBJDUMP = $(TOOLPREFIX)objdump CFLAGS = -fno-builtin -O2 -Wall -MD -ggdb -m32 ``` - `-fno-builtin`:不使用 C 中的內建函數。 - `-O2`:`-O` 表示最佳化的程度,數字越大越好,但會增加編譯時間。 - `-Wall`:Warm all 的意思,打開所有的警告。 - `-MD`:生成 .d(directory),等同於 `-M -MF file`。 :::info `gcc -M file.c` 會將 file.c 有 include 的 .h (包含標準函式庫)關聯起來[^1],即輸出為: `file.o: file.c header.h stdio.h` `gcc -M -MF file.c` 將 `-M` 的輸出存入 file.d 裡。 **註**:`-M` 會自動帶 `-E`,如果使用 `-MD` 替代 `-M -MF` 時則不會帶 `-E`。 ::: - `-ggdb`:為 GDB 生成更多的 debug 資訊。 - `-m32`:生成 32 位元的程式碼。 [^1]:[Linux Makefile 生成 *.d 依赖文件及 gcc -M -MF -MP 等相关选项说明](https://blog.csdn.net/QQ1452008/article/details/50855810) ```c=+ CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) ASFLAGS = -m32 # FreeBSD ld wants ``elf_i386_fbsd'' LDFLAGS += -m $(shell $(LD) -V | grep elf_i386 2>/dev/null) ``` ```c=+ xv6.img: bootblock kernel fs.img dd if=/dev/zero of=xv6.img count=10000 dd if=bootblock of=xv6.img conv=notrunc dd if=kernel of=xv6.img seek=1 conv=notrunc ``` - 產生 xv6.img - `dd` 指令將 `if`(input file) 複製到 `of`(output file) - `count` 限制輸入的大小,單位為 blocks,一個 block 的大小由 `ibs=BYTES` 宣告(預設為 512)。 - `notrunc` 在輸出檔案時會比對輸入的檔案,只會輸出與輸入檔案不一樣的地方。 - `seek` 會略過數個 blocks 之後再輸出,block 大小由 `obs=BTYES` 宣告(預設為 512)。 :::warning /dev/zero 為一個特殊檔案,讀取時會提供無限的空字元,44 行為生成一個大小為 10000(個 block)的空白檔案 xv6.img(或是把 xv6.img 的前 10000 個 block 清除。) ::: ```c=+ bootblock: bootasm.S bootmain.c $(CC) $(CFLAGS) -O -nostdinc -I. -c bootmain.c $(CC) $(CFLAGS) -nostdinc -I. -c bootasm.S $(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 -o bootblock.o bootasm.o bootmain.o $(OBJDUMP) -S bootblock.o > bootblock.asm $(OBJCOPY) -S -O binary bootblock.o bootblock ./sign.pl bootblock ``` - 建立 bootblock |GCC|| | ---------|--| | `-nostdinc` | 不要從根目錄開始搜尋檔案,要從 `-I` 指定的目錄開始| | `-I.` | 指定現在的資料夾| | `-c` | 對後面的檔案編譯(或組譯),但不做連接| |LD|| |-|-| | `-N` || | `-e` | 使用後面的名稱(start)作為入口| | `-Ttext` | 將後面的位置作為輸出文件的起始位置(須為 16 進位)| | `-o` | 用來指定 ld 生成的名稱| |OBJDUMP|| |-|-| | `-S` | 反組譯目標文件| |OBJCOPY|| |-|-| | `-S` |去除所有符號資訊| | `-O binary` |輸出為二進位的文件| :::info 反組譯的用意是為了 debug ::: ```c=+ bootother: bootother.S $(CC) $(CFLAGS) -nostdinc -I. -c bootother.S $(LD) $(LDFLAGS) -N -e start -Ttext 0x7000 -o bootother.out bootother.o $(OBJCOPY) -S -O binary bootother.out bootother $(OBJDUMP) -S bootother.o > bootother.asm ``` - 啟動其他的 CPU ```c=+ initcode: initcode.S $(CC) $(CFLAGS) -nostdinc -I. -c initcode.S $(LD) $(LDFLAGS) -N -e start -Ttext 0 -o initcode.out initcode.o $(OBJCOPY) -S -O binary initcode.out initcode $(OBJDUMP) -S initcode.o > initcode.asm ``` - 建立 initcode ```c=+ kernel: $(OBJS) bootother initcode $(LD) $(LDFLAGS) -Ttext 0x100000 -e main -o kernel $(OBJS) -b binary initcode bootother $(OBJDUMP) -S kernel > kernel.asm $(OBJDUMP) -t kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > kernel.sym ``` - 建立 kernel ```c=+ tags: $(OBJS) bootother.S _init etags *.S *.c vectors.S: vectors.pl perl vectors.pl > vectors.S ``` - 使用 vectors.pl 生成 vectors.S ```c=+ ULIB = ulib.o usys.o printf.o umalloc.o _%: %.o $(ULIB) $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $@ $^ $(OBJDUMP) -S $@ > $*.asm $(OBJDUMP) -t $@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $*.sym ``` |特殊符號|| |-|-| |%|萬用字元,如 `_a` 需對應 `a.o`| |$@|代表工作目標,即 `_%`| |$^|代表所有必要條件,即 `%.o $(ULIB)`| |$*|代表第一個必要條件,但不包含副檔名| ```c=+ _forktest: forktest.o $(ULIB) # forktest has less library code linked in - needs to be small # in order to be able to max out the proc table. $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o _forktest forktest.o ulib.o usys.o $(OBJDUMP) -S _forktest > forktest.asm mkfs: mkfs.c fs.h gcc $(CFLAGS) -Wall -o mkfs mkfs.c UPROGS=\ _cat\ _echo\ _forktest\ _grep\ _init\ _kill\ _ln\ _ls\ _mkdir\ _rm\ _sh\ _usertests\ _wc\ _zombie\ fs.img: mkfs README $(UPROGS) ./mkfs fs.img README $(UPROGS) ``` - fs 指的是 file system ```c=+ -include *.d clean: rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg \ *.o *.d *.asm *.sym vectors.S parport.out \ bootblock kernel xv6.img fs.img mkfs \ $(UPROGS) # make a printout FILES = $(shell grep -v '^\#' runoff.list) PRINT = runoff.list $(FILES) xv6.pdf: $(PRINT) ./runoff print: xv6.pdf # run in emulators bochs : fs.img xv6.img if [ ! -e .bochsrc ]; then ln -s dot-bochsrc .bochsrc; fi bochs -q qemu: fs.img xv6.img qemu -parallel stdio -hdb fs.img xv6.img qemutty: fs.img xv6.img qemu -nographic -smp 2 -hdb fs.img xv6.img # CUT HERE # prepare dist for students # after running make dist, probably want to # rename it to rev0 or rev1 or so on and then # check in that version. EXTRA=\ mkfs.c ulib.c user.h cat.c echo.c forktest.c grep.c\ kill.c ln.c ls.c mkdir.c rm.c usertests.c wc.c zombie.c\ printf.c umalloc.c \ README dot-bochsrc *.pl toc.* runoff runoff1 runoff.list\ dist: rm -rf dist mkdir dist for i in $(FILES); \ do \ grep -v PAGEBREAK $$i >dist/$$i; \ done sed '/CUT HERE/,$$d' Makefile >dist/Makefile echo >dist/runoff.spec cp $(EXTRA) dist dist-test: rm -rf dist make dist rm -rf dist-test mkdir dist-test cp dist/* dist-test cd dist-test; ../m print cd dist-test; ../m bochs || true cd dist-test; ../m qemu # update this rule (change rev1) when it is time to # make a new revision. tar: rm -rf /tmp/xv6 mkdir -p /tmp/xv6 cp dist/* /tmp/xv6 (cd /tmp; tar cf - xv6) | gzip >xv6-rev2.tar.gz ``` --- ## Make > 我本來是打算在 MacOX 的環境下 make xv6 的,但是如果要 make 的話需要有 `i386-jos-elf` 的相關工具,需另外安裝([安裝方法](https://pdos.csail.mit.edu/6.828/2016/tools.html))。由於我一直沒辦法安裝,最後只好裝 ubuntu 來 make。[color=#81C784] - 在主目錄下輸入 `make` 指令 - 接著會多出兩個檔案:xv6.img、fs.img,我們使用 qemu 來運行 xv6。 --- ## QEMU - 輸入以下代碼[^2] ``` qemu-system-i386 -serial mon:stdio -hdb fs.img xv6.img -smp 1 -m 512 ``` ![](https://i.imgur.com/sxKQLWH.jpg) [^2]:[【學習 Xv6 】在 Mac OSX 下運行 Xv6](http://leenjewel.github.io/blog/2014/07/24/%5B%28xue-xi-xv6-%29%5D-zai-mac-osx-xia-yun-xing-xv6/) :::warning 註:make 好了以後,在 MacOS 的環境下使用 QEMU 模擬器即可運行。 ::: --- ## Code: Kernel.ld ```c= /* Simple linker script for the JOS kernel. See the GNU ld 'info' manual ("info ld") to learn the syntax. */ OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") OUTPUT_ARCH(i386) ENTRY(_start) SECTIONS { /* Link the kernel at this address: "." means the current address */ /* Must be equal to KERNLINK */ . = 0x80100000; ``` - 設定記憶體位置到 `0x8010000`(虛擬地址,分頁硬體會映射至實體位置 `0x0010000`) - *bootmain.c* 將 ELF 指標指向 `0x100000` 接著將 kernel 讀入。 ```c=+ .text : AT(0x100000) { *(.text .stub .text.* .gnu.linkonce.t.*) } PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } /* Include debugging information in kernel memory */ .stab : { PROVIDE(__STAB_BEGIN__ = .); *(.stab); PROVIDE(__STAB_END__ = .); BYTE(0) /* Force the linker to allocate space for this section */ } .stabstr : { PROVIDE(__STABSTR_BEGIN__ = .); *(.stabstr); PROVIDE(__STABSTR_END__ = .); BYTE(0) /* Force the linker to allocate space for this section */ } /* Adjust the address for the data segment to the next page */ . = ALIGN(0x1000); /* Conventionally, Unix linkers provide pseudo-symbols * etext, edata, and end, at the end of the text, data, and bss. * For the kernel mapping, we need the address at the beginning * of the data section, but that's not one of the conventional * symbols, because the convention started before there was a * read-only rodata section between text and data. */ PROVIDE(data = .); /* The data segment */ .data : { *(.data) } PROVIDE(edata = .); .bss : { *(.bss) } PROVIDE(end = .); /DISCARD/ : { *(.eh_frame .note.GNU-stack) } } ``` ###### tags: `xv6` `kernel` `make`