ucore lab1 实验报告 01 -- function.mk

0. ucore 实验介绍

ucore 是清华大学操作系统课程教学团队编写的教学操作系统,详细信息可参考 ucore 简介

课程主页http://os.cs.tsinghua.edu.cn/oscourse/OS2020spring

源码仓库https://github.com/LearningOS/ucore_os_lab

实验指导书https://learningos.github.io/ucore_os_webdocs/

指导书仓库https://github.com/LearningOS/ucore_os_webdocs

1. lab1 源码结构及 demo 说明

1.1 lab1 源码结构

lab1_result
├── boot
│   ├── bootasm.S bootmain.c asm.h
├── kern
│   ├── debug
│   │   └── kdebug.c kmonitor.c panic.c assert.h kdebug.h kmonitor.h stab.h
│   ├── driver
│   │   └── clock.c console.c intr.c picirq.c clock.h console.h intr.h kbdreg.h picirq.h
│   ├── init
│   │   └── init.c
│   ├── libs
│   │   └── readline.c stdio.c
│   ├── mm
│   │   └── pmm.c memlayout.h mmu.h pmm.h
│   └── trap
│       └── trap.c trapentry.S vectors.S trap.h
├── libs
│   └── defs.h elf.h error.h printfmt.c stdarg.h stdio.h string.c string.h x86.h
├── Makefile
├── report.md
└── tools
    └── function.mk gdbinit grade.sh kernel.ld lab1init moninit sign.c vector.c

1. 2 demo 说明

为方便说明 Makefile 中各个函数的作用,构造了 demo,各文件如下:

.
├── Makefile # 每节例子
├── src
│   ├── common.c
│   ├── common.h
│   ├── init.c
│   ├── init.h
│   ├── main.c
│   └── README
└── tools
    └── function.mk # ucore 源码目录下待解析的 Makefile

各源码文件内容如下:

// common.c
#include <stdio.h>
#include "common.h"

void prinfo(const char *s)
{
	if (!s) return ;
	printf(INFO "%s\n", s);
}

// commmon.h
#ifndef _COMMON_H
#define _COMMON_H

#define INFO "[INFO] "
void prinfo(const char *s);

#endif

// init.c
#include "init.h"
#include "common.h"

static void mm_init(void)
{
	prinfo("memory init...");
}

static void rest_init(void)
{
	prinfo("rest init...");
}

int start_kernel(void)
{
	prinfo("start kernel...");
	mm_init();
	rest_init();
}

//init.h
#ifndef _INIT_H
#define _INIT_H

int start_kernel(void);
static void mm_init(void);
static void rest_init(void);

#endif

// main.c
#include "init.h"

void main(void)
{
	start_kernel();
}

2. function.mk

2.1 .SECONDEXPANSION

可参考 https://blog.csdn.net/Anhui_Chen/article/details/113097231

2.2 listf – 列出所有文件

SLASH := /
# list all files in some directories: (#directories, #types)
listf = $(filter $(if $(2),$(addprefix %.,$(2)),%),\
		  $(wildcard $(addsuffix $(SLASH)*,$(1))))

列出指定目录下所有指定后缀的文件。参数 1 指定目录,参数 2 指定后缀,如果参数 2 为空,则列出参数 1 指定目录,用法如下:

# 列出 src 目录下所有 .c .h 后缀的文件 
SLASH		:= /

include tools/function.mk

all:
	echo $(call listf,src,c h)
	
# 或者省略后缀类型,则列出所有文件 
or:
	echo $(call listf,src)
	
# make -s 结果如下:
result1:
	src/init.h src/common.h src/init.c src/common.c src/main.c
# make or -s 结果如下:
result2:
	src/init.h src/common.h src/init.c src/common.c src/README src/main.c

2.3 toobj – 替换后缀为 .o

OBJDIR := obj
SLASH := /
# get .o obj files: (#files[, packet]) 
toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)),\
		$(addsuffix .o,$(basename $(1))))

后缀替换成 .o ,参数 1 为文件列表,由空格隔开,用法如下:

SLASH		:= /
OBJDIR		:= obj

include tools/function.mk

all:
	echo $(call toobj,$(notdir $(call listf,src,c)),temp)
or:
	echo $(call toobj,$(notdir $(call listf,src,c)))
	
# make -s 结果如下:
result1:
	obj/temp/init.o obj/temp/common.o obj/temp/main.o
# make or -s 结果如下:
result2:
	obj/init.o obj/common.o obj/main.o

2.4 todep – 替换后缀为 .d

# get .d dependency files: (#files[, packet])
todep = $(patsubst %.o,%.d,$(call toobj,$(1),$(2)))

后缀替换成 .d,用法与 toobj 相同,如下:

SLASH		:= /
OBJDIR		:= obj

include tools/function.mk

all:
	echo $(call todep,$(notdir $(call listf,src,c)),temp)
or:
	echo $(call todep,$(notdir $(call listf,src,c)))
	
# make -s 结果如下:
result1:
	obj/temp/init.d obj/temp/common.d obj/temp/main.d
