寒假的作业是写一个操作系统。老师给我们的要求比较低,基本上是能看懂学长以前写过的代码就给及格,自己写的像样的基本上良以上了。当然一个学期蜻蜓点水地学了这点东西,写一个操作系统,能不能“操作”,还是个大问号呢。

参考书自然想到了于渊的《一个操作系统的实现》。目前基本上就是抄着大神的代码,然后改改变量名啥的。抄到第三章的时候,看起来以后还要继续编译好多次,所以就干脆写个 Makefile 偷懒。但是这 Makefile 我是需要写的时候就查,写完之后又忘。所以干脆自己写个文章记下来算了。

所以,这篇文章适合那些对 Makefile 有初步了解,但是不常用,需要找点线索来回忆的人(比如我)。如果是对 Makefile 一无所知的话,请 Google 之。

Makefile 是由很多组目标规则组成的。某种角度说,目标分为两种,一种是文件目标,一种是非文件目标。嗯,先写一个简单的 Makefile 吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 定义了两个非文件目标
.PHONY = all clean
# 在未指定 make 的目标时,第一个目标为默认
all : test.bin
# 这是非文件目标,因为 .PHONY 中定义了
clean :
    rm -rf *.o *.bin
# 这是文件目标
test.bin : main.o func.o
    ld -o test.bin main.o func.o
# 这是文件目标
main.o : main.c type.h
    gcc -c -o main.o main.c
# 这是文件目标
func.o : func.c type.h
    gcc -c -o func.o func.c

需要注意的是,编写 Makefile 时,命令一定要以 Tab 开始,而不能以空格取代之。所以使用的编辑器一定要支持 Tab 并且不把 Tab 转换成空格才行。

然后,比如在写有上面 Makefile 的目录里,执行了 make all 操作,那么 make 程序将寻找 all 目标的依赖目标,即 test.bin, 是否在 Makefile 里存在。在上例中确实是存在的,所以 make 程序就去看 test.bin 的两个依赖目标,即 main.o 和 func.o 在 Makefile 里是否存在,事实是它们也是存在的。所以 make 先去 main.o 去看它的依赖目标,即 main.c 和 type.h, 但它们不在 Makefile 里,所以 make 程序就去寻找这两个文件是否存在。如果它们有任何一个比 main.o 要新,就执行 main.o 下的命令,把 main.o 编译出来。func.o 也类似。然后回溯到 test.bin, 如果 main.o 和 func.o 任何一个有更新,那么 test.bin 也会更新。然后,整个编译过程就结束了。

感觉这种以依赖的方式控制编译流程是挺好的。

最后把我目前操作系统写的 Makefile 贴出来备忘吧。

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
# C Compiler
CC = gcc
CCFLAGS = -I include/ -I include/libcrt/ -fno-builtin -fno-stack-protector -fpack-struct -Wall
CCOFLAGS = $(CCFLAGS) -c
# Assembly Compiler
ASM = nasm
ASMFLAGS = -I include/
ASMOFLAGS = $(ASMFLAGS) -f elf
# Linker
LD = ld
LDFLAGS = -s
# Archiver
AR = ar
ARFLAGS = rcs
# Image Maker
IMG = bximage
IMGFFLAGS = -fd -size=1.44 -q
IMGHFLAGS = -hd -size=16 -mode=flat -q
# Linux Mount
MNT = mount
MNTFLAGS = -o loop
UMNT = umount
UMNTFLAGS =
MNTFTMP = /mnt/cosftmp/
MNTHTMP = /mnt/coshtmp/
# Library & kernel dependent objects
LIBCRT_OBJECTS = tmp/cstringa.o tmp/cstringc.o tmp/cstdlibc.o
KERNEL_OBJECTS = tmp/kproteca.o tmp/kprotecc.o tmp/kernelc.o tmp/kprocesc.o
DEVICE_OBJECTS = \
    tmp/dvideoa.o tmp/dvideoc.o tmp/dclocka.o tmp/dclockc.o tmp/dkeybdc.o
