第八章(上) 断言&位图
本文是对《操作系统真象还原》第八章学习的笔记,欢迎大家一起交流。
断言
断言(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:
第二行即获取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