没腿的鸟 发表于 2025-4-16 20:02:44

《操纵系统真象还原》第八章(1)——内存管理系统

前言

前两天北方大风,不太好去实行室,《真象还原》的学习有所搁置,这两天继承。
第八章标题内存管理系统,下面分5节。分别是makefile相关、实现assert断言、实现字符串操纵函数、位图相关、内存管理系统。我们的博客也分成这几部分。
贴一下我的第七章博客链接和本章参考的love6博客链接:
《操纵系统真象还原》第七章(1)——中断-CSDN博客
《操纵系统真象还原》第七章(2)——中断-CSDN博客
《操纵系统真象还原》第八章 ---- 初入内存管理系统 涉足MakeFile 了解摸谈一二_makefile内存管理-CSDN博客
makefile相关

简单来说makefile是一种脚本,由make指令实行。通过查验依赖文件的mtime是否比目标文件mtime新,决定是否实行makefile里的指令。
这部分内容在P358页,博客不再具体阐明,使用时多查阅即可。
根本语法
目标文件:依赖文件
命令
跳转到目标处实行
实行完一个目标后就退出,不再实行背面的指令,格式如下
make 目标名称
伪目标
.PHONY:伪目标名
常见伪目标名称截图
https://i-blog.csdnimg.cn/img_convert/2c65be9b7b67ac194169e2b970812a1b.png
自定义变量与系统变量
自定义变量定义的格式:变量名=值(字符串)
自定义变量引用的格式:$(变量名)
系统变量表截图
https://i-blog.csdnimg.cn/img_convert/5c3399b864ffa598ae1a12c2ae4daaf9.png
隐含规则
反斜杠\是多行之间的连续符。井号#用来注释。
在缺少依赖文件时,make指令按照一些默认的习惯,用隐含的规则完善依赖文件。简单的说就是主动编译,根据源码文件.c、.cc、.C、.p生成目标文件.o。
主动化变量
一种能代表一类文件文件名的符号。


[*]                                        @                            ,表现规则中的目标文件名聚集,如果存在多个目标文件,                                  @,表现规则中的目标文件名聚集,如果存在多个目标文件,                     @,表现规则中的目标文件名聚集,如果存在多个目标文件,@则表现其中每一个文件名。助记,’@’ 很像是at,aim at,表现对准目标。
[*]$<,表现规则中依赖文件中的第1个文件。助记,‘<’很像是聚集的最左边,也就是第1个。
[*]                                                             ,                                    表现规则中所有依赖文件的聚集,如果聚集中有重复的文件,                                  ^,表现规则中所有依赖文件的聚集,如果聚集中有重复的文件,                     ,表现规则中所有依赖文件的聚集,如果聚集中有重复的文件,会主动去重。助记,’’很像从上往下 罩的动作,能罩住很大的范围,所以称为聚集。
[*]$?,表现规则中,所有比目标文件 mtime 更新的依赖文件聚集。助记,’?’表现疑问,make 最大的疑 问就是依赖文件的mtime是否比目标文件的mtime要新。
模式规则
就一句话,%用来匹配恣意多个非空字符。好比%.o代表所有以.o为结尾的文件,g%s.o是以字符g开头的所有以.o 为结尾的文件,make会拿这个字符串模式去文件系统上查找文件,默认为当前路径下。
实现assert断言

简单来说就是写一个debug程序,如果内核运行过程中出错,这个程序告诉我们哪个文件哪个函数哪行出了问题,把相关信息打印到屏幕上,帮助我们解决问题。下面直接给出代码。
interrupt.c

新增部分是两行宏定义和四个函数。关于这个罗列体还有四个函数的声明,看下面的interrupt.h。
#define EFLAGS_IF 0x00000200   // 中断标志位IF,在EFLAGS寄存器中
#define GET_EFLAGS_IF(EFLAGS_VAR) asm volatile ("pushfl ; popl %0" : "=g" (EFLAGS_VAR)) // 获取中断标志位IF

/* 开启中断并返回开启中断之前的状态 */
enum intr_status intr_enable(void) {
   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");        // 开中断
      return old_status;
   }
}

/* 关闭中断并返回在关闭中断之前的状态 */
enum intr_status intr_disable(void) {
   enum intr_status old_status;
   if(INTR_OFF == intr_get_status()) {
      old_status = INTR_OFF;        // 如果当前中断已经关闭,则直接返回
      return old_status;
   }
   else {
      old_status = INTR_ON;        // 如果当前中断打开,则关闭中断
      asm volatile ("cli" : : : "memory");        // 关中断
      return old_status;
   }
}

/* 将中断状态设置为status */
enum intr_status intr_set_status(enum intr_status status) {
   return (status & INTR_ON) ? intr_enable() : intr_disable();
}

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

