Bootstrap

实验13.内存管理系统

简介

实验.内存管理系统

内存管理

    1,在低于1M的内核空间中,存放了3个内存池,内核池、用户池、内核虚拟地址池
    2.内核池和用户池来管理真实的物理内存,每一位表示4096字节的状态。
    3.申请一个内核虚拟地址,就会申请一个内核物理地址,然后把虚拟地址和物理地址绑定,连续的虚拟地址,每个虚拟地址绑定的物理页不一定是连续的

主要代码

引导

省略

内核

main.c

// 文件: main.c
// 时间: 2024-07-19
// 来自: ccj
// 描述: 内核从此处开始

#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 0x");
    put_int((uint32_t)addr);
    put_str("\n");

    while (1);
    return 0;
}

string.c

// 文件: string.c
// 时间: 2024-07-23
// 来自: ccj
// 描述: 字符串相关操作
 省略,参考很多

bitmap.c

// 文件: bitmap.c
// 时间: 2024-07-23
// 来自: ccj
// 描述: 位图,就是一片连续的内存,每一个比特表示内存空间的使用或空闲

#include "bitmap.h"

#include "stdint.h"
#include "string.h"
#include "print.h"
#include "interrupt.h"
#include "debug.h"

/// @brief 初始化位置,每一位都是0
/// @param btmp 位图指针
void bitmap_init(struct bitmap* btmp) { memset(btmp->bits, 0, btmp->btmp_bytes_len); }

/// @brief 判断bit_idx位是否为1,
/// @param btmp 位图指针
/// @param bit_idx 比特偏移
/// @return 若为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));
}

/// @brief 在位图中申请连续cnt个位,
/// @param btmp 位图指针
/// @param cnt 位的数量
/// @return 成功则返回其起始位下标,失败返回-1
int bitmap_scan(struct bitmap* btmp, uint32_t cnt) {
    uint32_t idx_byte = 0;

    // 逐字节比较,如果这个字节全为1,说明已经使用了,idx_byte++;
    while ((0xff == btmp->bits[idx_byte]) && (idx_byte < btmp->btmp_bytes_len)) idx_byte++;

    // 如果所有位都是1那么返回-1
    ASSERT(idx_byte < btmp->btmp_bytes_len);
    if (idx_byte == btmp->btmp_bytes_len) return -1;

    // 找到0的下标 现在 bits[idx_byte] 中可能是 0000_1111, (1 << idx) & 0000_1111 = 0 说明 idx=4
    int idx_bit = 0;
    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))) {  // 如果是空闲的那么计数+1
            count++;
        } else {  // 如果不是空闲的,计数归0
            count = 0;
        }
        if (count == cnt) {  // 若找到连续的cnt个空位
            bit_idx_start = next_bit - cnt + 1;
            break;
        }
        next_bit++;
    }
    return bit_idx_start;
}

/// @brief 将位图btmp的bit_idx位设置为value
/// @param btmp 位图指针
/// @param bit_idx 比特偏移
/// @param 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);
    }
}

memory.c

// 文件: memory.c
// 时间: 2024-07-23
// 来自: ccj
// 描述:
//      存放了3个内存池,内核池、用户池、内核虚拟地址池
//      内核池和用户池来管理真实的物理内存
//      虚拟内存池的虚拟地址用来绑定内核池的地址

#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

// 0xc009f000 内核主线程栈顶
// 0xc009e000 内核主线程的pcb
// 0xc009a000 位图起始地址
// 0xc009e000-0xc009a000=16384=4*4096 = 4个物理页
// 4*4096个字节= 4*4096*8个比特,1个比特代表1个页,最大表示内存 4*4096*8*4096(512M)的空间
#define MEM_BITMAP_BASE 0xc009a000

#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)  // 页目录索引
#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)  // 页表索引

// 0xc0000000是内核从虚拟地址3G起. 0x100000意指跨过低端1M内存,使虚拟地址在逻辑上连续
#define K_HEAP_START 0xc0100000

/// @brief 物理内存池结构
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;

/// @brief 在虚拟池中申请pg_cnt个虚拟页
/// @param  pool_flags 哪1个池
/// @param pg_cnt 虚拟页数量
/// @return 返回虚拟页的起始地址, 失败则返回NULL
static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt) {
    int vaddr_start = 0, bit_idx_start = -1;
    if (pf == PF_KERNEL) {
        bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);  // 在虚拟内核位图中申请
        if (bit_idx_start == -1) return NULL;

        // 申请成功,把申请到的位设置1
        uint32_t cnt = 0;
        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;
}

/// @brief 得到虚拟地址vaddr对应的pde的指针 *pde=vaddr的页目录项内容(包含页表地址)
/// @param vaddr 虚拟地址
/// @return 页目录项地址
uint32_t* pde_ptr(uint32_t vaddr) {
    uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr) * 4);
    // 现在的pte
    // 页目录索引=高10位=全1
    // 页表索引=中10位=全1
    // 物理页偏移=低12位=vaddr页目录索引*4

    // 分页转换之后 pde指向vaddr的页目录项
    // 1.页目录索引=全1, 是第1023个页目录项,该页目录项的内容是页目录起始位置
    // 2.页表索引=全1,是第1023个页目录项,该页目录项的内容是页目录起始位置
    // 3.物理页偏移=低12位=vaddr页目录索引*4,该页目录项的内容包含是vaddr的页表地址
    return pde;
}