# make or -s 结果如下:
result2:
	obj/init.d obj/common.d obj/main.d

2.5 totarget – 添加 bin/ 前缀

BINDIR := bin
SLASH := /
totarget = $(addprefix $(BINDIR)$(SLASH),$(1))

添加 bin/ 前缀,用法如下:

SLASH		:= /
OBJDIR		:= obj
BINDIR		:= bin

include tools/function.mk

all:
	echo $(call totarget,image)

# make -s 输出结果如下:
result:
	bin/image

2.6 packetname – 添加 __objs_ 前缀

OBJPREFIX := __objs_
# change $(name) to $(OBJPREFIX)$(name): (#names)
packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX))

添加 _objs 前缀,用法如下:

SLASH		:= /
OBJDIR		:= obj
BINDIR		:= bin
OBJPREFIX	:= __objs_

include tools/function.mk

all:
	echo $(call packetname,image)
or:
	echo $(call packetname)
	
# make -s 结果如下:
result1:
	__objs_image
# make or -s 结果如下:
result2:
	__objs_

2.7 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 指定源文件,参数 2 指定编译器,参数 3 指定编译选项,参数4 与 toobj、todep 第二个参数作用相同,用法如下:

.SECONDEXPANSION:
$(eval $(call cc_template,main.c,gcc,-g,temp))

展开后,效果如下:

obj/temp/main.d: main.c | obj/temp/
	@gcc -I./ -g -MM main.c -MT "obj/temp/main.o obj/temp/main.d"> obj/temp/main.d
obj/temp/main.o: main.c | obj/temp/
	$(V)gcc -I./ -g -c main.c -o obj/temp/main.o
ALLOBJS += obj/temp/main.o

说明:
-I gcc 参数:首先在指定目录中寻找头文件
-M 生成依赖信息,包含标准头文件
-MM 生成依赖信息,忽略由标准头文件 #include <head.h> 造成的依赖
-MT 指定依赖目标

# gcc -MM main.c init.c common.c 的结果为
main.o: main.c init.h
init.o: init.c init.h common.h
common.o: common.c common.h

# gcc -MM main.c init.c common.c -MT "main init" 的结果为
main init: main.c init.h
main init: init.c init.h common.h
main init: common.c common.h 

| 表示 order-only 依赖目标,第一次执行 make 时寻找并生成该依赖,生成后即使该依赖目标文件时间变化,也不更新目标。
$< 第一个依赖目标
$@ 第一个 target 目标
完整示例如下:

SLASH		:= /
OBJDIR		:= obj
BINDIR		:= bin
OBJPREFIX	:= __objs_

.SECONDEXPANSION:

include tools/function.mk

SRC := src/main.c
TMP := tmp
OBJ	:= $(call toobj,$(SRC),$(TMP))
DEP := $(call todep,$(SRC),$(TMP))
ORDER := $(OBJDIR)$(SLASH)$(TMP)$(SLASH)$(dir $(SRC))

all: $(OBJ) $(DEP)

$(eval $(call cc_template,$(SRC),gcc,-g,$(TMP)))

$(ORDER):
	mkdir -p $@

.PHONY: clean
clean:
	rm -rf obj bin

2.8 do_cc_compile – 列表中文件分别按编译模板展开

# compile file: (#files, cc[, flags, dir])
define do_cc_compile
$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(4))))
endef

对参数 1 列出的每个文件分贝按编译模板展开,用法如下:

SLASH       := /
OBJDIR      := obj
BINDIR      := bin
OBJPREFIX   := __objs_

.SECONDEXPANSION:

include tools/function.mk

SRC := $(call listf,src,c)
TMP := tmp
OBJ := $(call toobj,$(SRC),$(TMP))
DEP := $(call todep,$(SRC),$(TMP))
ORDER := $(OBJDIR)$(SLASH)$(TMP)$(SLASH)src/

all: $(DEP) $(OBJ)
$(eval $(call do_cc_compile,$(SRC),gcc,-g,$(TMP)))

$(ORDER):
    mkdir -p $@

.PHONY: clean
clean:
    rm -rf obj bin

2.9 do_add_files_to_packet

# add files to packet: (#files, cc[, flags, packet, dir])
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

用法如下:


SLASH       := /
OBJDIR      := obj
BINDIR      := bin
OBJPREFIX   := __objs_

.SECONDEXPANSION:

include tools/function.mk

SRC := $(call listf,src,c)
TMP := tmp
OBJ := $(call toobj,$(SRC),$(TMP))
DEP := $(call todep,$(SRC),$(TMP))
ORDER := $(OBJDIR)$(SLASH)$(TMP)$(SLASH)src/

all: $(DEP) $(OBJ)
    echo "        ALLOBJS: $(ALLOBJS)"
    echo "__temp_packet__: $(__temp_packet__)"
    echo "  __temp_objs__: $(__temp_objs__)"
    echo "   __objs_HELLO: $(__objs_HELLO)"

