真象还原操作系统_第八章_内存管理系统

一、makefile简介

1. makefile是什么

  • make和makefile:
    • 依赖关系定义在makefile文件中,make程序通过解析makefile文件自动找出变更的文件以及依赖此变更文件的相关文件,然后对所有受影响的相关文件执行事先定义好的命令规则。
    • make和makefile并不编译文件。
    • 关系类似于脚本解析器和脚本语言文件。makefile相当于脚本语言文件,其中所写的内容必须遵循make所定义的语法;make程序是文件makefile的解析器,它定义了各种关键字、语法结构、函数、变量等。make程序解析makefile中的内容,从而产生不同的行为。

2. makefile基本语法

  • makefile基本语法:
目标文件:依赖文件
[Tab]命令
  • makefile基本语法包括三部分
    1. 目标文件是指此规则中想要生成的文件,可以是.o的目标文件,也可以是可执行文件,也可以是个伪目标。
    2. 依赖文件是指要生成此规则中的目标文件,需要哪些文件,是列表。
    3. 命令是指此规则中要执行的动作,这些动作是各种shell命令。命令可以有多个,但是一个命令要单独占一行,在行首必须以Tab开头。
    4. 意义:要想生成目标文件,需要提前准备好依赖文件。如果依赖文件列表中任意一个文件比目标文件新,就去执行规则中的命令。
  • 在Linux中,文件分为属性和数据两部分。每个文件有三种时间,分别用于记录与文件属性和数据相关的时间:
时间英文作用
atimeaccess time访问文件数据部分的时间,每次读取文件数据部分时都会更新atime
ctimechange time文件属性或数据的改变时间,每当文件的属性或数据被修改时,就会更新ctime
mtimemodify time文件数据部分的修改时间,每次文件的数据被修改时就会更新mtime
  • make程序分别获取依赖文件和目标文件的mtime,对比依赖文件的mtime是否比目标文件的mtime新,就知道是否要执行规则中的命令。
  • stat命令可以查看时间
//例如:
cat -n makefile
//makefile文件:若文件2的mtime比文件1的mtime新,就调用echo命令打印字符串
1:2
	echo "makefile test ok"
  • 若文件是最新的,则make不会执行echo,会提示:
    在这里插入图片描述
  • 当makefile中有很多目标时,可以用目标名称作为make的参数,采用“make 目标名称”的方式单独执行目标名称处的规则。即使后面有其他目标也不会执行。
    在这里插入图片描述

3. 伪目标

  • make规定:当规则中不存在依赖文件时,这个目标文件名就称为——伪目标。
  • 伪目标:不产生真正的目标文件,所以也就不需要依赖文件。伪目标就是单纯地执行指令。
//举例:
all:
	@echo "test ok"

在这里插入图片描述

  • 伪目标不能与真实目标文件同名,可以用关键字“.PHONY”来修饰伪目标,格式为:.PHONY:伪目标名。
