操作系统真象还原实验记录之实验十二:实现ASSERT

操作系统真象还原实验记录之实验十二:实现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;
}

总结:

  1. ASSERT(1==2); 调用ASSERT函数
  2. ASSERT函数,调用PANIC(#CONDITION)函数;
  3. PANIC(…) panic_spin (FILE, LINE, func, VA_ARGS)前三个参数固定,第四个参数由#CONDITION转化为字符串传入,__VA_ARGS__接收
  4. 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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值