$(eval $(call do_add_files_to_packet,$(SRC),gcc,-g,HELLO,$(TMP)))

$(ORDER):
    mkdir -p $@

.PHONY: clean
clean:
    rm -rf obj bin

上例中,调用 do_add_files_to_packet 部分效果如下:

__temp_packet__ := __objs_HELLO
# 如果 __objs_HELLO 未定义,则定义
ifeq ($(origin __objs_HELLO),undefined)
__objs_HELLO :=
endif
__temp_objs__ := obj/tmp/src/init.o obj/tmp/src/common.o obj/tmp/src/main.o
# foreach 语句和 do__cc_compile 效果相同
__objs_HELLO += obj/tmp/src/init.o obj/tmp/src/common.o obj/tmp/src/main.o

2.10 do_add_objs_to_packet

# add objs to packet: (#objs, packet)
define do_add_objs_to_packet
__temp_packet__ := $(call packetname,$(2))
ifeq ($$(origin $$(__temp_packet__)),undefined)
$$(__temp_packet__) :=
endif
$$(__temp_packet__) += $(1)
endef

用法如下:

$(eval $(call do_add_objs_to_packet,main.o init.o common.o,HELLO))

效果与 1.9 节类似,只是没有展开为编译模板部分,如下所示:

__temp_packet__ := __objs_HELLO
# 如果 __objs_HELLO 未定义,则定义
ifeq ($(origin __objs_HELLO),undefined)
__objs_HELLO :=
endif
__objs_HELLO += main.o init.o common.o

2.11 do_create_target

# 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

用法如下:

$(eval $(call do_create_target,main,,obj/tmp/src/main.o obj/tmp/src/init.o obj/tmp/src/common.o,gcc,-g))

效果如下:

__temp_target__ = bin/main
__temp_objs__ = obj/tmp/src/main.o obj/tmp/src/init.o obj/tmp/src/common.o # 这里没有使用参数 2
TARGETS += bin/main
# 如果参数4 (指定编译器) 不为空,则
bin/main: obj/tmp/src/main.o obj/tmp/src/init.o obj/tmp/src/common.o | bin/
	gcc -g $^ -o $@
# $^ 表示所有依赖文件
# $@ 表示目标文件
# 如果参数4 为空,则
bin/main: obj/tmp/src/main.o obj/tmp/src/init.o obj/tmp/src/common.o | bin/

2.12 do_finish_all

# finish all
define do_finish_all
ALLDEPS = $$(ALLOBJS:.o=.d)
$$(sort $$(dir $$(ALLOBJS)) $(BINDIR)$(SLASH) $(OBJDIR)$(SLASH)):
	@$(MKDIR) $$@
endef

定义 ALLDEPS 变量为所有目标文件后缀替换成 .d,并创建第一个目标所需的目录

2.13 封装函数

下面的函数是对前面介绍的各种定义的简单封装。

# 对 do_cc_compile 的封装
# @files 编译源文件
# @cc 指定编译器
# @flags 编译参数,如 -g
# @dir 中间目录
# Example: $(eval $(call cc_compile,main.c init.c common.c,gcc,-g,temp))
# compile file: (#files, cc[, flags, dir])
cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4)))

# 对 do_add_files_to_packet 的封装
# @files 编译源文件
# @cc 指定编译器
# @flags 编译参数
# @packet packet指定的项目前添加 OBJPREFIX 指定的字符前缀,包含多项时,每项通过空格分割
# @dir 中间目录
# Example: $(eval $(call do_add_files_to_packet,main.c init.c,gcc,-g,main init,temp))
# add files to packet: (#files, cc[, flags, packet, dir])
add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5)))

# 对 do_add_objs_to_packet 的封装
# @objs 目标文件
# @packet 同 add_files 中的 packet
# Example: $(eval $(call do_add_objs_to_packet,main.o init.o,main init))
# add objs to packet: (#objs, packet)
add_objs = $(eval $(call do_add_objs_to_packet,$(1),$(2)))

# 对 do_create_target 的封装
# @target 最终目标
# @packet 同 add_files 中的 packet
# @obj 目标文件
# @cc 编译器
# @flags 编译选项
# Example: $(eval $(call do_create_target,main,PACKET,main.o init.o common.o,gcc,-g))
# add packets and objs to target (target, #packet, #objs, cc, [, flags])
create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5)))

# 对参数 1 指定的项目中的每一项添加 OBJPREFIX 指定的字符前缀,然后构成新的变量,输出新变量的值
read_packet = $(foreach p,$(call packetname,$(1)),$($(p)))
# 下例的输出为 HELLO WORLD
OBJPREFIX := __objs_
__objs_main := HELLO
__objs_init := WORLD
target:
    echo "$(call read_packet,main init)"

# 参数 1 指定目标,参数 2 指定依赖
add_dependency = $(eval $(1): $(2))

# 对 do_finish_all 的封装,定义 ALLDEPS 变量为所有目标文件后缀替换成 .d,并创建第一个目标所需的目录
finish_all = $(eval $(call do_finish_all))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值