.PHONY:clean
clean:
	rm ./build/*.o	//删除编译过程中的.o文件

在这里插入图片描述

4. make:递归式推导目标

  • 在makefie的目标中,是以递归的方式逐层向上查找目标的。
  • 例:通过make all来编译test.bin。make发现all的依赖文件test.bin不存在,于是去找test.bin的目标文件test1.o和test2.o。发现他们都没有,于是根据目标文件test1.c和test2.c来编译得到目标文件。最后生成可执行文件test.bin进行编译。
    在这里插入图片描述
    在这里插入图片描述

5. 自定义变量与系统变量

  • makefile中定义变量:变量名=值(字符串),多个值之间用空格分开。
  • 引用变量:$(变量名)。在引用变量时,变量名就会被其值(字符串)替换。
  • NOTE:字符串不加引号。
test2.o:test2.c
	gcc -c -o test2.o test2.c
test1.o:test1.c
	gcc -c -o test1.o test1.c
objfiles = test1.o test2.o
test.bin:$(objfiles)
	gcc -o test.bin $(objfiles)
all:test.bin
	@echo "compile done"

在这里插入图片描述

  • 在命令相关的系统变量是有默认值的,一般参数相关的变量没有默认值,可以通过重新赋值的方式修改。
    在这里插入图片描述

6. 隐含规则

  • ‘’:一行写不下,在行尾添加字符 ‘’,下一行的内容便被认为是一行的。
  • #:注释符。
  • 隐含规则只限于那些编译过程中基本固定的依赖关系。
  • 若想通过隐含规则自动推导生成目标,存在于文件系统上的文件,除扩展名之外的文件名必须相同。如x.o的C源文件必须为x.c,才能通过隐含规则生成x.o。
objfiles = test1.o test2.o
test.bin:$(objfiles)
	gcc test.bin $(objfiles)
all:test.bin
	@echo "compile done"

在这里插入图片描述

  • 常见的隐含规则:
  1. C程序
    • x.o的生成依赖于x.c,生成x.o的命令为:
    $(CC) -c $(CPPFLAGS) $(CFLAGS)
    
  2. C++程序
    • x.o的生成依赖于x.cc或x.C,生成x.o的命令为:
    $(CXX) -c $(CPPFLAGS) $(CFLAGS)
    

7. 自动化变量

  • 自动化变量代表一组文件名。
自动化变量代表文件
$@规则中目标文件名的集合
$<规则中依赖文件的第一个文件
$^规则中所有依赖文件的集合
$?规则中,所有比目标文件mtime更新的依赖文件集合
test2.o:test2.c
	gcc -c -o test2.o test2.c
test1.o:test1.c
	gcc -c -o test1.o test1.c
objfiles = test1.o test2.o
test.bin:$(objfiles)
	gcc -o $@ $^
all:test bin
	@echo :"compile done"

8. 规则模式

  • 模式:字符串模子。
  • %:用来匹配任意多个非空字符。比如%.o代表所有以.o结尾的文件,g%s.o代表以g开头的所有以.o结尾的文件。
%.o:%.c
	gcc -c -o $@ %^
objfiles = test1.o test2.o
test.bin:objfiles
	gcc -o $@ $^
all:test.bin
	@echo "compile done"

二、实现assert断言

1. 实现开、关中断

  • 断言:程序员断定程序运行到此处时,某数据的值一定为多少多少。
  • 断言分类:
    1. 为内核系统使用的ASSERT。
    2. 为用户进程使用的assert。
  • 内核的ASSERT
    • 当内核运行出现问题时,多属于严重错误,没必要继续运行下去。
    • ASSERT排查出错误后,最好在关中断的情况下打印报错信息。

--------------------代码----------------------

  • 关中断函数
  • “boot/kernel/interrupt.c”
#include "interrupt.h"
#include "stdint.h"
#include "global.h"
#include "io.h"
#include "print.h"

#define IDT_DESC_CNT 0x21           //目前共支持的中断数,33
#define PIC_M_CTRL 0x20             //主片的控制端口是0x20
#define PIC_M_DATA 0x21             //主片的数据端口是0x21
#define PIC_S_CTRL 0xa0             //从片的控制端口是0xa0
#define PIC_S_DATA 0xa1             //从片的数据端口是0xa1
#define EFLAGS_IF 0x00000200	//eflags寄存器中的if=1
#define GET_FLAGS(EFLAG_VAR) asm volatile("pushfl;popl %0":"=g"(EFLAG_VAR))	
//"=g" 指示编译器将结果放在任意通用寄存器中,并将其赋值给 EFLAG_VAR。
//pushfl 指令将标志寄存器 EFLAGS 的值入栈,然后 popl %0 指令将栈顶的值弹出到指定的操作数 %0 中。
//当调用 GET_FLAGS(EFLAG_VAR) 宏时,它将 EFLAGS 寄存器的值存储到 EFLAG_VAR 变量中。

/*中断门描述符结构体*/
struct gate_desc{
    uint16_t func_offset_low_word;  //低32位——0~15位:中断处理程序在目标代码段内的偏移量的第15~0位
    uint16_t selector;              //低32位——16~31位:目标CS段选择子
    uint8_t dcount;                 //高32位——0~7位:此项为双字计数字段,是门描述符中的第4字节,为固定值
    uint8_t attribute;              //高32位——8~15位:P+DPL+S+TYPE
    uint16_t func_offset_high_word; //高32位——16~31位:中断处理程序在目标代码段内的偏移量的第16~31位
};

//静态函数声明,非必须
static void make_idt_desc(struct gate_desc* p_gdesc,uint8_t attr,intr_handler function);
static struct gate_desc idt[IDT_DESC_CNT];  //IDT是中描述符表,实际上是中断门描述符数组

extern intr_handler intr_entry_table[IDT_DESC_CNT]; //指针格式应该与数组类型一致,这里intr_entry_table中的元素类型就是function,就是.text的地址。所以用intr_handle来引用。声明引用定义在kernel.S中的中断处理函数入口数组
char* intr_name[IDT_DESC_CNT];      //用于保存异常的名字
intr_handler idt_table[IDT_DESC_CNT];   //idt_table为函数数组,里面保持了中断处理函数的指针

/*创建中断门描述符*/
static void make_idt_desc(struct gate_desc* p_gdesc,uint8_t attr,intr_handler function){ //intr_handler是个空指针类型,仅用来表示地址 
    //中断门描述符的指针、中断描述符内的属性、中断描述符内对应的中断处理函数
    p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000ffff;
    p_gdesc->selector = SELECTOR_K_CODE;
    p_gdesc->dcount = 0;
    p_gdesc->attribute = attr;
    p_gdesc->func_offset_high_word = ((uint32_t)function & 0xffff0000) >> 16;
}

/*初始化中断描述符表*/
static void idt_desc_init(void){
    int i;
    for(i = 0;i < IDT_DESC_CNT;i++){
        make_idt_desc(&idt[i],IDT_DESC_ATTR_DPL0,intr_entry_table[i]);
    }
    put_str("   idt_desc_init done\n");
}