新增部分是一个罗列声明,四个函数声明,直接贴出完整代码
#ifndef __KERNEL_INTERRUPT_H
#define __KERNEL_INTERRUPT_H
#include "stdint.h"
typedef void* intr_handler;
void idt_init(void);
/* 定义中断的两种状态 */
/*关于枚举体enum,第一项默认值0,后面每项默认+1*/
enum intr_status {
    INTR_OFF,        // 关中断
    INTR_ON          // 开中断
};
enum intr_status intr_get_status(void);        // 获取中断状态
enum intr_status intr_set_status(enum intr_status status);        // 设置中断状态
enum intr_status intr_enable(void);          // 打开中断
enum intr_status intr_disable(void);        // 关闭中断
#endif
debug.h

放在kernel文件夹下。主要内容是assert的声明和打印函数的声明,代码如下。
#ifndef __KERNEL_DEBUG_H
#define __KERNEL_DEBUG_H
void panic_spin(char* filename, int line, const char* func, const char* condition);
/* __VA_ARGS__代表若干个参数,对应前面的... */
#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*/
#endif /*结束__KERNEL_DEBUG_H*/
debug.c

同样放在kernrl文件夹下,实现报错打印函数
#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!!!kernel panic!!!\n");
    put_str("filename:");put_str((char*)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);
}
main.c

简单修改一下内核,用来测试assert
#include "print.h"
#include "init.h"
#include "debug.h"
int main(void){
        put_str("HongBai's OS\n");
        init_all();
        asm volatile("sti");
        ASSERT(1 == 2); // 断言失败,会调用panic_spin函数
        while(1);
}
makefile

相当于是写脚本,目的是编译、毗连、写入之前的文件,整体cv的love6的博客,修改了路径。如果要cv下面的代码,记得修改路径。
BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS =-m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
      $(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
      $(BUILD_DIR)/debug.o

##############   c代码编译                           ###############
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
      lib/kernel/stdint.h kernel/init.h
        $(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
      lib/kernel/stdint.h kernel/interrupt.h device/timer.h
        $(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
      lib/kernel/stdint.h kernel/global.h kernel/io.h lib/kernel/print.h
        $(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/kernel/stdint.h\
      kernel/io.h lib/kernel/print.h
        $(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
      lib/kernel/print.h lib/kernel/stdint.h kernel/interrupt.h
        $(CC) $(CFLAGS) $< -o $@


##############    汇编代码编译    ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.S
        $(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/print.o: lib/kernel/print.S
        $(AS) $(ASFLAGS) $< -o $@

##############    链接所有目标文件    #############
$(BUILD_DIR)/kernel.bin: $(OBJS)
        $(LD) $(LDFLAGS) $^ -o $@

.PHONY : mk_dir hd clean all

mk_dir:
        if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi

hd:
        dd if=$(BUILD_DIR)/kernel.bin \
         of=/home/hongbai/bochs/bin/c.img \
         bs=512 count=200 seek=10 conv=notrunc

clean:
        cd $(BUILD_DIR) && rm -f./*

build: $(BUILD_DIR)/kernel.bin

all: mk_dir build hd

测试

我们make all,效果如下
https://i-blog.csdnimg.cn/img_convert/4dec7816a73427d272bc110ddecb3813.png
运行一下bochs
https://i-blog.csdnimg.cn/img_convert/d4e4b0392c9355196a46e934480e2b2c.png
实现了99%功能,就是打印的有点歪,bug应该出现在print.S里,留待后续修改。
debug(无果)

先去研究了一下print.S,重写了一下换行回车部分代码
;关于换行符:我们按照习惯,把换行回车结合起来,实现日常敲下enter的效果,所以换行=换行+回车
.is_line_feed:                        ;换行操作:光标挪到下一行行首
        ;内联的回车逻辑
        xor dx,dx
        mov ax,bx
        mov si,80
        div si       
        sub bx,dx
        ;单独的换行逻辑
        add bx,80                ;目前是本行行首,再加80就是下一行行首
        cmp bx,2000                ;是否清屏
        jl .set_cursor                ;bx<2000,即还在这一屏,就执行

.is_backspace:                        ;退格操作:光标bx前移一个显存位置,待删除位置补空格字符
        dec bx                        ;自减,前移到上一个坐标
        shl bx,1                ;左移1位,等价于*2,坐标位置*2=实际字节偏移量
        mov byte ,0x20
        inc bx
        mov byte ,0x07        ;上面三行代码等价于mov word ,0x0720,即打印一个空格字符,属性是黑底白字。
        shr bx,1                ;右移1位,将地址重新转化为坐标
        jmp .set_cursor                ;将光标转移到新位置
然而仍旧没解决问题
页: [1]
查看完整版本: 《操纵系统真象还原》第八章(1)——内存管理系统