第08章中 断言和位图

目录结构:

└── bochs
├── 02.tar.gz
├── 03.tar.gz
├── 04.tar.gz
├── 05a.tar.gz
├── 05b.tar.gz
├── 06a.tar.gz
├── 07a.tar.gz
├── 07b.tar.gz
├── 07c.tar.gz
├── 08a
│   ├── boot
│   │   ├── include
│   │   │   └── boot.inc
│   │   ├── loader.asm
│   │   └── mbr.asm
│   ├── build
│   ├── device
│   │   ├── timer.c
│   │   └── timer.h
│   ├── kernel
│   │   ├── debug.c
│   │   ├── debug.h
│   │   ├── global.h
│   │   ├── idt.asm
│   │   ├── init.c
│   │   ├── init.h
│   │   ├── interrupt.c
│   │   ├── interrupt.h
│   │   └── main.c
│   ├── lib
│   │   ├── kernel
│   │   │   ├── bitmap.c
│   │   │   ├── bitmap.h
│   │   │   ├── io.h
│   │   │   ├── print.asm
│   │   │   ├── print.h
│   │   │   ├── string.c
│   │   │   └── string.h
│   │   └── libint.h
│   ├── makefile
│   └── start.sh
└── hd60m.img

1 assert断言

1.1 断言简介

随着模块越来越多,程序出错的概率越来越大,为了方便调试,一个好的细观是在程序的关键部分设置哨兵,监督数据的正确定

断言是就是断定的意思,程序员断定在程序执行到此处的时候,某项数据的值应该是多少。

1.2 断言实现原理

assert断言是一个宏,而不是一个函数,因此当程序正式发布的时候,可以方便的将assert的语句通过宏的形式去掉。

就是:

#ifdef NODEBUG
    #define assert(cond) ((void)0)
#else
    #define assert(cond) \
        if(cond) \
        {   \
            \
        }else{\
            \
        }\
#endif

在编译中如果定义了NODEBUG宏那么编译的时候assert会被替换为((void)0)

然后,在assert在开Debug的时候,如果出错,应该能够打印出错代码所在的文件名,行号,和函数。这些使用gcc的预处理的语句即可。另外还希望打印出错的值。在不适用预处理的时候,无法实现这个功能,但是使用预处理,可以将一个变量变为字符串。

然后考虑,出错时候打印的信息:

void panic(char* filename,int line,char* func,char* cond)
{
    intr_disable();
    put_str("\n\n\n error:");
    put_str("filename: ");put_str(filename);put_str("\n");
    put_str("line:");put_int(line);put_str("\n");
    put_str("function: ");put_str(func);put_str("\n");
    put_str("condition: ");put_str(cond);put_str("\n");
    
    while(1){} // 用于停住程序
}
#define PANIC(cond) panic(__FILE__,__LINE__,__func__,cond)

#ifdef NODEBUG
    #define assert(cond) ((void)0)