/*初始化可编程中断控制器8259A*/
static void pic_init(void){
    /*初始化主片*/
    outb(PIC_M_CTRL,0x11);          //ICW1:边沿触发,级联8259,需要ICW4
    outb(PIC_M_DATA,0x20);          //ICW2:起始中断向量号为0x20,也就是IR[0-7]为0x20~0x27
    outb(PIC_M_DATA,0x04);          //ICW3:IR2接从片
    outb(PIC_M_DATA,0x01);          //ICW4:8086模式,正常EOI
    /*初始化从片*/
    outb(PIC_S_CTRL,0x11);          //ICW1:边沿触发,级联8259,需要ICW4
    outb(PIC_S_DATA,0x28);          //ICW2:起始中断向量号为0x28,也就是IR[8-15]为0x28~0x2F
    outb(PIC_S_DATA,0x02);          //ICW3:设置从片连接到主片的IR2引脚
    outb(PIC_S_DATA,0x01);          //ICW4:8086模式,正常EOI
    /*打开主片上IR0,也就是目前只接受时钟产生的中断*/
    outb(PIC_M_DATA,0xfe);
    outb(PIC_S_DATA,0xff);
    put_str("   pic_init done\n");
}

/*通用的中断处理函数,一般用在异常出现时的处理*/
static void general_intr_handler(uint8_t vec_nr){
    if(vec_nr == 0x27 || vec_nr == 0x2f){   //IRQ7和IRQ5会产生伪中断,无需处理;0x2f是从片8259A的最后一个IRQ引脚,保留项
        return;
    }
    put_str("int vector : 0x");
    put_int(vec_nr);
    put_char('\n');
}

/*初始化idt_table*/
static void exception_init(void){
    //将idt_table的元素都指向通用的中断处理函数,名称为unknown
    int i;
    for(i = 0;i < IDT_DESC_CNT;i++){
        idt_table[i] = general_intr_handler;    //默认为general_intr_handler,以后会由register_handler来注册具体函数
        intr_name[i] = "unknown";
    }
    intr_name[0] = "#DE Divide Error";
    intr_name[1] = "#DB Debug Exception";
    intr_name[2] = "NMI Interrupt";
    intr_name[3] = "BP Breakpoint Exception";
    intr_name[4] = "#OF Overflow Exception";
    intr_name[5] = "#BR BOUND Range Exceeded Exception";
    intr_name[6] = "#UD Invalid Opcode Exception";
    intr_name[7] = "#NM Device Not Available Exception";
    intr_name[8] = "#DF Double Fault Exception";
    intr_name[9] = "Coprocessor Segment Overrun";
    intr_name[10] = "#TS Invalid TSS Exception";
    intr_name[11] = "#NP Segment Not Present";
    intr_name[12] = "#SS Stack Fault Exception";
    intr_name[13] = "#GP General Protection Exception";
    intr_name[14] = "#PF Page-Fault Exception";
    //intr_name[15]是intel保留项,未使用
    intr_name[16] = "#MF x87 FPU Floating-Point Error";
    intr_name[17] = "#AC Alignment Check Exception";
    intr_name[18] = "#MC Machine-Check Exception";
    intr_name[19] = "#XF SIMD Floating-Point Exception";
}

/*完成所有有关中断的初始化工作*/
void idt_init(){
    put_str("idt_init start\n");
    idt_desc_init();                //初始化中断描述符表
    exception_init();
    pic_init();                     //初始化8259A
    /*加载idt*/
    // uint64_t idt_operand = ((sizeof(idt)-1) | (uint64_t)((uint32_t)idt << 16)); //书上是错的
    uint64_t idt_operand = ((sizeof(idt)-1) | ((uint64_t)(uint32_t)idt << 16)); //低16位是idt的大小,高48位是IDT的基址。因为idt是32位,左移16位后会丢失高16位,所以先转换为64位再左移
    asm volatile("lidt %0" : : "m" (idt_operand));   //加载IDT,IDT的0~15位是表界限,16~47位是表基址
    put_str("idt_init done\n");
}

/*开中断,并返回开中断前的状态*/
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");	//开中断
		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
		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();
}

/*获取当前中断状态*/
enum intr_status intr_get_status(){
	uint32_t eflags = 0;
	GET_FLAGS(eflags);
	return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}
  • “/kernel/interrupt.h”
#ifndef __KERNEL_INTERRUPT_H
#define __KERNEL_INTERRUPT_H
#include "stdint.h"
typedef void* intr_handler;
void idt_init(void);
/*定义中断的两种状态:
INTR_OFF值为0,表示关中断
INTR_ON 值为1,表示开中断*/
enum intr_status{
	INTR_OFF = 0,
	INTR_ON = 1
};
//枚举常量在 C 语言中被赋予默认的整数值,按照声明的顺序从 0 开始递增。因此,在这个例子中,INTR_OFF 的值为 0,INTR_ON 的值为 1。也可以显式地为枚举常量指定特定的值。

enum intr_status intr_get_status(void);
enum intr_status intr_set_status(enum intr_status);
enum intr_status intr_enable(void);
enum intr_status intr_disable(void);
#endif

2. 实现断言ASSERT

  • C语言中使用ASSERT:ASSERT(条件表达式);
  • C语言中ASSERT是用宏来定义的,原理是判断传给ASSERT的表达式是否成立。成立则什么都不做;不成立则打印错误信息。

