操作系统真象还原:内存管理系统

第8章-内存管理系统

这是一个网站有所有小节的代码实现,同时也包含了Bochs等文件

8.1 Makefile简介

8.1.1 Makefile是什么
8.1.2 makefile基本语法

在这里插入图片描述

make 给咱们提供了方法,可以在命令之前加个字符’@’,这样就不会输出命令本身信息了

8.1.3 跳到目标处执行

我们可以用目标名称作为 make 的参数,采用“ make 目标名称”的方式,单独执行目标名称处的规则.

8.1.4 伪目标

make 规定,当规则中不存在依赖文件时,这个目标文件名就称为一一伪目标。伪目标,顾名思义,也就是不产生真实的目标文件,所以当然也就不需要依赖文件了。于是,伪目标所在的规则就变成了纯粹地执行命令,只要给 make 指定该伪目标名做参数,就能让伪目标规则中的命令直接执行。

为了避免伪目标和真实目标文件同名的情况,可以用关键字“ .PHONY”来修饰伪目标,格式为“ .PHONY:伪目标名”,这样不管与伪目标同名的文件是否存在, make 照样执行伪目标处的命令。

在这里插入图片描述

8.1.5 make:递归式推导目标
8.1.6 自定义变量和系统变量

变量定义的格式:变量名=值(字符串),多个值之间用空格分开。 make 程序在处理时会用空格将值打散,然后遍历每一个值。另外,值仅支持字符串类型,即使是数字也被当作字符串来处理 。

变量引用的格式:$(变量名)。这样,每次引用变量时,变量名就会被其值(宇符串)替换。

在这里插入图片描述

8.1.8自动变量

$@:表示规则中的目标文件名集合,如果存在多个目标文件,$@则表示其中每一个文件名。

$<: 表示规则中依赖文件中的第 1 个文件。

$^:表示规则中所有依赖文件的集合,如果集合中有重复的文件,$^会自动去重。

$?:表示规则中,所有比目标文件 mtime 更新的依赖文件集合。
在这里插入图片描述

8.1.9模式规则

%:用来匹配任意多个非空字符。比如%.o 代表所有以.o为结尾的文件,g%s.o是以字符 g 开头的所有以.o。为结尾的文件, make 会拿这个字符串模式去文件系统上查找文件,默认为当前路径下。

在这里插入图片描述

8.2实现assert断言