/// @brief 得到虚拟地址vaddr对应的pte指针 *pte=vaddr的页表项(包含物理页地址)
/// @param vaddr 虚拟地址
/// @return 页表项地址
uint32_t* pte_ptr(uint32_t vaddr) {
    uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr) * 4);
    // 现在的pte
    // 页目录索引=高10位=全1
    // 页表索引=中10位=vaddr的高10位=vaddr的页目录索引
    // 物理页偏移=低12位=vaddr的页表索引*4

    // 转换
    // 1.页目录索引=全1, 是第1023个页目录项,其内容是页目录起始位置
    // 2.页表索引=vaddr的页目录索引,该页目录项内容是vaddr的页表地址
    // 3.物理页偏移=低12位=vaddr的页表索引*4,该页表项的内容是vaddr所绑定的物理地址
    return pte;
}

/// @brief 在m_pool指向的物理内存池中分配1个物理页
/// @param m_pool
/// @return  成功则返回页框的物理地址,失败则返回NULL
static void* palloc(struct pool* m_pool) {
    // 找一个物理页面
    int bit_idx = bitmap_scan(&m_pool->pool_bitmap, 1);
    if (bit_idx == -1) { return NULL; }

    // 将这个物理页的位图的比特设置为1
    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;
}

/// @brief 绑定虚拟地址_vaddr与物理地址_page_phyaddr
/// @param _vaddr 虚拟地址
/// @param _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);

    if (*pde & 0x00000001) {  // 页目录项P=1表示已存在
        ASSERT(!(*pte & 0x00000001));
        if (!(*pte & 0x00000001)) {                              // 页表项P=1表示已存在
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);  // US=1,RW=1,P=1
        } else {  // 应该不会执行到这,因为上面的ASSERT会先执行。
            PANIC("pte repeat");
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);  // US=1,RW=1,P=1
        }
    } else {  // 页目录项P=1表示不存在
        // 在内核空间申请一个物理页来放页表
        uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);
        *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);  // 绑定页目录到页表

        // 把刚才申请的页表清0 (int)pte & 0xfffff000)为页表的虚拟地址
        memset((void*)((int)pte & 0xfffff000), 0, PG_SIZE);

        // 绑定页表项到物理地址
        ASSERT(!(*pte & 0x00000001));
        *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);  // US=1,RW=1,P=1
    }
}

/// @brief 分配pg_cnt个页空间,
/// @param  pool_flags 区别内核空间还是用户空间
/// @param pg_cnt 连续的页数量
/// @return 成功则返回起始虚拟地址,失败时返回NULL
void* malloc_page(enum pool_flags pf, uint32_t pg_cnt) {
    ASSERT(pg_cnt > 0 && pg_cnt < 3840);

    // 申请连续的虚机空间
    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;  // 虚拟地址+4096
    }
    return vaddr_start;
}

/// @brief 从内核物理内存池中申请pg_cnt页内存,
/// @param pg_cnt 页数量
/// @return 成功则返回其虚拟地址,失败则返回NULL
void* get_kernel_pages(uint32_t pg_cnt) {
    void* vaddr = malloc_page(PF_KERNEL, pg_cnt);

    // 申请成功,将页框清0后返回
    if (vaddr != NULL) { memset(vaddr, 0, pg_cnt * PG_SIZE); }
    return vaddr;
}

/// @brief 初始化内存池
/// @param all_mem 系统的内存大小
static void mem_pool_init(uint32_t all_mem) {
    put_str("[mem] mem_pool_init start\n");

    //---分配物理内核池和用户池的内存 begin---
    // 统计已使用的内存大小
    // 1个页目录表(4096字节) + 第0个页表((4096字节)) + 第[1-254]个页表(254*4096字节)
    uint32_t page_table_size = PG_SIZE * 256;
    uint32_t used_mem = page_table_size + 0x100000;  // 0x100000为低端1M内存

    // 把可用内存各分一半给内核空间和用户空间 舍弃小于1个页的空间
    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;

    // kernel bitmap length 和 user bitmap length
    uint32_t kbm_length = kernel_free_pages / 8;  // 内核空间位图长度
    uint32_t ubm_length = user_free_pages / 8;    // 用户空间位图长度

    // kernel pool start 和 user pool start
    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;
    //---分配物理内核池和用户池的内存 end---

    // 内核池位图首地址和用户池位图首地址
    kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
    user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);

    //---输出相关信息 begin---
    put_str("[mem] kernel_pool_bitmap_start:0x");
    put_int((int)kernel_pool.pool_bitmap.bits);
    put_str("\n");

    put_str("[mem] kernel_pool_phy_addr_start:0x");
    put_int(kernel_pool.phy_addr_start);
    put_str("\n");

    put_str("[mem] user_pool_bitmap_start:0x");
    put_int((int)user_pool.pool_bitmap.bits);
    put_str("\n");

    put_str("[mem] user_pool_phy_addr_start:0x");
    put_int(user_pool.phy_addr_start);
    put_str("\n");
    //---输出相关信息 begin---

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

    // 虚拟内存池位图长度=真实物理内存池位图长度
    // 虚拟内存池的位图起始地址是物理用户内存池之后
    // 虚拟内存池的起始地址是K_HEAP_START
    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] mem_pool_init done\n");
}

/// @brief 内存管理部分初始化入口
void mem_init() {
    put_str("[mem] mem_init start\n");

    uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
    mem_pool_init(mem_bytes_total);

    put_str("[mem] mem_init done\n");
}

编译

省略

运行

start.sh

#/bin/bash
# 文件: start.sh
# 描述: 启动bochs
# 时间: 2024-07-19
# 来自: ccj


set -x

bin/bochs -f bochsrc.disk

在这里插入图片描述

;