--------------------代码----------------------

  • “boot/kernel/debug.h”
#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   //CONDITION判断为真,将ASSERT变成0,相当于删除
    #define ASSERT(CONDITION) \
    if(CONDITION){}else{ \
        PANIC(#CONDITION);	}//符号#让编译器将宏的参数转化为字符串字面量
    #endif 
#endif
  • “/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_str(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);
}
  • “/kernel/main.c”
#include "print.h"
#include "init.h"
#include "debug.h"
int main(void){
	put_str("I am kernel\n");
	init_all();
	ASSERT(1==2);
	while(1);
	return 0;
}

3. 通过makefile编译

--------------------代码----------------------

  • “boot//makefile”
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
##-fno-builtin是告诉编译器不要采用内部函数 -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
## OBJS用来存储所有目标文件名,不要用%.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 lib/kernel/io.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/timer.o:device/timer.c device/timer.h lib/kernel/stdint.h lib/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	
###fi为终止符

hd:	
	dd if=$(BUILD_DIR)/kernel.bin of=/home/lily/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc

clean:	##将build目录下文件清空
	cd $(BUILD_DIR) && rm -f ./*

build:$(BUILD_DIR)/kernel.bin	##编译kernel.bin,只要执行make build就是编译文件

all:mk_dir build hd	
##依次执行伪目标mk_dir build hd,只要执行make all就是完成了编译到写入硬盘的全过程
  • 执行make all
    在这里插入图片描述
  • 启动bochs
bin/bochs -f bochsrc.disk

在这里插入图片描述

三、实现字符串操作函数

--------------------代码----------------------

  • “boot/lib/string.c”
#include "string.h"
#include "global.h"
#include "debug.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--)
		*dst++ = value;
}

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

//连续比较地址a_和地址b_开头的size个字节,若相等则返回0,大于返回1,小于返回-1
int memcmp(const char* a_,const char* b_,uint32_t size){
	const char* a = a_;
	const char* b = b_;
	ASSERT(a != NULL && b != NULL);
	while(size--){
		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* temp = dst_;
	while((*temp++ = *src_++));
	return dst_;
}

//返回字符串长度
//[做了修改]
uint32_t strlen(const char* str){
	ASSERT(str != NULL);
	uint32_t len = 0;
	const char* p = str;
	while(*p++){
		len++;
	}
	return len;
}

//比较两个字符串
int8_t strcmp(const char* a,const char* b){
	ASSERT(a != NULL && b != NULL);
	while(*a != 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;
		str++;
	}
	return NULL;
}

//有后向前找字符串str中首次出现字符ch的地址
char* strtchr(const char* str,const uint8_t ch){
	ASSERT(str != NULL);
	char* last_str = NULL;
	while(*str != 0){
		if(*str == ch)
			last_str = (char*)str;
		str++;
	}
	return last_str;
}

//将字符串src_拼接到dst_后,返回拼接的字符串地址
char* strcat(char* dst_,const char* src_){
	ASSERT(dst_ != NULL && src_ != NULL);
	char* p = dst_;
	while(*p++);
	p--;	//消除'\0'
	while((*p++ = *src_++));
	return dst_;
}

//在字符串str中查找字符ch出现的次数
uint32_t strchrs(const char* str,uint8_t ch){
	ASSERT(str != NULL);
	uint32_t cnt = 0;
	while(*str != 0){
		if(*str == ch)
			cnt++;
		str++;
	}
	return cnt;
}
  • “/home/lily/OS/boot/lib/string.h”
#ifndef __LIB_STRING_H
#define __LIB_STRING_H
#include "stdint.h"
#define NULL 0

void memset(void* dst_,uint8_t value,uint32_t size);
void memcpy(void* dst_,const void* src_,uint32_t size);
int memcmp(const char* a_,const char* 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* str,const uint8_t ch);
char* strtchr(const char* str,const uint8_t ch);
char* strcat(char* dst_,const char* src_);
uint32_t strchrs(const char* str,uint8_t ch);

#endif
  • “/home/lily/OS/boot/lib/kernel/stdint.h”
  • 如果编译器识别不了bool的话:
#ifndef __LIB_STDINT_H
#define __LIB_STDINT_H

typedef signed char int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;
typedef signed long long int int64_t;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long int uint64_t;
typedef enum{false = 0,true = 1} bool;

#endif

四、位图bitmap及其函数实现

1. 位图的定义与实现

  • 位图的本质是一串二进制位,用字节型数组实现。
  • 在一个字节里,位是从右向左排序的:例如0xff。所以在位图中,一字节中的最右位为0位,做左位为7位。
    在这里插入图片描述

--------------------代码----------------------

  • “boot/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
  • “boot/lib/kernel/bitmap.c”
#include "bitmap.h"
#include "stdint.h"
#include "string.h"
#include "print.h"
#include "interrupt.h"
#include "debug.h"

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

//判断bit_idx位是否为1 [修改]
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
int bitmap_scan(struct bitmap* btmp,uint32_t cnt){
    uint32_t idx_byte = 0;
    //找到空闲的首字节
    while ((btmp->bits[idx_byte] == 0xff) && (idx_byte < btmp->btmp_bytes_len))
        idx_byte++;
    //若idx_byte>len则返回错误,=len则返回-1
    ASSERT(idx_byte <= btmp->btmp_bytes_len);
    if(idx_byte == btmp->btmp_bytes_len)
        return -1;
    //找到空间的首位
    int idx_bit = 0;
    while (btmp->bits[idx_byte] & (uint8_t)(BITMAP_MASK << idx_bit))
        idx_bit++;

    int bit_idx_start = -1;    //返回首地址
    if(cnt == 1){
        bit_idx_start = idx_byte*8 + idx_bit;
        return bit_idx_start;
    }
    uint32_t bit_left = btmp->btmp_bytes_len*8 - (idx_byte*8 + idx_bit);
    uint32_t next_bit = idx_byte*8 + idx_bit + 1;
    uint32_t count = 1;

    while (bit_left > 0){
        if(!bitmap_scan_test(btmp,next_bit))
            count++;
        else
            count = 0;
        if(count == 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;
    if(value){
        btmp->bits[byte_idx] |= (BITMAP_MASK << bit_odd);
    }else{
        btmp->bits[byte_idx] &= ~(BITMAP_MASK << bit_odd);
    }
};

五、内存管理系统

1. 内存池规划

  • 为保证OS的正常运行,将物理内存池分为用户物理内存池和内核物理内存池。
  • 内存池中的内存单位为页,4KB。
  • 内存分配过程:
    1. 内核申请内存:当内核申请内存时,从内核自己的虚拟地址池中分配虚拟地址,再从内核物理池(内核专用)中分配物理内存,然后在内核自己的页表中将两种地址建立好映射关系。
    2. 用户进程申请内存:OS从用户进程自己的虚拟地址池中分配空闲虚拟地址,然后再从用户物理内存池(所有用户进程共享的)中分配空闲的物理内存,然后在该用户进程自己的页表中将这两种地址建立好映射关系。
      在这里插入图片描述
  • 多个进程可以拥有相同的虚拟地址,是因为这些虚拟地址所对应的物理地址是不同的。但在同一个进程中的虚拟地址是唯一的,这通常是由链接器为其分配的,由链接器负责虚拟地址的唯一性。
    在这里插入图片描述
    在这里插入图片描述

--------------------代码----------------------

  • “/home/lily/OS/boot/kernel/memory.h”
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"
//虚拟地址池,用于虚拟地址管理
struct virtual_addr
{
    struct bitmap vaddr_bitmap; //虚拟地址用到的位图结构
    uint32_t vaddr_start;       //虚拟地址起始地址
};

extern struct pool kernel_pool, user_pool;
void mem_init(void);
#endif
  • “/home/lily/OS/boot/kernel/memory.c”
#include "memory.h"
#include "stdint.h"
#include "print.h"

#define PG_SIZE 4096
//4KB

/*位图地址
因为0xc009_f000是内核主线程栈顶,0xc009_e000是内核主线程的pcb。
一个页框大小的位图可表示128MB内存,位图位置安排在地址0xc009_a000,
这样本系统最大支持4个页框的位图,即512MB
*/
#define MEM_BITMAP_BASE 0xc009a000
/****************/

/*0xc000_0000是内核从虚拟地址3G起
0x10_0000是指跨过低端1MB内存,使虚拟地址在逻辑上连续*/
#define K_HEAP_START 0xc0100000

//内存池结构,生成两个实例用于管理内核内存池和用户内存池
struct pool{
    struct bitmap pool_bitmap;  //本内存池用到的位图结构,用于管理物理内存
    uint32_t phy_addr_start;    //本内存池所管理物理内存的起始地址
    uint32_t pool_size;         //本内存池的字节容量
};

struct pool kernel_pool,user_pool;  //生成内核内存池和用于内存池
struct virtual_addr kernel_vaddr;   //用来给内核分配虚拟地址

//初始化内存池
static void mem_pool_init(uint32_t all_mem){
    put_str("   mem_pool_init start\n");
    uint32_t page_table_size = PG_SIZE * 256;
    //页表大小=1页的页目录表 + 第0和第768个页目录项指向同一个页表 + (769~1022)个页目录项共指向254个页表 = 256
    uint32_t used_mem = page_table_size + 0x100000;     //0x10_0000是指跨过低端1MB内存 + 页表,使虚拟地址在逻辑上连续
    uint32_t free_mem = all_mem - used_mem;
    uint16_t all_free_pages = free_mem/PG_SIZE;
    uint16_t kernel_free_pages = all_free_pages / 2;
    uint16_t user_free_pages = all_free_pages - kernel_free_pages;

    //为简化位图操作,余数不处理
    uint32_t kbm_length = kernel_free_pages / 8;
    uint32_t ubm_length = user_free_pages / 8;
    uint32_t kp_start = used_mem;   //内核内存池起始地址
    uint32_t up_start = kp_start + kernel_free_pages*PG_SIZE;   //用户内存池起始地址

    kernel_pool.phy_addr_start = kp_start;
    user_pool.phy_addr_start = up_start;

    kernel_pool.pool_size = kernel_free_pages*PG_SIZE;
    user_pool.pool_size = user_free_pages*PG_SIZE;

    kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
    user_pool.pool_bitmap.btmp_bytes_len = ubm_length;

    /*内核内存池和用户内存池位图
    位图是全局数据,长度不固定
    全局/静态数组需要在编译时知道长度
    我们需要根据总内存大小算出需要多少字节,所以改为指定一块内存来生成位图
    */
    kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
    user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);
    
    /*输出内存池信息*/
    put_str("   kernel_pool_bitmap_start:");
    put_int((int)kernel_pool.pool_bitmap.bits);
    put_str("   kernel_pool_phy_addr_start:");
    put_int(kernel_pool.phy_addr_start);
        put_str("\n");
        put_str("user_pool_bitmap_start:");
        put_int((int)user_pool.pool_bitmap.bits);
        put_str("   user_pool_phy_addr_start:");
        put_int((int)user_pool.phy_addr_start);
        put_str("\n");

        /*将位图置0*/
        bitmap_init(&kernel_pool.pool_bitmap);
        bitmap_init(&user_pool.pool_bitmap);

        /*初始化内核虚拟地址的位图,按照实际物理内存大小生成数组*/
        kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;
        //用于维护内核堆的虚拟地址,所以要和内核内存池大小一致

        /*位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外*/
        kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);
        kernel_vaddr.vaddr_start = K_HEAP_START;
        bitmap_init(&kernel_vaddr.vaddr_bitmap);
        put_str("   mem_pool_init done\n");
}

