本文前置内容:assert与ifC语言中的#和##
本节对应分支:assert

关于 assert 断言函数的意义和用法,请参见assert与if ,本文不再赘述。在我们自制的 OS 中,会实现两种 assert 函数,一种为内核服务,另一种为用户进程服务。本节实现内核 assert 函数。

内核 assert 函数有以下几个要点需要注意:

1) 一旦内核 assert 函数被调用,就说明此时发生了严重的错误,系统可能面临崩溃的危险,所以应该立即停止运行。如何让系统停止运行呢?你可能会想到在 assert 函数末尾加上一个 while(1) 。没错,这也是我们的做法,但这还不够!还记得吗,操作系统是由中断来驱动并发的,即使当前代码正在 while(1) 中循环,只要发生中断,执行流依旧会转移到中断程序 。如果中断目的是任务调度,那么执行流转移到任务后,因为操作系统已经出现问题,所以任务的执行也是不可靠的。因此,我们还需要关闭中断 。视频演示如下:
{%
dplayer
“url=/2022/video/assert.mp4”
“pic=/2022/image/1.jpg” //设置封面图,同样是放在根目录下面
“loop=false” //循环播放
“theme=#FADFA3” //主题
“autoplay=false” //自动播放
“screenshot=true” //允许截屏
“hotkey=true” //允许hotKey,比如点击空格暂停视频等操作
“preload=auto” //预加载:auto
“volume=0.9” //初始音量
“playbackSpeed=1”//播放速度1倍速,可以选择1.5,2等
“lang=zh-cn”//语言
“mutex=true”//播放互斥,就比如其他视频播放就会导致这个视频自动暂停

​ “id=9E2E3368B56CD123BB4”
​ “api=https://api.prprpr.me/dplayer/”
​ “token=tokendemo”
​ “maximum=1000”
​ “addition=[‘https://api.prprpr.me/dplayer/v3/bilibili?aid=4157142’]”
​ “user=DIYgod”
​ “bottom=15%”
​ “unlimited=true”

%}

显然,用户进程的 assert 函数无需关闭中断,即使 A 用户程序遇上 assert 而崩溃,B 程序可还要接着运行呢。再者,用户进程也没有关闭中断的权限,因为 cli 指令是 ring0 级别才能使用的指令。

2) 发生错误后,我们需要快速定位错误源,因此还需要用到几个常见的预定义宏:__FILE____FUNC____LINE__ ,分别指示 assert 被触发的所在文件、函数、行号。

本分支新增了 debug.hdebug.cintrmgr.c 三个文件,以下对此三个文件进行分析。

intrmgr
intrmgrinterrupt manager 的缩写,意为管理中断:

#include "../include/interrupt.h"
#include "../include/system.h"
static int EFLAGS=0;
//以下函数的声明放在了interrupt.h
enum intr_status intr_enable()
{
    if(INTR_ON==intr_get_status())
        return INTR_ON;
    else
    {
        STI;
        return INTR_OFF;
    }
}

enum intr_status intr_disable()
{
    if(INTR_OFF==intr_get_status())
        return INTR_OFF;
    else
    {
        CLI;
        return INTR_ON;
    }
}

enum intr_status intr_get_status()
{
    asm volatile("pushf; pop EFLAGS"); //获取eflags的值
    return (EFLAGS_IF & EFLAGS) ? INTR_ON:INTR_OFF;
}

enum intr_status intr_set_status(enum intr_status st)
{
    return INTR_ON & st ? intr_enable() : intr_disable();
}

  • 第 29 行,intr_get_status() 函数用于获取中断的开闭状态。第 29 行内联汇编,先使用 pushf 将 EFLAGS 寄存器压栈,再将其弹出到全局变量 EFLAGS 中,这样便获取了标志寄存器的值。注意,如果想要在内联汇编修改 C 语言变量的值,则该变量必须为全局变量!!因为局部变量是不会记录在符号表中的,所以编译器根本不认识局部变量的符号。这涉及到编译原理,详情请参阅《装载,链接与库》。

    其实,如果理解了笔者之前的文章函数调用过程,你也会明白为什么内联汇编不认识局部变量。简单来说,局部变量在函数栈中被创建,其定位是通过 EBP 进行的,而不是通过符号(符号本身代表地址)进行的。

    随后第 30 行识别中断开闭状态,并返回该状态。另外,EFLAGS_IF 定义在 interrupt.h 中:

    #define EFLAGS_IF  (1<<9)      //eflags中的if位
    
  • intr_enable()intr_disable() 很简单,不再赘述。唯一可能的疑惑是,为什么要返回修改之前的状态?这与以后的任务调度有关,后续还会用到这些函数,我们先提前在这做好准备。

  • intr_set_status() 并不多余,它可以提升代码的灵活性,后续我们也会看到这一点。