TASK_OBJECTS = \
    tmp/terminc.o
# Kernel configurations
KERNEL_ENTRY = 0x30400
KERNEL_TARGETS = $(KERNEL_OBJECTS) $(DEVICE_OBJECTS) $(TASK_OBJECTS)
.PHONY : all clean path fd hd cd
all : fd hd cd
clean :
    rm -rf bin tmp
path : bin tmp
bin :
    mkdir bin
tmp :
    mkdir tmp
fd : path bin/coslivef.img
hd : path bin/cosliveh.img
cd : path bin/coslivec.img
bin/coslivef.img : tmp/bsfat12 tmp/loader tmp/kernel
    @echo '>>> Make live floppy image.'
    @rm -rf $@
    $(IMG) $(IMGFFLAGS) $@
    @echo '>>> Flush boot sector into live floppy.'
    dd if=tmp/bsfat12 of=$@ bs=512 count=1 conv=notrunc
    @echo '>>> Copy loader into live floppy, sudo password required.'
    @echo `sudo $(UMNT) $(MNTFTMP)`Unmounted temporary device.
    @sudo rm -rf $(MNTFTMP)
    @sudo mkdir $(MNTFTMP)
    sudo $(MNT) $(MNTFLAGS) $@ $(MNTFTMP)
    sudo cp -fv tmp/loader $(MNTFTMP)
    sudo cp -fv tmp/kernel $(MNTFTMP)
    sudo $(UMNT) $(MNTFTMP)
    @sudo rm -rf $(MNTFTMP)
bin/cosliveh.img :
    @echo '>>> Cannot make hard disk image at this moment.'
bin/coslivec.img :
    @echo '>>> Cannot make CD image at this moment.'
# FAT12 bootloader and kernel
tmp/bsfat12 : src/boot/bsfat12.asm
    $(ASM) $(ASMFLAGS) -o $@ $<
tmp/loader : src/boot/loader.asm
    $(ASM) $(ASMFLAGS) -o $@ $<
tmp/kernel : $(KERNEL_TARGETS) tmp/coscrt.a
    $(LD) $(LDFLAGS) -Ttext $(KERNEL_ENTRY) -o $@ $^
# OS C dynamic runtime library
tmp/coscrt.a : $(LIBCRT_OBJECTS)
    $(AR) $(ARFLAGS) $@ $^
tmp/cstringa.o : src/libcrt/string.asm
    $(ASM) $(ASMOFLAGS) -o $@ $<
tmp/cstringc.o : src/libcrt/string.c
    $(CC) $(CCOFLAGS) -o $@ $<
tmp/cstdlibc.o : src/libcrt/stdlib.c
    $(CC) $(CCOFLAGS) -o $@ $<
# OS kernel objects
tmp/kproteca.o : src/kernel/protect.asm
    $(ASM) $(ASMOFLAGS) -o $@ $<
tmp/kprotecc.o : src/kernel/protect.c
    $(CC) $(CCOFLAGS) -o $@ $<
tmp/kernelc.o : src/kernel/kernel.c
    $(CC) $(CCOFLAGS) -o $@ $<
tmp/kprocesc.o : src/kernel/process.c
    $(CC) $(CCOFLAGS) -o $@ $<
# OS kernel devices
tmp/dvideoa.o : src/device/video.asm
    $(ASM) $(ASMOFLAGS) -o $@ $<
tmp/dvideoc.o : src/device/video.c
    $(CC) $(CCOFLAGS) -o $@ $<
tmp/dclocka.o : src/device/clock.asm
    $(ASM) $(ASMOFLAGS) -o $@ $<
tmp/dclockc.o : src/device/clock.c
    $(CC) $(CCOFLAGS) -o $@ $<
tmp/dkeybdc.o : src/device/keyboard.c
    $(CC) $(CCOFLAGS) -o $@ $<
# OS kernel tasks
tmp/terminc.o : src/task/terminal.c
    $(CC) $(CCOFLAGS) -o $@ $<