/*内存管理部分初始化入口*/
void mem_init(){
    put_str("mem_init start\n");
    uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
    mem_pool_init(mem_bytes_total);
    put_str("mem_init done\n");
}
  • “/home/lily/OS/boot/kernel/init.c”
#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"
#include "memory.h"

/*负责初始化所有模块*/
void init_all(){
    put_str("init_all\n");
    idt_init();     //初始化中断
    timer_init();   //初始化PIT
    mem_init();
}
  • “/home/lily/OS/boot/makefile”
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
##-fno-builtin是告诉编译器不要采用内部函数 -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 \
	$(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/bitmap.o
## OBJS用来存储所有目标文件名,不要用%.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 lib/kernel/io.h lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/timer.o:device/timer.c device/timer.h lib/kernel/stdint.h lib/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)/string.o:lib/string.c lib/string.h kernel/debug.h kernel/global.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/memory.o:kernel/memory.c kernel/memory.h lib/kernel/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/bitmap.o:lib/kernel/bitmap.c lib/kernel/bitmap.h lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.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	
###fi为终止符

hd:	
	dd if=$(BUILD_DIR)/kernel.bin of=/home/lily/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc

clean:	##将build目录下文件清空
	cd $(BUILD_DIR) && rm -f ./*

build:$(BUILD_DIR)/kernel.bin	##编译kernel.bin,只要执行make build就是编译文件

all:mk_dir build hd	
##依次执行伪目标mk_dir build hd,只要执行make all就是完成了编译到写入硬盘的全过程
  • make all编译
    在这里插入图片描述
  • 启动bochs
 bin/bochs -f bochsrc.disk

在这里插入图片描述

2. 内存管理第一步:分配页内存

  • 32位虚拟地址的转换过程:
    1. 页目录表的物理地址 + pde索引*4 = 页目录pde的物理地址。
    2. 页目录pde的物理地址 + 页目录项pte*4 = 页目录项pte物理地址
    3. 页目录项pte物理地址 + 物理页内偏移地址 = 物理地址
  • 如何得到虚地址vaddr的pte*指针(页表项的地址 = 指向物理页的地址)?
    • 高10位放置页目录表的物理地址(0xffc0_0000),找到页目录表的物理地址。
    • 中10位放置PDE(vaddr的高10位,pde,pte,12位偏移),找到页目录项,即页表的地址。
    • 低12位放置PTE*4,得到页表项,即页的物理地址。
  • 如何得到虚地址vaddr的pde*指针(页目录项的地址 = 指向页表的物理地址)?
    • 高10位和中10位都放置页目录表的物理地址(0xffc0_0000),使指针停留在页目录表。
    • 低12位放置PDE*4,得到页目录项,即页表的物理地址。
      在这里插入图片描述

--------------------代码----------------------

  • “/home/lily/OS/boot/kernel/memory.c”
#include "memory.h"
#include "bitmap.h"
#include "stdint.h"
#include "global.h"
#include "debug.h"
#include "print.h"
#include "string.h"

#define PG_SIZE 4096
//4KB
#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)
#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)

/*位图地址
因为0xc009_f000是内核主线程栈顶,0xc009_e000是内核主线程的pcb。
一个页框大小的位图可表示128MB内存,位图位置安排在地址0xc009_a000,
这样本系统最大支持4个页框的位图,即512MB
*/
#define MEM_BITMAP_BASE 0xc009a000
/****************/

/*0xc000_0000是内核从虚拟地址3G起
0x10_0000是指跨过低端1MB内存,使虚拟地址在逻辑上连续*/
#define K_HEAP_START 0xc0100000

//内存池结构,生成两个实例用于管理内核内存池和用户内存池
struct pool{
    struct bitmap pool_bitmap;  //本内存池用到的位图结构,用于管理物理内存
    uint32_t phy_addr_start;    //本内存池所管理物理内存的起始地址
    uint32_t pool_size;         //本内存池的字节容量
};

struct pool kernel_pool,user_pool;  //生成内核内存池和用于内存池
struct virtual_addr kernel_vaddr;   //用来给内核分配虚拟地址

/*在pf表示的虚拟内存池中申请pg_cnt个虚拟页,成功则返回虚拟页起始地址,失败则返回NULL*/
static void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt){
    int vaddr_start = 0,bit_idx_start = -1;
    uint32_t cnt = 0;
    if(pf == PF_KERNEL){
        //在位图中申请pg_cnt位
        bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap,pg_cnt);
        if(bit_idx_start == -1)
            return NULL;
        //申请成功则分配页
        while (cnt < pg_cnt)
            bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start + cnt++,1);
        vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start*PG_SIZE;
    }else{
        //用户内存池
    }
    return (void*)vaddr_start;
}

