首页 > 其他分享 >第八章(上) 断言&位图

第八章(上) 断言&位图

时间:2025-01-18 10:44:09浏览次数:1  
标签:status __ 断言 中断 第八章 bitmap intr 位图 btmp

第八章(上) 断言&位图

本文是对《操作系统真象还原》第八章学习的笔记,欢迎大家一起交流。

断言

断言(Assertion) 可视为一种调试工具,主要用于检测程序在运行时是否满足某个条件。如果断言条件为 真,程序继续执行;如果为 假,程序通常会停止执行并抛出错误信息,帮助开发者发现潜在的问题。

断言语句通常具有以下结构:

assert( condition, "Error message if condition is false")
  • condition​ 是你期望为真的条件。
  • 如果 condition​ 为 True​,程序继续执行。
  • 如果 condition​ 为 False​,则会抛出一个异常或错误,并输出后面的错误信息。

一方面,当内核运行中出现问题时,多属于严重的错误,着实没必要再运行下去了。另一方面,断言在输出报错信息时,屏幕输出不应该被其他进程干扰,这样咱们才能专注于报错信息。

综上两点原因,ASSERT排查出错误后,最好在关中断的情况下打印报错信息。

内核运行时,为了通过时钟中断定时调度其他任务,大部分情况下中断是打开的,因此我们需要实现一些可以控制中断开关的函数

中断控制函数

如下,在/kernel/interrupt.h中添加中断状态的数据结构,以及控制中断状态的函数声明

/*定义中断状态*/
enum intr_status
{
    INTR_OFF, // 表示中断关闭
    INTR_ON   // 表示中断打开
};

/* 获取当前中断状态 */
enum intr_status intr_get_status(void);
/* 开中断并返回开中断前的状态*/
enum intr_status intr_enable(void);
/* 关中断,并且返回关中断前的状态 */
enum intr_status intr_disable(void);
/* 将中断状态设置为status */
enum intr_status intr_set_status(enum intr_status);

然后我们在/kernel/interrupt.c​中实现其功能。

#define EFLAGS_IF   0x00000200       // eflags寄存器中的if位为1
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR))
//pop到了eflags_var所在内存中,该约束自然用表示内存的字母,但是内联汇编中没有专门表示约束内存的字母,所以只能用
//g 代表可以是任意寄存器,内存或立即数

eflags寄存器格式如下,if位为1即0x200:

image

第二行即获取eflags寄存器中的值。

然后实现四个函数,比较简单。

/* 获取当前中断状态 */
enum intr_status intr_get_status() {
   uint32_t eflags = 0; 
   GET_EFLAGS(eflags);
   return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}


/* 开中断并返回开中断前的状态*/
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
                                          //cli指令不会直接影响内存。然而,从一个更大的上下文来看,禁用中断可能会影响系统状态,
                                          //这个状态可能会被存储在内存中。所以改变位填 "memory" 是为了安全起见,确保编译器在生成代码时考虑到这一点。
      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();   //enable与disable函数会返回旧中断状态
}

实现断言

我们首先在/kernel/debug.h​中定义ASSERT​的断言宏

#ifndef __KERNEL_DEBUG_H
#define __KERNEL_DEBUG_H
void panic_spin(char* filename, int line, const char* func, const char* condition);

//...是可变参数,也就是随便你传多少个参数,然后原封不动地传到__VA_ARGS_那里去
//__FILE__,__LINE__,__func__是预定义宏,代表这个宏所在的文件名,行数,与函数名字,编译器处理
#define PANIC(...) panic_spin (__FILE__, __LINE__, __func__, __VA_ARGS__)

//如果定义了NDEBUG,那么下面定义的ASSERT就是个空。这样我们可以便捷的让所有ASSERT宏失效。因为有时候断言太多,程序会运行
//很慢。我们如果不想要ASSERT起作用,编译时用gcc-DNDEBUG就行了
#ifdef NDEBUG
   #define ASSERT(CONDITION) ((void)0)
