目录结构
workdir
|------ GNUmakefile
|
|------ boot
| |---- Makefrag
| |---- boot.S
| |---- main.c
|------ fs
|
|------ inc
|
|------ kern
| |---- Makefrag
| |---- kernel.ld
| |---- entry.S
| |---- entrypgdir,c
| |---- init.c
| |---- monitor.c
| |---- monitor.h
| |---- kdebug.c
| |---- kdebug.h
| |---- printf.c
| |---- console.c
| |---- console.h
|
|------ user
|
|------ conf
| |---- env.mk
| |---- lab.mk
这里boot和kern目录下的Makefrag分别定义了内核boot和主体在编译时所需要的文件
文件boot/Makefarg
内容如下:
# 编译之后对象文件输出的目录
OBJDIRS += boot
# 编译之后输出的对象文件
BOOT_OBJS := $(OBJDIR)/boot/boot.o $(OBJDIR)/boot/main.o
# 得到对象文件的方法
# 下面出现的变量都是在顶层Makefile中
# 将所有的.c文件编译成.o文件
$(OBJDIR)/boot/%.o: boot/%.c
@echo + cc -Os $<
@mkdir -p $(@D)
$(V)$(CC) -nostdinc $(KERN_CFLAGS) -Os -c -o $@ $<
# 将所有的.S文件编译.o文件
$(OBJDIR)/boot/%.o: boot/%.S
@echo + as $<
@mkdir -p $(@D)
$(V)$(CC) -nostdinc $(KERN_CFLAGS) -c -o $@ $<
$(OBJDIR)/boot/main.o: boot/main.c
@echo + cc -Os $<
$(V)$(CC) -nostdinc $(KERN_CFLAGS) -Os -c -o $(OBJDIR)/boot/main.o boot/main.c
$(OBJDIR)/boot/boot: $(BOOT_OBJS)
@echo + ld boot/boot
$(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 -o $@.out $^
$(V)$(OBJDUMP) -S $@.out >$@.asm
$(V)$(OBJCOPY) -S -O binary -j .text $@.out $@
$(V)perl boot/sign.pl $(OBJDIR)/boot/boot
文件kern/Makefarg
内容如下:
OBJDIRS += kern
KERN_LDFLAGS := $(LDFLAGS) -T kern/kernel.ld -nostdlib
# entry.S must be first, so that it's the first code in the text segment!!!
#
# We also snatch the use of a couple handy source files
# from the lib directory, to avoid gratuitous code duplication.
KERN_SRCFILES := kern/entry.S \
kern/entrypgdir.c \
kern/init.c \
kern/console.c \
kern/monitor.c \
kern/pmap.c \
kern/env.c \
kern/kclock.c \
kern/picirq.c \
kern/printf.c \
kern/trap.c \
kern/trapentry.S \
kern/sched.c \
kern/syscall.c \
kern/kdebug.c \
lib/printfmt.c \
lib/readline.c \
lib/string.c
# Only build files if they exist.
KERN_SRCFILES := $(wildcard $(KERN_SRCFILES))
# Binary program images to embed within the kernel.
KERN_BINFILES :=
KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFILES))
KERN_OBJFILES := $(patsubst %.S, $(OBJDIR)/%.o, $(KERN_OBJFILES))
KERN_OBJFILES := $(patsubst $(OBJDIR)/lib/%, $(OBJDIR)/kern/%, $(KERN_OBJFILES))
KERN_BINFILES := $(patsubst %, $(OBJDIR)/%, $(KERN_BINFILES))
# How to build kernel object files
$(OBJDIR)/kern/%.o: kern/%.c $(OBJDIR)/.vars.KERN_CFLAGS
@echo + cc $<
@mkdir -p $(@D)
$(V)$(CC) -nostdinc $(KERN_CFLAGS) -c -o $@ $<
$(OBJDIR)/kern/%.o: kern/%.S $(OBJDIR)/.vars.KERN_CFLAGS
@echo + as $<
@mkdir -p $(@D)
$(V)$(CC) -nostdinc $(KERN_CFLAGS) -c -o $@ $<
$(OBJDIR)/kern/%.o: lib/%.c $(OBJDIR)/.vars.KERN_CFLAGS
@echo + cc $<
@mkdir -p $(@D)
$(V)$(CC) -nostdinc $(KERN_CFLAGS) -c -o $@ $<
# Special flags for kern/init
$(OBJDIR)/kern/init.o: override KERN_CFLAGS+=$(INIT_CFLAGS)
$(OBJDIR)/kern/init.o: $(OBJDIR)/.vars.INIT_CFLAGS
# How to build the kernel itself
$(OBJDIR)/kern/kernel: $(KERN_OBJFILES) $(KERN_BINFILES) kern/kernel.ld \
$(OBJDIR)/.vars.KERN_LDFLAGS
@echo + ld $@
$(V)$(LD) -o $@ $(KERN_LDFLAGS) $(KERN_OBJFILES) $(GCC_LIB) -b binary $(KERN_BINFILES)
$(V)$(OBJDUMP) -S $@ > $@.asm
$(V)$(NM) -n $@ > $@.sym
# How to build the kernel disk image
$(OBJDIR)/kern/kernel.img: $(OBJDIR)/kern/kernel $(OBJDIR)/boot/boot
@echo + mk $@
$(V)dd if=/dev/zero of=$(OBJDIR)/kern/kernel.img~ count=10000 2>/dev/null
$(V)dd if=$(OBJDIR)/boot/boot of=$(OBJDIR)/kern/kernel.img~ conv=notrunc 2>/dev/null
$(V)dd if=$(OBJDIR)/kern/kernel of=$(OBJDIR)/kern/kernel.img~ seek=1 conv=notrunc 2>/dev/null
$(V)mv $(OBJDIR)/kern/kernel.img~ $(OBJDIR)/kern/kernel.img
all: $(OBJDIR)/kern/kernel.img
grub: $(OBJDIR)/jos-grub
$(OBJDIR)/jos-grub: $(OBJDIR)/kern/kernel
@echo + oc $@
$(V)$(OBJCOPY) --adjust-vma=0x10000000 $^ $@
关于makefile语法的一些补充
makefile中等号的使用
=
:make会将整个makefile展开之后再决定左边变量的值,所以左边变量的值会是makefile中最后被指定的值
:=
:变量使用:=
赋值会覆盖之前的值,当使用:=
赋值时,变量的值取决于赋值的位置
?=
:如果左边变量之前没有被赋值,则将右边的值赋给左边,反之,变量保留原值
+=
:将右边的值加到左边的变量中
$<
代表依赖目标,即:
右边的文件,这里是将右边的文件一个一个的提取出来
$@
代表目标文件,即:
左边的文件,这里是将左边的文件一个一个的提取出来
以上面的Makefile为例
$(V)$(CC) -nostdinc $(KERN_CFLAGS) -Os -c -o $@ $<
这句在执行时会将$(OBJDIR)/boot/%.o: boot/%.c
指定的.c文件一个一个的编译成.o文件
关于gcc编译选项的说明
-Os
:优化等级和-O2
一致,但是不缩减代码尺寸
-nostdinc
:不在系统目录中寻找头文件
-pipe
:在编译的各个阶段使用管道而不是临时文件进行通信
-fno-builtin
:不引用标准库的c语言函数
-MD
:根据makefile会生成对应目标文件的依赖文件,即.d文件
关于ld的链接选项
-N
:与--omagic
等价,作用是将text段和data段设置为可读可写的。同时data段不以page为单位对齐
-e
:与--entry
等价,作用是将后面跟的符号作为程序执行的入口,而不是默认入口(一般是main)
-Ttext
:设置text段的地址,与其类似的还有-Tbss
,Tdata
关于dd指令的使用
创建一个可以启动的jos镜像需要以下三步
- dd if=/dev/zero of=obj/kern/kernel.img~ count=10000 2>/dev/null
- dd if=obj/boot/boot of=obj/kern/kernel.img~ conv=notrunc 2>/dev/null
- dd if=obj/kern/kernel of=obj/kern/kernel.img~ seek=1 conv=notrunc 2>/dev/null
第一步是初始化了一个10000个block的镜像文件kernel.img~,每个block的大小可以有ibs选项设置,默认是512bytes
第二步是将编译出来的boot elf文件加载到kernel.img~的第一个block中
第三步是将编译出来的kernel elf文件从第二个block开始加载到kernel.img中
seek
:跳过seek选项后面数值的block之后开始复制
conv
:设置传输模式,这里设置的notrunc意思是不截断输出文件
jos在qemu中启动内核的操作
qemu-system-i386 -drive file=obj/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::26000 -D qemu.log
里面比较关键的几个选项是:
-
-drive file=obj/kern/kernel.img,index=0,media=disk,format=raw
-
-serial mon:stdio
-
gdb tcp::26000
-
-D qemu.log
我尝试了一下不加-drive选项直接引导镜像,也可以启动系统,但是有一个警告,并且在系统运行的过程中shell中没有任何的log输出。
WARNING: Image format was not specified for 'obj/kern/kernel.img' and probing guessed raw.Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.Specify the 'raw' format explicitly to remove the restrictions.
这里的提示是没有指定镜像的格式,自动探测为raw格式。限制了对block 0的写操作。
qemu一些标志的使用
标准选项
-
-h : 显示帮助
-
-version : 显示版本
-
-machine [type]name[,prop=value[,…]]
- 该选项中name指定qemu模拟的机器平台,prop用于指定该机器运行时的一些属性。当前支持的机器平台可以使用-machine help得到
-
-drive option[[,option[,option[,…]]]]
- file=file 该选项用于指定要使用的镜像文件
- if=interface 该选项用于指定驱动器连接的接口类型,包括ide, scsi, sd, mtd, floppy, pflash, virtio, none.
- bus=bus,unit=unit 该选项指定bus号和unit号,决定驱动器的连接位置。
- index=index 在给定的接口类型的列表中定位连接驱动器的位置
- media=media 指定存储介质,包括disk和cdrom
-
-serial dev 该选项试讲虚拟串口重定向到宿主机的字符设备上去。默认是vc(图形化界面)和stdio(非图形化界面)
- vc[:WxH] 配置图形的大小
- pty
- chardev:id
- mon:dev_string 允许监视器被多路复用到另一个端口