// /*得到虚拟地址vaddr对应的pte指针*/
uint32_t* pte_ptr(uint32_t vaddr){
    uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr)*4);
    return pte;
}

/*得到虚拟地址vaddr对应的pde指针*/
uint32_t* pde_ptr(uint32_t vaddr){
    uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr)*4);
    return pde;
}

/*在m_pool指向的物理内存池中分配 一个物理页,成功则返回页框物理地址,失败则返回NULL*/
static void* palloc(struct pool* m_pool){
    //检测有无空间
    int bit_idx = bitmap_scan(&m_pool->pool_bitmap,1);
    //分配空间
    if(bit_idx == -1)
        return NULL;
    bitmap_set(&m_pool->pool_bitmap,bit_idx,1);
    uint32_t page_phyaddr = m_pool->phy_addr_start + (bit_idx*PG_SIZE);
    return (void*)page_phyaddr;
}

// /*页表中添加虚拟地址_vaddr与物理地址_page_phyadddr的映射*/
static void page_table_add(void* _vaddr,void* _page_phyaddr){
    uint32_t vaddr = (uint32_t)_vaddr;
    uint32_t page_phyaddr = (uint32_t)_page_phyaddr;
    uint32_t* pte = pte_ptr(vaddr);
    uint32_t* pde = pde_ptr(vaddr);

    /*要确保pde创建完成后才能执行pte*/
    if(*pde & 0x00000001){
        //如果pde存在,则将pte指向物理地址
        ASSERT(!(*pte & 0x00000001));   //pte理应不存在
        if(!(*pte & 0x00000001)){
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        }else{
            PANIC("pte repeat!");
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        }
    }else{
        //页目录项不存在,创建页目录项
        uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);  //页表中用到的页框一律从内核空间分配
        *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
        //pte清零
        memset((void*)((int)pte & 0xfffff000),0,PG_SIZE);   //pte & 0xfffff000表示新页表的起始地址,将整页清零
        ASSERT(!(*pte & 0x00000001));
        *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
    }
}