#else
#define ASSERT(CONDITION)   \
    if(CONDITION){}         \
    else{PANIC(#CONDITION);}    //加#后,传入的参数变成字符串

#endif  //结束#ifdef NDEBUG
#endif  //结束#define __KERNEL_DEBUG_H

如果没有定义NDEBUG 那么就会调用ASSERT(CONDITION),然后就会PANIC(#CONDITION),PANIC定义在第七行,我们传递的参数会变成panic_spin的最后一个参数,然后进入panic_spin的处理逻辑,在/kernel/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();	//发生错误时打印错误信息,不应该被打扰
   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);
}

位图实现

位图也很简单,就是用一位来表示一个内存块4kb是否空闲,0表示空闲,1表示已被使用。

/lib/kernel/bitmap.h​里的代码如下:

#ifndef __LIB_KERNEL_BITMAP_H
#define __LIB_KERNEL_BITMAP_H
#include "global.h"
#define BITMAP_MASK 1
struct bitmap {                 //这个数据结构就是用来管理整个位图
   uint32_t btmp_bytes_len;     //记录整个位图的大小,字节为单位
   uint8_t* bits;               //用来记录位图的起始地址,我们未来用这个地址遍历位图时,操作单位指定为最小的字节
};

void bitmap_init(struct bitmap* btmp);
bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx);
int bitmap_scan(struct bitmap* btmp, uint32_t cnt);
void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value);
#endif

其中第3行定义了一个BITMAP_MASK,用于指示某位为1

5-8行定义bitmap结构体,用于指示位图

/lib/kernel/bitmap.c​的代码如下:

#include "bitmap.h" //不仅是为了通过一致性检查,位图的数据结构struct bitmap也在这里面
#include "stdint.h"
#include "string.h" //里面包含了内存初始化函数,memset
#include "print.h"
#include "interrupt.h"
#include "debug.h" //ASSERT

/* 将位图btmp初始化 */
void bitmap_init(struct bitmap *btmp)
{
    memset(btmp->bits, 0, btmp->btmp_bytes_len);
}

// 用来确定位图的某一位是1,还是0。若是1,返回真(返回的值不一定是1)。否则,返回0。传入两个参数,指向位图的指针,与要判断的位的偏移
bool bitmap_scan_test(struct bitmap *btmp, uint32_t bit_idx)
{
    uint32_t byte_idx = bit_idx / 8; // 确定要判断的位所在字节的偏移
    uint32_t bit_odd = bit_idx % 8;  // 确定要判断的位在某个字节中的偏移
    return (btmp->bits[byte_idx] & (BITMAP_MASK << bit_odd));
}

// 用来在位图中找到cnt个连续的0,以此来分配一块连续未被占用的内存,参数有指向位图的指针与要分配的内存块的个数cnt
// 成功就返回起始位的偏移(如果把位图看做一个数组,那么也可以叫做下标),不成功就返回-1
int bitmap_scan(struct bitmap *bitmap, uint32_t cnt)
{
    uint32_t area_start = 0, area_size = 0; // 用来存储一个连续为0区域的起始位置, 存储一个连续为0的区域大小
    while (1)
    {
        while (bitmap_scan_test(bitmap, area_start) && area_start / 8 < bitmap->btmp_bytes_len) // 当这个while顺利结束1、area_start就是第一个0的位置;2、area_start已经越过位图边界
            area_start++;
        if (area_start / 8 >= bitmap->btmp_bytes_len) // 上面那个循环跑完可能是area_start已经越过边界,说明此时位图中是全1,那么就没有可分配内存
            return -1;
        area_size = 1; // 来到了这一句说明找到了位图中第一个0,那么此时area_size自然就是1
        while (area_size < cnt)
        {
            if ((area_start + area_size) / 8 < bitmap->btmp_bytes_len)
            {                                                              // 确保下一个要判断的位不超过边界
                if (bitmap_scan_test(bitmap, area_start + area_size) == 0) // 判断区域起始0的下一位是否是0
                    area_size++;
                else
                    break; // 进入else,说明下一位是1,此时area_size还没有到达cnt的要求,且一片连续为0的区域截止,break
            }
            else
                return -1; // 来到这里面,说面下一个要判断的位超过边界,且area_size<cnt,返回-1
        }
        if (area_size == cnt) // 有两种情况另上面的while结束,1、area_size == cnt;2、break;所以要判断
            return area_start;
        area_start += (area_size + 1); // 更新area_start,判断后面是否有满足条件的连续0区域
    }
}

// 将位图某一位设定为1或0,传入参数是指向位图的指针与这一位的偏移,与想要的值
void bitmap_set(struct bitmap *btmp, uint32_t bit_idx, int8_t value)
{
    ASSERT((value == 0) || (value == 1));
    uint32_t byte_idx = bit_idx / 8; // 确定要设置的位所在字节的偏移
    uint32_t bit_odd = bit_idx % 8;  // 确定要设置的位在某个字节中的偏移

    /* 一般都会用个0x1这样的数对字节中的位操作,
     * 将1任意移动后再取反,或者先取反再移位,可用来对位置0操作。*/
    if (value)
    { // 如果value为1
        btmp->bits[byte_idx] |= (BITMAP_MASK << bit_odd);
    }
    else
    { // 若为0
        btmp->bits[byte_idx] &= ~(BITMAP_MASK << bit_odd);
    }
}

