内存管理--实现内存池

实现位图
#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);
void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value);
int bitmap_scan(struct bitmap* btmp, uint32_t cnt);
  • 位图初始化
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_index = bit_idx / 8;
    uint32_t bit_odd = bit_idx % 8;

    return (btmp->bits[byte_index] & (BITMAP_MASK << bit_odd));
}
  • 将位图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)      // value为0x1
        btmp->bits[byte_idx] |=  (BITMAP_MASK << bit_odd);
    else            // value为0x0
        btmp->bits[byte_idx] &= ~(BITMAP_MASK << bit_odd);
}
  • 在位图中申请连续 cnt个位,成功则返回其起始位下标,失败返回-1
int bitmap_scan(struct bitmap *btmp, uint32_t cnt)
{
    uint32_t idx_byte = 0;

    // 0xff 表示该位已被分配
    while((0xff == btmp->bits[idx_byte]) && (idx_byte < btmp->btmp_bytes_len))
        idx_byte++;

    if (idx_byte == btmp->btmp_bytes_len)
        return -1;

    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))) // 若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;
}
内存管理--实现内存池_第1张图片
用位图实现内存分配管理.png
内存池规划

在实模式下,程序中的地址就等于物理地址,在保护模式下,程序地址变成了虚拟地址。虚拟地址转换为物理地址由分页机制完成。因此,OS有责任把这两种地址分别管理,并通过页表将这两类地址关联。

先讨论下如何规划物理内存:
一种方案是将物理内存划分成两部分,一部分只用来运行内核,另一部分只用来运行用户进程,所以,将其分为内核物理内存池,用户物理内存池。

物理内存划分.png

再讨论虚拟地址内存:
分页机制一个好处就是每个任务都有自己的 4GB 虚拟地址空间,各程序中的虚拟地址可以相同,只需要映射的物理地址不一样就可以。

内存管理--实现内存池_第2张图片
内存管理.png
#define MEM_BITMAP_BASE 0xc009a000  
#define K_HEAP_START 0xc0100000  // 内核进程堆的起始地址

struct pool
{
    struct bitmap pool_bitmap;  // 本内存池用到的位图,用于管理物理内存
    uint32_t phy_addr_start;    // 本内存池所管理物理内存的起始地址
    uint32_t pool_size;         // 本内存池容量
};

struct pool kernel_pool;    // 内核物理内存池
struct pool user_pool;      // 用户物理内存池

struct virtual_addr
{
    struct bitmap vaddr_bitmap;     // 虚拟地址池用到的位图
    uint32_t vaddr_start;           // 虚拟地址池起始地址
};

struct virtual_addr kernel_vaddr;   // 内核虚拟地址池
  • mem_pool_init 初始化内存池信息
    void mem_pool_init(uint32_t all_mem)  // all_mem 表示物理内存总大小,这里32MB
    {
        uint32_t page_table_size = PG_SIZE * 256;   // 1个页目录表 + 0和768页目录项指向同一个页表,+ 769~1023=254个页表=256
        uint32_t used_mem = 0x100000 + page_table_size; // 0x100000, 1MB低端内存, 用于OS
    
        uint32_t free_mem = all_mem - used_mem;     // 32MB-2MB = 30MB
    
        uint16_t all_free_pages = free_mem / PG_SIZE;   //  30MB/4K = 7680
        uint16_t kernel_free_pages = all_free_pages / 2;    // 7680/2 = 3840
        uint16_t user_free_pages = all_free_pages - kernel_free_pages;  // 7680-3840 = 3840
    
        uint32_t kbm_length = kernel_free_pages / 8; // kernel 内存池中位图长度,位图中每一位表示一个页框 3840/8 = 480
        uint32_t ubm_length = user_free_pages / 8;   // user 内存池中位图长度,位图中每一位表示一个页框  3840/8 = 480
    
        uint32_t kp_start = used_mem;       // kernel内存池能用的内存起始地址
        uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE; // user内存池能用的内存起始地址
    
    
        kernel_pool.phy_addr_start = kp_start;
        kernel_pool.pool_size = kernel_free_pages * PG_SIZE;    // 15MB kernel 内存池
        kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;  // MEM_BITMAP_BASE = 0xc009a000 存放内核内存池位图
        kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
        bitmap_init(&(kernel_pool.pool_bitmap));
    
        user_pool.phy_addr_start = up_start;
        user_pool.pool_size = user_free_pages * PG_SIZE;     // 15MB user 内存池
        user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length); // 存放用户内存池位图紧随其后
        user_pool.pool_bitmap.btmp_bytes_len = ubm_length;
        bitmap_init(&(user_pool.pool_bitmap));
    
      /* 下面初始化内核虚拟地址池的位图,按实际物理内存大小生成数组 */
        kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;      // 用于维护内核堆的虚拟地址,所以要和内核内存池(15MB)大小一致
    
      /* 虚拟地址0xc0000000~0xc00fffff 映射 物理地址0x00000000~0x000fffff(1MB) */
        kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length); // 0xc009a3c0
        bitmap_init(&kernel_vaddr.vaddr_bitmap);
    
        kernel_vaddr.vaddr_start = K_HEAP_START;    // 0xc0100000
    }
    
