操作系统——ASSERT(十六)
操作系统——ASSERT(十六)
2020-10-01 20:49:38 hawk
概述
这篇我们,我们接着前面实现的中断处理的基础上,增加assert机制,并且加深对于中断的理解
断言
assert,断言,是程序员断定程序运行在此处时,某数据的值一定为多少多少。
在这个系统中,实际上会实现两种断言——一种是为系统内核使用的ASSERT;另一种是为用户进程使用的assert。
这里我们首先实现专门为内核使用的ASSERT。我们首先简单的分析一下——当内核运行中出现问题时,一般就没有必要继续运行下去了,因为出现的问题往往会是比较严重的问题。另一方面,当内核输出保存信息时,屏幕上的输出最后也不应该被其他进程干扰,这样才方便我们进行排除错误。因此我们往往会在关中断的情况下,进行打印报错信息。
因此,实际上我们实现的ASSERT,首先需要进行自由的开、关中断的函数,如下所示
#define EFLAGS_IF (0x00000200) //eflags寄存器的IF位为1 /* b:1字节 w:2字节 l:4字节 q:8字节 g表示任意地址 */ #define GET_EFLAGS(EFLAG_VAR) asm volatile(" \ pushfl;\ popl %0":"=g"((EFLAG_VAR))); /* 获取当前中断状态变量 */ enum intr_status intr_get_status() { uint32_t eflags = 0; GET_EFLAGS(eflags); return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF; } /* 开中断 返回开中断前eflags的状态 */ enum intr_status intr_enable() { if(INTR_ON == intr_get_status()) { return INTR_ON;} else { asm volatile("\ sti"); //sti, Set IF,将IF位置为1 return INTR_OFF; } } /* 关中断 返回开中断前eflags的状态 */ enum intr_status intr_disable() { if(INTR_OFF == intr_get_status()) { return INTR_OFF;} else { asm volatile("\ cli":::"memory"); //cli, Clear IF,将IF位置为0 不知道为什么吧cobber/modify位设置,感觉并没有修改 return INTR_ON; } } /* 将中断设置为给定的status 返回修改前的中断状态 */ enum intr_status intr_set_status(enum intr_status status) { return status & INTR_ON ? intr_enable() : intr_disable(); }
根据对应的代码可以看出来,这里获取状态就是通过读eflags寄存器。而对于关、开中断来说,使用sti(set IF)、cli(clear IF)进行开中断、关中断。
下面,我们实现ASSERT函数。其思路也很简单——首先简单的关闭(屏蔽)中断,避免被其他进程干扰。下面则是调用打印程序对应输出即可,其源代码如下所示
#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*/ void panic_spin(char* filename, int line, const char* func, const char* condition) { // 关闭中断,防止被干扰 intr_disable(); put_str("\n\n\n!!!!!!! error !!!!!!\n"); put_str("filename:"); put_str(filename); put_char('\n'); put_str("line:0x"); put_uHex(line); put_char('\n'); put_str("function:"); put_str(func); put_char('\n'); put_str("condition:"); put_str(condition); put_char('\n'); while(1) {;} }
这里只需要说明两点
1. ...和__VA_ARGS__。也就是...相当于可变参数,而__VA_ARGS__代表的就是所有与"..."相对应的参数。因此,这里换一个更加形象的表明
PANIC(...) panic_spin(__FILE__, __LINE__, __func__, ...)
2. __FILE__、__LINE__、__func__预编译的宏。对于__FILE__,其表明当前行语句所在的文件名称。__LINE__,其表明当前行语句所在的文件中的行。__func__,其表明当前行语句所在的父函数的名称。
实际上,我们如果想要输出ASSERT要求的文件、行以及函数等信息,一般一定会使用到__FILE__、__LINE__和__func__这些宏。而实际上为了我们使用ASSERT的时候方便一些——不需要每一次调用ASSERT,都需要在添加__FILE__、__LINE__和__func__等宏,从而我们首先用ASSERT作为宏包装一下前面实现的panic_spin,从而最后完成ASSERT要求的功能。
下面我们在kernel中尝试调用ASSERT,如下所示
#include "print.h" #include "init.h" #include "debug.h" int main(void) { /* 初始化所有的模块 */ init_all(); ASSERT(1 == 2); while(1); return 0; }
这里给出仓库链接。类似于前面的步骤,这里使用make进行构建,然后再虚拟机上进行运行,结果如图所示