debug.h

#ifndef OSLEARNING_DEBUG_H
#define OSLEARNING_DEBUG_H
void panic(char* err_msg, char* file_name, int line, char* func);
#ifdef NDEBUG
#define assert(expression) ((void)0)
#else
#define assert(expr) \
    if(expr){}       \
    else{            \
    panic(#expr, __FILE__, __LINE__, __func__);}
#endif
#endif //OSLEARNING_DEBUG_H
  • 再次强调,assert 仅在 Debug 模式下使用,当软件发行(release)后,就需要屏蔽 assert 。因此,在非 Debug 时,定义 NDEBUG 宏,则 assert 成为空值,不参与编译;在 Debug 下,assert 被定义为第 7~10 行代码段。

    指定非 Debug 有两种方式:
    1)使用 GCC 的 -D 参数即可定义 NDEBUG:

    gcc -DNDEBUG
    

    2)在 debug.h 第 2 行插入 #define NDEBUG

  • 第 10 行使用到了 # 号,用于字符串化,详见C语言中的#与##

  • 宏定义是以行为单位的,跨多行需要使用 \ 进行连接。

  • __FILE____FUNC____LINE__ 是预定义宏,分别指示该行所在文件、函数、行号,这是在编译阶段就确定了的。

debug.c

#include "../include/debug.h"
#include "../include/interrupt.h"
#include "../include/print.h"
void panic(char* err_msg, char* file_name, int line, char* func)
{
    intr_disable();  //务必关闭中断
    put_str("\n===============================",BG_BLACK+FT_RED);
    put_str("\ndebug error:",BG_BLACK+FT_RED);
    put_str("\nFileName: ",BG_BLACK+FT_RED);
    put_str(file_name,BG_BLACK+FT_RED);
    put_str("\nFunction: ",BG_BLACK+FT_RED);
    put_str(func,BG_BLACK+FT_RED);
    put_str("\nLine:     ",BG_BLACK+FT_RED);
    put_int(line,BG_BLACK+FT_RED,DEC);
    put_str("\nmessage:  ",BG_BLACK+FT_RED);
    put_str(err_msg,BG_BLACK+FT_RED);
    put_str("\n===============================",BG_BLACK+FT_RED);
    while(1);       //将程序停止
}

演示如下:
{%
dplayer
“url=/2022/video/assert_debug.mp4”
“pic=/2022/image/1.jpg” //设置封面图,同样是放在根目录下面
“loop=false” //循环播放
“theme=#FADFA3” //主题
“autoplay=false” //自动播放
“screenshot=true” //允许截屏
“hotkey=true” //允许hotKey,比如点击空格暂停视频等操作
“preload=auto” //预加载:auto
“volume=0.9” //初始音量
“playbackSpeed=1”//播放速度1倍速,可以选择1.5,2等
“lang=zh-cn”//语言
“mutex=true”//播放互斥,就比如其他视频播放就会导致这个视频自动暂停

​ “id=9E2E3368B56CD123BB4”
​ “api=https://api.prprpr.me/dplayer/”
​ “token=tokendemo”
​ “maximum=1000”
​ “addition=[‘https://api.prprpr.me/dplayer/v3/bilibili?aid=4157142’]”
​ “user=DIYgod”
​ “bottom=15%”
​ “unlimited=true”
%}

文章作者: 极简
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 后端技术分享
自制操作系统
喜欢就支持一下吧