// /*分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL*/
void* malloc_page(enum pool_flags pf,uint32_t pg_cnt){
    ASSERT(pg_cnt > 0 && pg_cnt < 3840);
    /*malloc_page原理的三个动作:
    1. 通过vaddr_get在虚拟内存池中申请虚拟地址
    2. 通过palloc在物理内存池中申请物理地址
    3. 通过page_table_add将两个地址进行映射*/

    //1.在虚拟内存池中申请
    void* vaddr_start = vaddr_get(pf,pg_cnt);   
    if(vaddr_start == NULL)
        return NULL;
    uint32_t vaddr = (uint32_t)vaddr_start;
    uint32_t cnt = (uint32_t)pg_cnt;
    struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;

    //2.虚拟内存是连续的,物理内存不是,需要逐个映射
    while (cnt--){
        void* page_phyaddr = palloc(mem_pool);  //申请一个物理地址
        if(page_phyaddr == NULL)
            return NULL;
        //3.在页表中做映射
        page_table_add((void*)vaddr,page_phyaddr);
        vaddr += PG_SIZE;   //下一个虚拟页
    }
    return vaddr_start;
}

// /*在内核物理池中申请一页内存,成功则返回其虚拟地址,失败返回NULL*/
void* get_kernel_pages(uint32_t pg_cnt){
    void* vaddr = malloc_page(PF_KERNEL,pg_cnt);
    if(vaddr == NULL)
        return NULL;
    memset(vaddr,0,pg_cnt*PG_SIZE);     //把虚拟地址对应的物理地址清零
    return vaddr;
}