9-12行bitmap_init函数,将位图全部初始化为0

15-20行bitmap_scan_test函数,判断某一位是否空闲

24-50行用于查找连续空闲块

53-69行将某一位设置为0/1

标签:status,__,断言,中断,第八章,bitmap,intr,位图,btmp
From: https://www.cnblogs.com/fdxsec/p/18678118/chapter-8-part-1-interseducting-gxlnv

相关文章

  • 位图有关的格式信息
     GetObject(hBitmap,sizeof(BITMAP),(LPSTR)&bmp); 获取HBITMAP句柄包含的位图信息结构,不包含像素数据内容。typedefstructtagBITMAP{LONGbmType;//位图类型,必须为0LONGbmWidth;//位图宽度(以像素为单位)LO......
  • Redis 是一个开源的高性能键值对存储数据库,通常被用作缓存、消息队列和持久化数据库。
    Redis服务器是什么?Redis是一个开源的高性能键值对存储数据库,通常被用作缓存、消息队列和持久化数据库。Redis支持多种数据结构,如字符串、哈希、列表、集合、有序集合、位图等。它被广泛用于需要快速读写操作、低延迟的场景。Redis可以作为一个独立的数据库使用,也可以作为缓......
  • 信息安全数学基础-期末(第八章)
    群定义半群的定义:设S是一个具有结合法的非空集合.如果S中有一个元素e;使得对S中所有元素a,都有ea=ae=a.单位元的定义:性质:设S是一个有单位元的半群,则对S中的任意可逆元a,其逆元a'是唯的群的定义:子群定义:同态和同构定义:单射、满射、双射:单射确保不同的输入产生......
  • RHCE-第八章:防火墙
    一、什么是防火墙防火墙是位于内部网和外部网之间的屏障,它按照系统管理员预先定义好的规则来控制数据包的进出。防火墙又可以分为硬件防火墙与软件防火墙。硬件防火墙是由厂商设计好的主机硬件,这台硬件防火墙的操作系统主要以提供数据包数据的过滤机制为主,并将其他不必要的功......
  • 【Rust自学】11.2. 断言(Assert)
    喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)11.2.1.使用assert!宏检查测试结果assert宏来自标准库,用来确定某个状态是否为true。这个宏可以接收一个返回类型为布尔类型的表达式:当assert!内的值为true时测试......
  • Postman断言介绍
    Postman断言介绍Postman工具代替人工自动判定预期结果和实际结果是否一致。1、断言方式pm.test()函数:参数1:字符串-测试断言名称参数2:回调函数-具体断言语句2、响应状态码断言pm.response.to.have.status(code:Number):判断是否包含指定的状态码pm.test("statuscodei......
  • 请说下TypeScript中的类型断言是什么?
    在TypeScript中,类型断言(TypeAssertion)是一种告诉编译器“相信我,我知道我在做什么”的方式。它允许开发者明确地指定一个值的类型,而不是让TypeScript编译器自动推断。这在某些情况下是非常有用的,特别是当编译器无法正确推断类型,或者我们需要覆盖编译器的推断时。类型断言的语法有......
  • 第八章
    8.4代码点击查看代码importnumpyasnpimportmatplotlib.pyplotaspltfromscipy.integrateimportsolve_ivpdefsystem(t,state):x,y=statedxdt=-x*3-ydydt=x-y*3return[dxdt,dydt]t_span=(0,30)y0=[1,0.5]sol=solv......
  • Python 基础知识点!assert 断言及其应用场景最全汇总
    在编程中,断言(Assertion)是一种检查条件是否为真的语句,如果条件不为真,则会引发异常。断言通常用于验证程序中的假设,确保代码在预期的条件下运行。在Python中,断言使用assert关键字,其语法如下:assertcondition,[error_message]condition是一个表达式,如果该表达式为False,......
  • 《程序员修炼之路——从小工到专家》第八章学习笔记
    读《程序员修炼之路——从小工到专家》第八章有感《程序员修炼之路——从小工到专家》第八章,围绕程序员的软技能展开,令人耳目一新且收获颇丰。该章着重强调软技能于程序员的关键意义。沟通能力首当其冲,清晰准确地与团队成员交流,能大幅提升协作效率,避免因误解导致的项目延误,这让......