内存管理--实现内存池_第3张图片
image.png
  • 算出虚拟地址vaddr的页目录项pde地址

    #define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)   // 算出页目录项
    uint32_t* pde_ptr(uint32_t vaddr)
    {
        // 虚地址 0xfffff000 映射物理地址:0x00100000(页目录表起始地址)
        uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr) * 4);
        return pde;
    }
    
  • 算出虚拟地址vaddr对应的页表项pte地址

    #define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)   // 算出页表项
    uint32_t* pte_ptr(uint32_t vaddr)
    {
        // 虚地址 0xffc00000 映射物理地址 0x00100000(页目录表地址)
        uint32_t* pte = (uint32_t*)(0xffc00000 +
              ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr)*4);
        return pte;
    }
    
  • 算出虚拟地址映射的物理地址

    uint32_t addr_v2p(uint32_t vaddr)
    {
        uint32_t* pte = pte_ptr(vaddr);
        return ((*pte & 0xfffff000) + (vaddr & 0x00000fff));
    }
    
  • 在 pf 表示的虚拟地址池中申请 pg_cnt 个连续的虚拟页,成功则返回虚拟页的起始地址,失败则返回NULL

    static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt)
    {
        int vaddr_start = 0;
        int bit_idx_start = -1;
        uint32_t cnt = 0;
    
        if (pf == PF_KERNEL)
        {
            bit_idx_start = bitmap_scan(&(kernel_vaddr.vaddr_bitmap), pg_cnt);  // bit_idx_start 虚拟页起始位
    
            if (bit_idx_start == -1)    // 没有足够的虚拟页
                return NULL;
    
            while(cnt < pg_cnt)
            {
                bitmap_set(&(kernel_vaddr.vaddr_bitmap), bit_idx_start + cnt, 1);   // 更新位图
                cnt++;
            }
    
            vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
        }
        else if (pf == PF_USER)
        {
            struct task_struct* cur = running_thread();
            bit_idx_start = bitmap_scan(&(cur->userprog_vaddr.vaddr_bitmap), pg_cnt);
    
            if (bit_idx_start == -1)
                return NULL;
    
            while (cnt < pg_cnt)
            {
                bitmap_set(&(cur->userprog_vaddr.vaddr_bitmap), bit_idx_start + cnt, 1);
                cnt++;
            }
    
            vaddr_start = cur->userprog_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
    
            /* (0xc0000000 - PG_SIZE)做为用户3级栈已经在start_process被分配 */
            ASSERT((uint32_t)vaddr_start < (0xc0000000 - PG_SIZE));
        }
    
        return (void*)vaddr_start;
    }
    
  • 在 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);
        uint32_t page_phyaddr = ( m_pool->phy_addr_start + (bit_idx * PG_SIZE));
    
        return (void*)page_phyaddr;
    }
    
  • 页表中添加虚拟地址_vaddr到物理地址_page_phyaddr的映射,设置页目录项和页表项

    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* pde = pde_ptr(vaddr);     // 虚地址对应的pde
        uint32_t* pte = pte_ptr(vaddr);     // 虚地址对应的pte
    
        if (*pde & 0x00000001)  // 页目录项存在
        {
            ASSERT(!(*pte & 0x00000001));
    
            if (!(*pte & 0x00000001))   // 页表项不存在
                *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);      // 创建页目录项
            ASSERT(!(*pte & 0x00000001));
            *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);     // 创建页表项
    
            memset((void*)((int)pte & 0xfffff000), 0, PG_SIZE);     // 初始化页框
        }
    }
    
  • 在内核或者用户空间分配 pg_cnt 个物理页

    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;
        uint32_t 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;
    }
    
    内存管理--实现内存池_第4张图片
    malloc.png

你可能感兴趣的:(内存管理--实现内存池)