操作系统真象还原实验记录之实验十二:实现ASSERT,通过makefile完成编译
对应书P367 第8.2节
1.相关基础知识
见书
2.实验代码
完成了开关中断函数。实现assert断言函数用于调式程序,编写makefile简化编译
2.1 interrupt.c
增加的代码
#define EFLAGS_IF 0x00000200 // eflags寄存器中的if位为1
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR))
/* 开中断并返回开中断前的状态*/
enum intr_status intr_enable() {
enum intr_status old_status;
if (INTR_ON == intr_get_status()) {
old_status = INTR_ON;
return old_status;
} else {
old_status = INTR_OFF;
asm volatile("sti"); // 开中断,sti指令将IF位置1
return old_status;
}
}
/* 关中断,并且返回关中断前的状态 */
enum intr_status intr_disable() {
enum intr_status old_status;
if (INTR_ON == intr_get_status()) {
old_status = INTR_ON;
asm volatile("cli" : : : "memory"); // 关中断,cli指令将IF位置0
return old_status;
} else {
old_status = INTR_OFF;
return old_status;
}
}
/* 将中断状态设置为status */
enum intr_status intr_set_status(enum intr_status status) {
return status & INTR_ON ? intr_enable() : intr_disable();
}
/* 获取当前中断状态 */
enum intr_status intr_get_status() {
uint32_t eflags = 0;
GET_EFLAGS(eflags);
return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}
intr_enable() 用于开中断
2.2 interrupt.h
#ifndef __KERNEL_INTERRUPT_H
#define __KERNEL_INTERRUPT_H
#include "stdint.h"
typedef void* intr_handler;
void idt_init(void);
/* 定义中断的两种状态:
* INTR_OFF值为0,表示关中断,
* INTR_ON值为1,表示开中断 */
enum intr_status { // 中断状态
INTR_OFF, // 中断关闭
INTR_ON // 中断打开
};
enum intr_status intr_get_status(void);
enum intr_status intr_set_status (enum intr_status);
enum intr_status intr_enable (void);
enum intr_status intr_disable (void);
void register_handler(uint8_t vector_no, intr_handler function);
#endif
2.3 debug.h
#ifndef __KERNEL_DEBUG_H
#define __KERNEL_DEBUG_H
void panic_spin(char* filename, int line, const char* func, const char* condition);
/*************************** __VA_ARGS__ *******************************
* __VA_ARGS__ 是预处理器所支持的专用标识符。
* 代表所有与省略号相对应的参数.
* "..."表示定义的宏其参数可变.*/
#define PANIC(...) panic_spin (__FILE__, __LINE__, __func__, __VA_ARGS__)
/***********************************************************************/
#ifdef NDEBUG
#define ASSERT(CONDITION) ((void)0)
#else
#define ASSERT(CONDITION) \
if (CONDITION) {} else { \
/* 符号#让编译器将宏的参数转化为字符串字面量 */ \
PANIC(#CONDITION); \
}
#endif /*__NDEBUG */
#endif /*__KERNEL_DEBUG_H*/
PANIC(…)表示函数panic_spin参数可变,__VA_ARGS__代表所有可变参数
宏毕竟是一段代码,调用宏越多的地方,程序体积就越大,为了方便调试,这个宏ASSERT将会频繁出现在我们操作系统的各个角落。
当我们不再需要调试的时候,就应该可以手动取消这些宏;
预处理指令#ifdef,他的意思是,如果定义了宏NDEBUG,就使ASSERT等于0空值。
也就是说,定义宏NDEBUG是一个开关,掌握着ASSERT宏是否有效。
gcc -D NDEBUG就可以定义这个宏,但是一般-D NDEBUG写在makefile中
2.4 debug.c
#include "debug.h"
#include "print.h"
#include "interrupt.h"
/* 打印文件名,行号,函数名,条件并使程序悬停 */
void panic_spin(char* filename, \
int line, \
const char* func, \
const char* condition) \
{
intr_disable(); // 因为有时候会单独调用panic_spin,所以在此处关中断。
put_str("\n\n\n!!!!! error !!!!!\n");
put_str("filename:");put_str(filename);put_str("\n");
put_str("line:0x");put_int(line);put_str("\n");
put_str("function:");put_str((char*)func);put_str("\n");
put_str("condition:");put_str((char*)condition);put_str("\n");
while(1);
}
2.5 main.c
#include "print.h"
#include "init.h"
#include "debug.h"
int main(void) {
put_str("I am kernel\n");
init_all();
// asm volatile("sti"); //临时开中断
ASSERT(1==2);
while(1);
return 0;
}
总结:
- ASSERT(1==2); 调用ASSERT函数
- ASSERT函数,调用PANIC(#CONDITION)函数;
- PANIC(…) panic_spin (FILE, LINE, func, VA_ARGS)前三个参数固定,第四个参数由#CONDITION转化为字符串传入,__VA_ARGS__接收
- panic_spin函数实行关中断并且打印错误“1==2”。
2.6 makefile
BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
$(BUILD_DIR)/debug.o
############## c代码编译 ###############
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
lib/stdint.h kernel/init.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
lib/stdint.h kernel/interrupt.h device/timer.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/stdint.h\
lib/kernel/io.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
lib/kernel/print.h lib/stdint.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
############## 汇编代码编译 ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.S
$(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/print.o: lib/kernel/print.S
$(AS) $(ASFLAGS) $< -o $@
############## 链接所有目标文件 #############
$(BUILD_DIR)/kernel.bin: $(OBJS)
$(LD) $(LDFLAGS) $^ -o $@
.PHONY : mk_dir hd clean all
mk_dir:
if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
hd:
dd if=$(BUILD_DIR)/kernel.bin \
of=hd60M.img \
bs=512 count=200 seek=9 conv=notrunc
clean:
cd $(BUILD_DIR) && rm -f ./*
build: $(BUILD_DIR)/kernel.bin
all: mk_dir build hd
makefile相当于一种脚本语言文件,必须遵循make定义的语法
而make相当于脚本解析器
make和makefile的本质是,make通过解析makefile文件,找出哪些文件有变化,根据依赖关系找出受变化影响的文件,然后将找出所有的文件执行事先在makefile定义好的命令规则。
格式:
目标文件:依赖文件
[TAB]命令
命令的行首必须以TAB开头。
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h
lib/stdint.h kernel/init.h
$(CC) $(CFLAGS) $< -o $@
上述命令:
目标文件main.o依赖于main.c、print.h、stdint.h、int.h
依赖文件中main.c的mtime比main.o的mtime更新(或目标文件main.o不存在)
所以执行下列命令
$< :表示依赖文件中的第一个文件 也就是main.c
$@:表示所有目标文件的集合
$^:该规则中所有依赖文件集合
这个命令规则表示会重新编译生成目标文件main.o
运行make all 会递归执行所有命令
再举一个例子
若发现依赖文件有但是不存在,则递归找以该不存在依赖文件为目标文件的规则解析。
若发现依赖文件没有,则直接执行命令。
伪目标
上述概念和伪目标不同
clean后面没有依赖文件,则直接执行命令。上述是依赖文件不存在,则递归解析其他。有区别
3.效果图
3.1一句make all 完成编译到刻入磁盘
make all
3.2运行bochs
./bochs -f bochsrc.disk