#else
    #define assert(cond) \
        if(cond) \
        {   \
            \
        }else{\
            PANIC(#cond)
        }\
#endif

然后,intr_disable()关中断是因为,当断言为假,应该停住程序的时候,程序应该完全不执行,只执行循环,但是因为中断的存在,所以即使停在最后的while(1)循环,还是会产生中断,所以关了中断,这样程序完全的停住了。

1.3 debug.h

#ifndef __KERNEL_DEBUG_H
#define __KERNEL_DEBUG_H

void panic( char *filename, int line, const char *func, const char *cond );

#define PANIC( cond ) panic( __FILE__, __LINE__, __func__, cond )

#ifdef NDEBUG
#define ASSERT( cond ) ( (void) 0 )
#define BREAK() ( (void) 0 );

#else
#define ASSERT( cond )  \
    if ( cond ) {       \
    } else {            \
        PANIC( #cond ); \
    }
// 魔数断点
#define BREAK() __asm__( "xchg %%bx,%%bx" :: );

#endif /*__NDEBUG */

#endif /*__KERNEL_DEBUG_H*/

2 内存操作函数

1 思路

就是封装了操作内存的函数,memset

2 string.h/string.c

#ifndef __LIB_STRING_H
#define __LIB_STRING_H
#include "libint.h"
void     memset( void* dst_, uint8_t value, uint32_t size );
void     memcpy( void* dst_, const void* src_, uint32_t size );
int      memcmp( const void* a_, const void* b_, uint32_t size );
char*    strcpy( char* dst_, const char* src_ );
uint32_t strlen( const char* str );
int8_t   strcmp( const char* a, const char* b );
char*    strchr( const char* string, const uint8_t ch );
char*    strrchr( const char* string, const uint8_t ch );
char*    strcat( char* dst_, const char* src_ );
uint32_t strchrs( const char* filename, uint8_t ch );
#endif
#include "string.h"
#include "debug.h"
#include "global.h"
#include "print.h"
/* 将dst_起始的size个字节置为value */
void memset( void* dst_, uint8_t value, uint32_t size )
{

    ASSERT( dst_ != NULL );
    uint8_t* dst = ( uint8_t* )dst_;
    while ( size-- > 0 )
        *dst++ = value;
}

/* 将src_起始的size个字节复制到dst_ */
void memcpy( void* dst_, const void* src_, uint32_t size )
{
    ASSERT( dst_ != NULL && src_ != NULL );
    uint8_t*       dst = dst_;
    const uint8_t* src = src_;
    while ( size-- > 0 )
        *dst++ = *src++;
}

/* 连续比较以地址a_和地址b_开头的size个字节,若相等则返回0,若a_大于b_返回+1,否则返回-1
 */
int memcmp( const void* a_, const void* b_, uint32_t size )
{
    const char* a = a_;
    const char* b = b_;
    ASSERT( a != NULL || b != NULL );
    while ( size-- > 0 )
    {
        if ( *a != *b )
        {
            return *a > *b ? 1 : -1;
        }
        a++;
        b++;
    }
    return 0;
}

/* 将字符串从src_复制到dst_ */
char* strcpy( char* dst_, const char* src_ )
{
    ASSERT( dst_ != NULL && src_ != NULL );
    char* r = dst_;  // 用来返回目的字符串起始地址
    while ( ( *dst_++ = *src_++ ) )
        ;
    return r;
}

/* 返回字符串长度 */
uint32_t strlen( const char* str )
{
    ASSERT( str != NULL );
    const char* p = str;
    while ( *p++ )
        ;
    return ( p - str - 1 );
}

/* 比较两个字符串,若a_中的字符大于b_中的字符返回1,相等时返回0,否则返回-1. */
int8_t strcmp( const char* a, const char* b )
{
    ASSERT( a != NULL && b != NULL );
    while ( *a != 0 && *a == *b )
    {
        a++;
        b++;
    }
    /* 如果*a小于*b就返回-1,否则就属于*a大于等于*b的情况。在后面的布尔表达式"*a
     * > *b"中,
     * 若*a大于*b,表达式就等于1,否则就表达式不成立,也就是布尔值为0,恰恰表示*a等于*b
     */
    return *a < *b ? -1 : *a > *b;
}

/* 从左到右查找字符串str中首次出现字符ch的地址(不是下标,是地址) */
char* strchr( const char* str, const uint8_t ch )
{
    ASSERT( str != NULL );
    while ( *str != 0 )
    {
        if ( *str == ch )
        {
            return (
                char* )str;  // 需要强制转化成和返回值类型一样,否则编译器会报const属性丢失,下同.
        }
        str++;
    }
    return NULL;
}

/* 从后往前查找字符串str中首次出现字符ch的地址(不是下标,是地址) */
char* strrchr( const char* str, const uint8_t ch )
{
    ASSERT( str != NULL );
    const char* last_char = NULL;
    /* 从头到尾遍历一次,若存在ch字符,last_char总是该字符最后一次出现在串中的地址(不是下标,是地址)*/
    while ( *str != 0 )
    {
        if ( *str == ch )
        {
            last_char = str;
        }
        str++;
    }
    return ( char* )last_char;
}

/* 将字符串src_拼接到dst_后,将回拼接的串地址 */
char* strcat( char* dst_, const char* src_ )
{
    ASSERT( dst_ != NULL && src_ != NULL );
    char* str = dst_;
    while ( *str++ )
        ;
    --str;  // 别看错了,--str是独立的一句,并不是while的循环体
    while ( ( *str++ = *src_++ ) )
        ;  // 当*str被赋值为0时,此时表达式不成立,正好添加了字符串结尾的0.
    return dst_;
}

/* 在字符串str中查找指定字符ch出现的次数 */
uint32_t strchrs( const char* str, uint8_t ch )
{
    ASSERT( str != NULL );
    uint32_t    ch_cnt = 0;
    const char* p      = str;
    while ( *p != 0 )
    {
        if ( *p == ch )
        {
            ch_cnt++;
        }
        p++;
    }
    return ch_cnt;
}

3 位图

1 思路

位图是一种资源管理方式。

位是指bit,一个字节有8个bit。位图则是每一个实物对应一个位的意思。这是一种数据结构

我们将来使用位图来管理内存,每一个4K的页对应位图上的一个位,其1,0则表示该页是否被使用了。

首先,需要一个定义一个结构用来记录一个位图。

struct Bitmap
{
    uint32_t len;
    uint8_t bits;// 用指针的原因是,并不知道需要多少的空间
};

初始化位图结构的函数bitmap_init()的函数,主要是将len长度的字节,全部清零。

一个判断某个位是否被用了的函数bitmap_test(struct Bitmap*,uint32_t idx):工作原理是先计算idx所在的字节,然后计算idx在所在字节的位,其实就是除法和取模,按照数组访问元素的方式定位到元素,然后和1做左移后的结果进行&操作

一个连续分配cnt个位的函数bitmap_get(struct Bitmap*,unint32_t cnt):这个函数比较复杂:首先从头开始,对每个字节进行和0xFF的&操作,找到有空余位的字节。这过程中要判断越界。然后如果是取1位,则直接返回该位,如果取多位,那么需要从该位开始依次向下,去寻找能够分配这么多位的第一个位,并返回。

还有一个用于设置位使用和回收的函数bitmap_set(struct Bitmap,uint32_t idx,int_t v):和bitmap_test()差不多

2 bitmap.h/bitmap.c

#ifndef __LIB_KERNEL_BITMAP_H
#define __LIB_KERNEL_BITMAP_H
#include "global.h"
#define BITMAP_MASK 1
struct Bitmap
{
    uint32_t len;
    uint8_t* bits;
};

void bitmap_init( struct Bitmap* btmp );
int bitmap_test( struct Bitmap* btmp, uint32_t bit_idx );
int bitmap_get( struct Bitmap* btmp, uint32_t cnt );
void bitmap_set( struct Bitmap* btmp, uint32_t bit_idx, int8_t value );
#endif

#include "bitmap.h"
#include "debug.h"
#include "interrupt.h"
#include "libint.h"
#include "print.h"
#include "string.h"

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

/* 判断bit_idx位是否为1,若为1则返回true,否则返回false */
bool bitmap_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个位,成功则返回其起始位下标,失败返回-1 */
int bitmap_get( struct Bitmap* btmp, uint32_t cnt )
{
    uint32_t idx_byte = 0;  // 用于记录空闲位所在的字节
                            /* 先逐字节比较,蛮力法 */
    while ( ( 0xff == btmp->bits[ idx_byte ] ) && ( idx_byte < btmp->len ) )
    {
        /* 1表示该位已分配,所以若为0xff,则表示该字节内已无空闲位,向下一字节继续找
         */
        idx_byte++;
    }
    ASSERT( idx_byte < btmp->len );
    if ( idx_byte == btmp->len )
    {  // 若该内存池找不到可用空间
        return -1;
    }

    /* 若在位图数组范围内的某字节内找到了空闲位,
     * 在该字节内逐位比对,返回空闲位的索引。*/
    int idx_bit = 0;
    /* 和btmp->bits[idx_byte]这个字节逐位对比 */
    while ( ( uint8_t )( BITMAP_MASK << idx_bit ) & btmp->bits[ idx_byte ] )
    {
        idx_bit++;
    }

    int bit_idx_start = idx_byte * 8 + idx_bit;  // 空闲位在位图内的下标
    if ( cnt == 1 )
    {
        return bit_idx_start;
    }

    uint32_t bit_left = ( btmp->len * 8 - bit_idx_start );  // 记录还有多少位可以判断
    uint32_t next_bit = bit_idx_start + 1;
    uint32_t count    = 1;  // 用于记录找到的空闲位的个数

    bit_idx_start = -1;  // 先将其置为-1,若找不到连续的位就直接返回
    while ( bit_left-- > 0 )
    {
        if ( !( bitmap_test( btmp, next_bit ) ) )
        {  // 若next_bit为0
            count++;
        }
        else
        {
            count = 0;
        }
        if ( count == cnt )
        {  // 若找到连续的cnt个空位
            bit_idx_start = next_bit - cnt + 1;
            break;
        }
        next_bit++;
    }
    return bit_idx_start;
}

/* 将位图btmp的bit_idx位设置为value */
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 );
    }
}

猜你喜欢

转载自www.cnblogs.com/perfy576/p/9139135.html