8.2.1实现开、关中断的函数
#define EFLAGS_IF 0x00000200 //eflags寄存器中if位为1
#define GET_EFLAGS (EFLAG_VAR) asm volatile("pushfl; popl %0":"=g"(EFLAG_VAR)) //读取当前的标志寄存器 EFLAGS 的值,并将其存储到 C 语言中的变量 EFLAG_VAR 中/*定义中断的两个状态*/
enum intr_status{INTR_OFF,INTR_ON
};/*开中断并返回开中断前的状态*/
enum inter_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位置1return old_status;}
}/*关中断,并返回开关断前的状态*/
enum inter_status intr_disable(){enum intr_status old_status;if(INTR_ON==intr_get_status()){old_status = INTR_ON;asm volatile("cli": : :"memory");        //关闭中断,sti指令将IF位置0return 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_EFLAGS(eflags);return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}
8.2.2实现ASSERT

在C语言中 ASSERT是用宏来定义的,其原理是判断传给 ASSERT 的表达式是否成立,若表达式成立则什么都不做,否则打印出错信息并停止执行。

__FILE__,__LINE__,__func__,这三个是预定义的宏,分别表示被编译的文件名、被编译文件中的行号、被编译的函数名。

/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-04-08 09:38:54* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-04-08 10:26:38* @FilePath: /OS/chapter8/8.2/kernel/debug.c* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#include "debug.h"
#include "print.h"
#include "interrupt.h"void panic_spin(char* filename, int line, const char* func, const char* condition){intr_disable(); //因为有时候会单独调用panic_spin,所以在这里先关闭中断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);
}
#ifndef __KERNEL_DEBUG_H
#define __KERNEL_DEBUG_H
void panic_spin(char* filename, int line, const char* func, const char* condition);/***************************************************__VA_ARGS__***************************************************
*__VA_ARGS__是预处理器所支持的专用标识符。
*代表所有与省略号相对应的参数。
*”…”表示定义的宏其参数可变。
*/
#define PANIC(...) panic_spin(__FILE__,__LINE__,__func__,__VA_ARGS__)
/****************************************************************************************************************/#ifdef NDEBUF#define ASSERT(CONDITION) ((void)0)
#else#define ASSERT(CONDITION) \if(CONDITION){ }else{   \/*符号#让编译器将宏的参数转化为字符串字面量,就是转化为字符串*/ \PANIC(#CONDITION); \}
#endif // NDEBUF#endif //__KERNEL_DEBUG_H

8.3实现字符串操作函数

/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-04-08 11:22:49* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-04-08 12:18:47* @FilePath: /OS/chapter8/8.3/lib/string.c* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#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-->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 个字节,若相等则返回 O,若 a_大于 b_ ,返回 +1 ,否则返回-1 */
int memcmp(const void* a_, const void* b_, uint32_t size){const uint8_t* a = a_;const uint8_t* b = b_;ASSERT(a_!=NULL||b_!=NULL);while(size-->0){if(*a!=*b)return *a>*b ? 1:-1a++;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(cosnt char* a, const char* b){ASSERT(a!=NULL&&b!=NULL);while(*a!=0&&*a==*b){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){f(*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;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;while((*str++=*src++)); //当str被赋值为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;
}

8.4位图bitmap及其函数的实现

位图,也就是 bitmap,广泛用于资源管理,是一种管理资源的方式、手段。“资源”包括很多,比如内存或硬盘,对于此类大容量资源的管理一般都会采用位图的方式。

位图包含两个概念:位和图 。 位是指 bit,即字节中的位, 1 字节中有 8 个位。图是指 map, map 这个词在很久之前就介绍过啦,地图本质上就是映射的意思,映射,即对应关系。综合起来,位图就是用字节中的 1 位来映射其他单位大小的资源,按位与资源之间是一对一的对应关系。

位图中的每一位都将表示实际物理内存中的 4kb,也就是一页,即位图中的一位对应物理内存中的一页,如果某位为 0,表示该位对应的页未分配,可以使用,反之如果某位为 1 ,表示该位对应的页己经被分配出去了,在将该页回收之前不可再分配。

在这里插入图片描述

8.4.2位图的定义与实现
/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-04-09 10:16:53* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-04-09 17:25:49* @FilePath: /OS/chapter8/8.4/lib/kernel/bitmap.h* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#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
/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-04-09 10:31:56* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-06-10 17:42:57* @FilePath: /OS/chapter8/8.5.1/lib/kernel/bitmap.c* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#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,若为1则返回true,否则返回false */
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个位,成功则返回其起始位下标,失败返回-1 */
int bitmap_scan(struct bitmap* btmp, uint32_t cnt) {uint32_t idx_byte = 0;	 // 用于记录空闲位所在的字节
/* 先逐字节比较,蛮力法 */while (( 0xff == btmp->bits[idx_byte]) && (idx_byte < btmp->btmp_bytes_len)) {
/* 1表示该位已分配,所以若为0xff,则表示该字节内已无空闲位,向下一字节继续找 */idx_byte++;}ASSERT(idx_byte < btmp->btmp_bytes_len);if (idx_byte == btmp->btmp_bytes_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->btmp_bytes_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_scan_test(btmp, next_bit))) {	 // 若next_bit为0count++;} 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为1btmp->bits[byte_idx] |= (BITMAP_MASK << bit_odd);} else {		      // 若为0btmp->bits[byte_idx] &= ~(BITMAP_MASK << bit_odd);}
}

8.5内存管理系统

8.5.1内存池规划

由于在分页机制下有了虚拟地址和物理地址,为了有效地管理它们,我们需要创建虚拟内存地址池和物理内存地址池。

规划物理内存池:一种可行的方案是将物理内存划分成两部分,一部分只用来运行内核,另一部分只用来运行用户进程,将内存规划出不同的部分,专项专用。 我们把物理内存分成两个内存池, 一部分称为用户物理内存池,此内存池中的物理内存只用来分配给用户进程。另 一部分就是内核物理内存池,此内存池中的物理内存只给操作系统使用。

内存池中的内存也得按单位大小来获取,这个单位大小是4KB,称为页,故,内存池中管理的是一个个大小为 4KB的内存块,从内存池中获取的内存大小至少为 4KB或者为 4KB 的倍数

现在,我们就来进行内存管理的核心准备工作,初始化三个内存池:管理内核可用虚拟地址空间内存池管理内核可用物理地址空间内存池管理用户可用物理地址空间内存池用户可用虚拟地址空间内存池是要等到创建用户进程时才创立,现在不用初始化。

在这里插入图片描述

A、建立管理可用虚拟地址空间的数据结构虚拟内存池:virtual_addr,包含一个管理位图的数据结构、管理的可用虚拟地址空间的起始地址;建立管理可用物理地址空间的数据结构物理内存池:pool,包含一个管理位图的数据结构、管理的可用物理地址空间的起始地址、这个可用物理地址内存空间的大小;

B、通过A建立的数据结构,建立管理管理内核可用虚拟地址空间的内存池变量kernel_vaddr、管理内核可用物理地址空间的内存池变量kernel_pool、管理用户进程可用的物理地址空间内存池变量user_pool

C、根据作者设置与实际情况,初始化kernel_vaddr、user_pool、kernel_pool,就是初始化虚拟内存池内的位图数据结构、管理的地址空间起始地址,物理内存池内的位图数据结构、管理的地址空间起始地址、可用的物理地址空间大小

D、将C封装成一个函数mem_init(),并在init_all()中调用

/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-04-09 14:17:52* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-04-09 16:45:14* @FilePath: /OS/chapter8/8.4/kernel/memory.h* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"/*虚拟地址池,用于虚拟地址管理*/
struct virtual_addr
{/* data */struct bitmap vaddr_bitmap; //虚拟地址用到的位图结构uint32_t vaddr_start;   //虚拟地址起始地址
};extern struct pool kernel_pool, user_pool;
void mem_init(void);
#endif // !__KERNEL_MEMORY_H
/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-04-09 14:26:27* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-04-09 17:33:33* @FilePath: /OS/chapter8/8.4/kernel/memory.c* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#include "memory.h"
#include "stdint.h"
#include "print.h"#define PG_SIZE 4096/***********************************位图地址************************************* 因为 Oxc009f000 是内核主线程栈顶, Oxc009e000 是内核主线程的pcb。* 一个页框大小的位图可表示 128MB 内存,位图位置安排在地址。0xc009a000* 这样本系统最大支持 4 个页框的位图,即 512MB
********************************************************************************/
#define MEM_BITMAP_BASE 0xc009a000
/* 0xcOOOOOOO 是内核从虚拟地址 3G 起* 0xcOOOOOOO 是内核从虚拟地址 3G起 OxlOOOOO 意指跨过低端 lMB 内存,使虚拟地址在逻辑上连续
*/
#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; //0x100000为低端1MB内存uint32_t free_mem = all_mem - used_mem;uint16_t all_free_pages = free_mem / PG_SIZE; //1 页为 4KB,不管总内存是不是 4k 的倍数//对于以页为单位的内存分配策略,不足 1 页的内存不用考虑了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;    //kernel Bitmap的长度,位图中的一位表示一页,以字节为单位uint32_t ubm_length = user_free_pages / 8;     //user Bitmap长度uint32_t kp_start = used_mem;   //kernel pool start 内核内存次的起始地址uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE; //user pool start 内核内存次的起始地址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;/** 内核内存池和用户内存池位图* 位图是全局的数据,长度不固定。* 全局或静态的数组需要在编译时知道其长度,* 而我们需要根据总内存大小算出需要多少字节,* 所以改为指定一块内存来生成位图。*///内核使用的最高地址是 Oxc009f000,这是主线程的校地址//(内核的大小预计为 70KB 左右)//32MB内存占用的位图是2KB///内核内存池的位图先定在 MEM_BITMAP_BASE(Oxc009a000 )处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(user_pool.phy_addr_start);/*将位图置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_ini t 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");
}
8.5.2内存管理系统第一步,分配页内存

为减少学习阻力,先给大伙复习下 32 位虚拟地址的转换过程。

  1. 高 10 位是页目录项 pde 的索引,用于在页目录表中定位 pde,细节是处理器获取高 10 位后自动将其乘以 4,再加上页目录表的物理地址,这样便得到了 pde 索引对应的 pde 所在的物理地址,然后自动在该物理地址中,即该 pde 中,获取保存的页表物理地址(为了严谨,说的都有点拗口了)。
  2. 中间 10 位是页表项 pte 的 索引,用于在页表中定位 pte 。细节是处理器获取中间 10 位后自动将其乘以 4,再加上第一步中得到的页表的物理地址,这样便得到了 pte 索引对应的 pte 所在的物理地址,然后自动在该物理地址(该 pte )中获取保存的普通物理页的物理地址。
  3. 低 12 位是物理页内的偏移量,页大小是 4KB, 12 位可寻址的范围正好是 4阻,因此处理器便直接把低 12 位作为第二步中获取的物理页的偏移量,无需乘以 4c 用物理页的物理地址加上这低 12 位的和便是这 32 位虚拟地址最终落向的物理地址 。

注意啦,再提醒一次,页表的作用是将虚拟地址转换成物理地址,此工作表面虚幻,但内心真实,其转换过程中涉及访问的页目录表、页目录项及页表项,都是通过真实物理地址访问的,否则若用虚拟地址访问它们的话,会陷入转换的死循环中不可自拔。

1、代码功能

从内存池中分配地址,然后将分配到的物理地址与虚拟地址建立映射关系。

2、实现原理

物理内存池与虚拟内存池已经初始化完毕,我们自然就能够从这些内存池中申请到虚拟地址与物理地址。通过建立页表,完成虚拟地址到物理地址的映射。

3、代码逻辑

A、写函数完成申请地址空间,包括物理地址与虚拟地址

B、为二者建立映射关系

4、怎么写代码?

A、在memory.h中建立枚举类型结构体pool_flags用于选择从哪个虚拟内存池中分配内存,这样就可以实现用一个函数即能完成从用户虚拟地址空间分配地址,也能完成从内核虚拟地址空间分配地址;定义模块化的页表项字段宏,用于虚拟地址到物理地址的映射时的页表构建。

B、写函数vaddr_get,通过传入的poll_flags值完成对应的从对应的虚拟内存池中分配虚拟地址;写函数palloc,完成从传入的物理内存池中分配物理地址;

C、写宏PDE_IDX与PTE_IDX完成将从一个虚拟地址当中取出PDT与PTE的索引;写函数pde_ptr与pte_ptr将虚拟地址转换成访问虚拟地址对应的页目录表项的地址与页表表项的地址,这是为了当一个虚拟地址没有页表映射时,我们要动态建立映射,这就需要建立页目录表项与页表表项,自然得需要知道这两个的地址。

D、写出将申请得到的虚拟地址空间与物理地址空间,通过修改页表建立映射关系的函数page_table_add

1、页表存在,那么我们只需要将物理地址填入虚拟地址对应页表表项中即可

2、页表不存在(页目录表表项为空),我们需要先申请物理地址来存放页表,然后填入页目录表项这个页表的地址,然后初始化页表,最后将传入的物理地址填入虚拟地址对应页表表项中

E、写函数malloc_page根据传入的pool_flags的值决定是为内核空间还是用户空间分配连续的多个页面,包含从对应的虚拟内存池中分配虚拟地址(调用vaddr_get),从对应的物理内存池中分配物理地址(调用palloc),然后为虚拟地址与物理地址建立映射(调用page_talbe_add)。

F、写函数get_kernel_pages,快捷为内核申请地址空间(调用malloc_page)

/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-04-09 14:17:52* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-04-10 10:47:04* @FilePath: /OS/chapter8/8.4/kernel/memory.h* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"/*内存池标记,用于判断是哪个内存池*/
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 属性位值,系统级
#define PG_US_U 4    //U/S 属性位值,用户级/*虚拟地址池,用于虚拟地址管理*/
struct virtual_addr
{/* data */struct bitmap vaddr_bitmap; //虚拟地址用到的位图结构uint32_t vaddr_start;   //虚拟地址起始地址
};extern struct pool kernel_pool, user_pool;
void mem_init(void);
#endif // !__KERNEL_MEMORY_H
/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-04-09 14:26:27* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-06-10 18:59:08* @FilePath: /OS/chapter8/8.4/kernel/memory.c* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#include "memory.h"
#include "bitmap.h"
#include "stdint.h"
#include "global.h"
#include "debug.h"
#include "string.h"
#include "print.h"#define PG_SIZE 4096/***********************************位图地址************************************* 因为 Oxc009f000 是内核主线程栈顶, Oxc009e000 是内核主线程的pcb。* 一个页框大小的位图可表示 128MB 内存,位图位置安排在地址。xc009a000* 这样本系统最大支持 4 个页框的位图,即 512MB
********************************************************************************/
#define MEM_BITMAP_BASE 0xc009a000
/* 0xcOOOOOOO 是内核从虚拟地址 3G 起* 0xcOOOOOOO 是内核从虚拟地址 3G起 OxlOOOOO 意指跨过低端 lMB 内存,使虚拟地址在逻辑上连续
*/
#define K_HEAP_START 0xc0100000#define PDE_IDX(addr) ((addr&0xffc00000)>>22) //高10位
#define PTE_IDX(addr) ((addr&0x003ff000)>>12) //中10位/*存池结构,生成两个实例用于管理内核内存池和用户内存池*/
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){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;
}/*得看书205页*/
/*得到虚拟地址 vaddr 对应的 pte 指针*/
uint32_t* pte_ptr(uint32_t vaddr){/*访问到页表自己 再用页目录项 pde (页目录内页袤的索引)作为pte的索引访问到页表 再用pte的索引作为页内偏移*/uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000)>>10) + PTE_IDX(vaddr) * 4); //这里得到的是新的虚拟地址 能够访问到它的页表物理地址return pte;
}/*得到虚拟地址 vaddr 对应的 pde 的指针*/
uint32_t* pde_ptr (uint32_t vaddr){/* Oxfffff 用来访问到页表本身所在的地址 */uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr)*4);return pde;
}/*在 m_pool 指向的物理内存池中分配 1 个物理页,成功则返回页框的物理地址,失败则返回 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); //将这个位置1,表示以用uint32_t page_phyaddr = ((bit_idx * PG_SIZE) + m_pool->phy_addr_start);return (void*)page_phyaddr;
}/*表中添加虚拟地址_vaddr 与物理地址_page_phyaddr 的映射*/
static void page_table_add(void* _vaddr, void* _page_phyaddr){uint32_t vaddr = (uint32_t)_vaddr, page_phyaddr = (uint32_t)_page_phyaddr;uint32_t* pde = pde_ptr(_vaddr);uint32_t* pte = pte_ptr(_vaddr);/** 注意* 执行*pte ,会访问到空的pde。所以确保pde创建完成后才能执行*pte,* 否则会引发 page_fault。因此在*pde 为 0 时,* pte 只能出现在下面 else 语句块中的* pde 后面。*//*先在页目录内判断 目录项的 p 位,若为 1 ,则表示该表已存在*/if(*pde & 0x00000001){//页目录项和页表项的第 0 位为 p ,此处判断目录项是否存在ASSERT(!(*pte & 0x00000001));if(!(*pte & 0x00000001)){//只要是创建页表, pte 就应该不存在,多判断一下放心*pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);}else{  ///目前应该不会执行到这,因为上面的 ASSERT 会先执行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);/** 分配到的物理页地址 pde_phyaddr 对应的物理内存清 O,* 避免里面的陈旧数据变成了页表项,从而让页表混乱。* 访问到 pde 对应的物理地址,用 pte 取高 20 位便可。* 因为 pte 基于该 pde 对应的物理地址内再寻址,* 把低 12 位置0。便是该 pde 对应的物理页的起始**/memset((void*)((int)pte & 0xfffff000),0,PG_SIZE); //这里要的地址必须是pte的虚拟地址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 将以上得到的虚拟地址和物理地址在页表中完成映射*/void* vaddr_start = vaddr_get(pf,pg_cnt);if(vaddr_start==NULL)   return NULL;uint32_t vaddr = (uint32_t)vaddr_start, cnt = pg_cnt;struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;/*因为虚拟地址是连续的,但物理地址可以是不连续的,所以逐个做映射*/while(cnt-->0){void* page_phyaddr = palloc(mem_pool);if(page_phyaddr == NULL)    ///失败时要将曾经已申请的虚拟地址和物理页全部回滚,在将来完成内存回收时再补充return NULL;page_table_add((void*)vaddr,page_phyaddr);  //在页表中映射vaddr+=PG_SIZE;}return vaddr_start;
}/*从内核物理内存池中申请 1 页内存,成功则返回其虚拟地址,失败则返回 NULL */
void* get_kernel_pages(uint32_t pg_cnt){void* vaddr = malloc_page(PF_KERNEL, pg_cnt);if(vaddr != NULL)   //若分配的地址不为空,将页框清0后返回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; //0x100000为低端1MB内存uint32_t free_mem = all_mem - used_mem;uint16_t all_free_pages = free_mem / PG_SIZE; //1 页为 4KB,不管总内存是不是 4k 的倍数//对于以页为单位的内存分配策略,不足 1 页的内存不用考虑了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;    //kernel Bitmap的长度,位图中的一位表示一页,以字节为单位uint32_t ubm_length = user_free_pages / 8;     //user Bitmap长度uint32_t kp_start = used_mem;   //kernel pool start 内核内存次的起始地址uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE; //user pool start 内核内存次的起始地址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;/** 内核内存池和用户内存池位图* 位图是全局的数据,长度不固定。* 全局或静态的数组需要在编译时知道其长度,* 而我们需要根据总内存大小算出需要多少字节,* 所以改为指定一块内存来生成位图。*///内核使用的最高地址是 Oxc009f000,这是主线程的校地址//(内核的大小预计为 70KB 左右)//32MB内存占用的位图是2KB///内核内存池的位图先定在 MEM_BITMAP_BASE(Oxc009a000 )处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(user_pool.phy_addr_start);/*将位图置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_ini t 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");
}

所以,申请内存这个行为反映到代码上的实质是,将一个4k空间起始的物理地址,填入4K虚拟空间起始地址对应的页表表项中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/25883.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序使用 “云函数“ 获取 “openid“

文章目录 1.前期准备2.具体操作步骤 1.前期准备 必须使用云开发已经配置好云开发 2.具体操作步骤 1.进入小程序开发工具→在云函数目录上右键→选中新建云函数 创建结束&#xff0c;自动上传&#xff08;必须确认已经上传才生效&#xff09; 2.进入对应页面的js文件&#…

QT 信号和槽 信号关联到信号示例 信号除了可以绑定槽以外,信号还可以绑定信号

信号除了可以关联到槽函数&#xff0c;还可以关联到类型匹配的信号&#xff0c;实现信号的接力触发。上个示例中因为 clicked 信号没有参数&#xff0c;而 SendMsg 信号有参数&#xff0c;所以不方便直接关联。本小节示范一个信号到信号的关联&#xff0c;将按钮的 clicked 信号…

【优化过往代码】关于vue自定义事件的运用

【优化过往代码】关于vue自定义事件的运用 需求说明过往代码优化思路优化后代码&#xff08;Vue2&#xff09;遇到问题记录 Vue2官方自定义指令说明文档 Vue3官方自定义指令说明文档 需求说明 进入某些页面需要加载一些外部资源&#xff0c;并在资源加载完后进行一些处理&…

【栈】2751. 机器人碰撞

本文涉及知识点 栈 LeetCode2751. 机器人碰撞 现有 n 个机器人&#xff0c;编号从 1 开始&#xff0c;每个机器人包含在路线上的位置、健康度和移动方向。 给你下标从 0 开始的两个整数数组 positions、healths 和一个字符串 directions&#xff08;directions[i] 为 ‘L’ …

MySQL-数据处理函数

026-distinct去重 select job from emp;加个 distinct 就行了 select distinct job from emp;注意&#xff1a;这个去重只是将显示的结果去重&#xff0c;原表数据不会被更改。 select 永远不会改变原数据 select distinct deptno, job from emp order by deptno asc;027-数…

步态控制之足旋转点(Foot Rotation Indicator, FRI)

足旋转点(Foot Rotation Indicator, FRI) 足旋转点是人形机器人步态规划中的一个关键概念,用于描述步态过程中机器人脚部的旋转和稳定性。FRI 可以帮助确定机器人在行走时是否稳定,以及如何调整步态以保持稳定。下面详细介绍FRI的原理,并举例说明其应用。 足旋转点(FRI…

R语言统计分析——图形的简单示例

参考资料&#xff1a;R语言实战【第2版】 1、示例一 # 绑定数据框mtcars attach(mtcars)# 打开一个图形窗口并生成一个散点图plot(wt,mpg)# 添加一条最优拟合曲线abline(lm(mpg~wt))# 添加标题title("Regression of MPG on weight") # 解除数据框绑定 detach(mtcar…

ES8.13 _bulk报错Malformed content, found extra data after parsing: START_OBJECT解决

在使用elaticsearch8.13.0使用批量创建索引时&#xff0c;根据谷粒中说的es7.9方法去批量操作请求&#xff1a; http://127.0.0.1:9200/shop/_doc/_bulk 注意1&#xff1a;设置header为Content-Type:application/x-ndjson,否则请求报错&#xff1a; {"error": &qu…

机器学习笔记:focal loss

1 介绍 Focal Loss 是一种在类别不平衡的情况下改善模型性能的损失函数最初在 2017 年的论文《Focal Loss for Dense Object Detection》中提出这种损失函数主要用于解决在有挑战性的对象检测任务中&#xff0c;易分类的负样本占据主导地位的问题&#xff0c;从而导致模型难以…

【recast-navigation-js】使用three.js辅助绘制Agent寻路路径

目录 说在前面setAgentTarget绘制寻路路径结果问题其他 说在前面 操作系统&#xff1a;windows 11浏览器&#xff1a;edge版本 124.0.2478.97recast-navigation-js版本&#xff1a;0.29.0golang版本&#xff1a;1.21.5上一篇&#xff1a;【recast-navigation-js】使用three.js辅…

linux:centos7升级libstdc++版本到3.4.26

下载&#xff0c;解压 wget http://www.vuln.cn/wp-content/uploads/2019/08/libstdc.so_.6.0.26.zip unzip libstdc.so_.6.0.26.zip 复制到【/usr/lib64】&#xff1a; cp libstdc.so.6.0.26 /usr/lib64创建软链接 cd /usr/lib64 sln libstdc.so.6.0.26 libstdc.so.6查看一…

Python | Leetcode Python题解之第144题二叉树的前序遍历

题目&#xff1a; 题解&#xff1a; class Solution:def preorderTraversal(self, root: TreeNode) -> List[int]:res list()if not root:return resp1 rootwhile p1:p2 p1.leftif p2:while p2.right and p2.right ! p1:p2 p2.rightif not p2.right:res.append(p1.val)…

机器学习笔记 - LoRA:大型语言模型的低秩适应

一、简述 1、模型微调 随着大型语言模型 (LLM) 的规模增加到数千亿,对这些模型进行微调成为一项挑战。传统上,要微调模型,我们需要更新所有模型参数。这也称为完全微调 (FFT) 。下图详细概述了此方法的工作原理。 完全微调FFT 的计算成本和资源需求很大,因为更新每…

Vmess协议是什么意思? VLESS与VMess有什么区别?

VMess 是一个基于 TCP 的加密传输协议&#xff0c;所有数据使用 TCP 传输&#xff0c;是由 V2Ray 原创并使用于 V2Ray 的加密传输协议&#xff0c;它分为入站和出站两部分&#xff0c;其作用是帮助客户端跟服务器之间建立通信。在 V2Ray 上客户端与服务器的通信主要是通过 VMes…

【InternLM实战营第二期笔记】06:Lagent AgentLego 智能体应用搭建

文章目录 讲解为什么要有智能体什么是 Agent智能体的组成智能体框架AutoGPTReWooReAct Lagent & Agent LegoAgentLego 实操Lagent Web Demo自定义工具 AgentLego&#xff1a;组装智能体“乐高”直接使用作为智能体&#xff0c;WebUI文生图测试 Agent 工具能力微调 讲解 为…

idea如何使用git reset进行回退以及如何使用git stash将暂存区文件储藏,打包后重新恢复暂存区文件

最近遇到一个棘手的问题&#xff0c;本来按照计划表开发&#xff0c;但是项目经理突然让你改一个小bug&#xff0c;改完需要马上部署到线上&#xff0c;但是你手上的活做到一半还没做完&#xff0c;提交上去那肯定是不可行的。这时就可以使用git stash命令先把当前进度&#xf…

Discuz! X3.4发帖时间修改插件批量操作版

下载地址&#xff1a;Discuz! X3.4发帖时间修改插件批量操作版 发帖时间与回复时间说明 1、使用本插件修改发帖时间&#xff0c;则帖子中的回复楼层的时间会保持同步同间隔修改&#xff0c;所谓同步同间隔就是如果某个回复是在主题发布之后一小时回复的&#xff0c;那么修改之…

AI服务器相关知识

在当今社会&#xff0c;人工智能的应用场景愈发广泛&#xff0c;如小爱同学、天猫精灵等 AI 服务已深入人们的生活。随着人工智能时代的来临&#xff0c;AI 服务器也开始在社会各行业发挥重要作用。那么&#xff0c;AI 服务器与传统服务器相比&#xff0c;究竟有何独特之处&…

【docker】 pull access denied for alpine-java, repository does not exist

问题&#xff1a; com.spotify.docker.client.exceptions.DockerException: pull access denied for alpine-java, repository does not exist or may require docker login: denied: requested access to the resource is denied org.apache.maven.plugin.MojoExecutionExce…

Vue16-绑定class样式

一、vue绑定class样式 1-1、需求一&#xff1a;字符串写法 vue实现class样式绑定 1-2、需求二 点击div&#xff0c;随机切换样式。 math.random()&#xff1a;随机数的范围[0, 1) 1-3、需求三&#xff1a;数组写法 样式的追加 1-4、需求四 &#xff1a;对象写法 二、vue绑定…