// //初始化内存池
static void mem_pool_init(uint32_t all_mem){
    put_str("   mem_pool_init start\n");
    uint32_t page_table_size = PG_SIZE * 256;
    //页表大小=1页的页目录表 + 第0和第768个页目录项指向同一个页表 + (769~1022)个页目录项共指向254个页表 = 256
    uint32_t used_mem = page_table_size + 0x100000;     //0x10_0000是指跨过低端1MB内存 + 页表,使虚拟地址在逻辑上连续
    uint32_t free_mem = all_mem - used_mem;
    uint16_t all_free_pages = free_mem/PG_SIZE;
    uint16_t kernel_free_pages = all_free_pages / 2;
    uint16_t user_free_pages = all_free_pages - kernel_free_pages;

    //为简化位图操作,余数不处理
    uint32_t kbm_length = kernel_free_pages / 8;
    uint32_t ubm_length = user_free_pages / 8;
    uint32_t kp_start = used_mem;   //内核内存池起始地址
    uint32_t up_start = kp_start + kernel_free_pages*PG_SIZE;   //用户内存池起始地址

    kernel_pool.phy_addr_start = kp_start;
    user_pool.phy_addr_start = up_start;

    kernel_pool.pool_size = kernel_free_pages*PG_SIZE;
    user_pool.pool_size = user_free_pages*PG_SIZE;

    kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
    user_pool.pool_bitmap.btmp_bytes_len = ubm_length;

    /*内核内存池和用户内存池位图
    位图是全局数据,长度不固定
    全局/静态数组需要在编译时知道长度
    我们需要根据总内存大小算出需要多少字节,所以改为指定一块内存来生成位图
    */
    kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
    user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);
    
    /*输出内存池信息*/
    put_str("   kernel_pool_bitmap_start:");
    put_int((int)kernel_pool.pool_bitmap.bits);
    put_str("   kernel_pool_phy_addr_start:");
    put_int(kernel_pool.phy_addr_start);
        put_str("\n");
        put_str("user_pool_bitmap_start:");
        put_int((int)user_pool.pool_bitmap.bits);
        put_str("   user_pool_phy_addr_start:");
        put_int((int)user_pool.phy_addr_start);
        put_str("\n");

        /*将位图置0*/
        bitmap_init(&kernel_pool.pool_bitmap);
        bitmap_init(&user_pool.pool_bitmap);

        /*初始化内核虚拟地址的位图,按照实际物理内存大小生成数组*/
        kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;
        //用于维护内核堆的虚拟地址,所以要和内核内存池大小一致

        /*位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外*/
        kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);
        kernel_vaddr.vaddr_start = K_HEAP_START;
        bitmap_init(&kernel_vaddr.vaddr_bitmap);
        put_str("   mem_pool_init done\n");
}

// /*内存管理部分初始化入口*/
void mem_init(){
    put_str("mem_init start\n");
    uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
    mem_pool_init(mem_bytes_total);
    put_str("mem_init done\n");
}
  • “/home/lily/OS/boot/kernel/memory.h”
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"
//虚拟地址池,用于虚拟地址管理
struct virtual_addr
{
    struct bitmap vaddr_bitmap; //虚拟地址用到的位图结构
    uint32_t vaddr_start;       //虚拟地址起始地址
};

enum pool_flags{
    PF_KERNEL = 1,  //内核内存池
    PF_USER = 2     //用户内存池
};

#define PG_P_1 1    //页表项或页目录项存在属性位:此页在内存中存在
#define PG_P_0 0    //页表项或页目录项存在属性位:此页在内存中不存在
#define PG_RW_R 0   //R/W属性位值,读/写操作:此页运行读、写、执行
#define PG_RW_W 2   //R/W属性位值,读/写操作:此页允许读、执行
#define PG_US_S 0   //U/S属性位值,系统级:只允许特权级0、1、2的程序访问
#define PG_US_U 4   //U/S属性位值,用户级:允许所有特权级的进程访问

extern struct pool kernel_pool, user_pool;

static void* vaddr_get(enum pool_flags pf,uint32_t pg_cnt);

/*得到虚拟地址vaddr对应的pte指针*/
uint32_t* pte_ptr(uint32_t vaddr);

/*得到虚拟地址vaddr对应的pde指针*/
uint32_t* pde_ptr(uint32_t vaddr);

/*在m_pool指向的物理内存池中分配 一个物理页,成功则返回页框物理地址,失败则返回NULL*/
static void* palloc(struct pool* m_pool);

/*页表中添加虚拟地址_vaddr与物理地址_page_phyadddr的映射*/
static void page_table_add(void* _vaddr,void* _page_phyaddr);

/*分配pg_cnt个页空间,成功则返回起始虚拟地址,失败则返回NULL*/
void* malloc_page(enum pool_flags pf,uint32_t pg_cnt);

/*在内核物理池中申请一页内存,成功则返回其虚拟地址,失败返回NULL*/
void* get_kernel_pages(uint32_t pg_cnt);
//初始化内存池
static void mem_pool_init(uint32_t all_mem);

/*内存管理部分初始化入口*/
void mem_init(void);

#endif
  • “/home/lily/OS/boot/kernel/main.c”
#include "print.h"
#include "init.h"
#include "debug.h"
#include "memory.h"

int main(void){
	put_str("I am kernel\n");
	init_all();

	void* addr = get_kernel_pages(3);
	put_str("\n get_kernel_page start vaddr is:");
	put_int((uint32_t)addr);
	put_str("\n");

	while(1);
	return 0;
}
  • 编译运行
make all

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值