用bitmap来进行资源的管理,bitmap的一位可以对应我们想要让它管理的资源的一个单位。这里,我们用bitmap管理内存资源,包括虚拟内存和物理内存,单位为一页。
bitmap
struct bitmap {
uint_32 map_size;
uint_8* bits;
};
定义物理内存池,除了需要bitmap来管理外,还需要规定你所能管理的最大范围(起始的地方以及池子的大小)。
虚拟内存大小足足有4GB之多,对于我们来说几乎是无限的。结构体中就没有pool_size这个成员。
struct pool {
struct bitmap btmp;
uint_32 paddr_start;
uint_32 pool_size;
};
struct virt_addr {
struct bitmap btmp;
uint_32 vaddr_start;
};
struct pool kernel_pool,user_pool;
struct virt_addr ker_vaddr;
物理内存:减去已经最初1MB,以及使用过的页目录表和页表所占大小。剩下的部分便是可用的物理内存了。
虚拟内存:因为这里管理的是内核的虚拟地址,所以从已经用过的 0~0xc0100000 往上。
void mem_pool_init(uint_32 total_mem)
{
put_str(" mem pool init start\n");
uint_32 used_mem = 0x100000 + PAGE_SIZE*256;
uint_32 free_mem = total_mem - used_mem;
uint_32 all_free_pages = free_mem / PAGE_SIZE;
uint_32 ker_free_pages = all_free_pages / 2;
uint_32 usr_free_pages = all_free_pages - ker_free_pages;
//初始化 kernel_pool
uint_32 kbm_len = ker_free_pages / 8; //管理的区域小于实际区域
kernel_pool.btmp.bits = (uint_8*)BTMP_START;
kernel_pool.btmp.map_size = kbm_len;
kernel_pool.paddr_start = used_mem;
kernel_pool.pool_size = ker_free_pages * PAGE_SIZE;
//初始化 user_pool
uint_32 ubm_len = usr_free_pages / 8;
user_pool.btmp.bits = (uint_8*)(BTMP_START + kbm_len);
user_pool.btmp.map_size = ubm_len;
user_pool.paddr_start = used_mem + ker_free_pages*PAGE_SIZE;
user_pool.pool_size = usr_free_pages * PAGE_SIZE;
bit_init(&kernel_pool.btmp);
bit_init(&user_pool.btmp);
//初始化虚拟内存
ker_vaddr.btmp.bits = (uint_8*)(BTMP_START + kbm_len + ubm_len);
ker_vaddr.btmp.map_size = ker_free_pages / 8;
ker_vaddr.vaddr_start = K_HEAP_START;
put_str(" mem pool init done\n");
}
申请资源就是将对应bitmap中的bit给置1。
void bitmap_set(struct bitmap* btmp,uint_32 bit_idx,uint_8 value)
{
ASSERT(value==0 || value==1);
uint_32 bit_byte = bit_idx / 8;
uint_32 bit_odd = bit_idx % 8;
if (value) {
btmp->bits[bit_byte] |= (BIT_MASK << bit_odd);
} else {
btmp->bits[bit_byte] &= ~(BIT_MASK << bit_odd);
}
}
申请虚拟内存因为是连续的,所以可以一次申请一大块,然后返回所申请的连续虚拟页的起始地址以供后续使用。
static void* get_vaddr(enum pool_flag pf,uint_32 pcnt)
{
uint_32 vaddr_get;
if (pf == PF_KERNEL)
{
uint_32 bit_start = bit_scan(&ker_vaddr.btmp,pcnt);
if (bit_start == -1){
return NULL;
}
uint_32 cnt = 0;
while (cnt < pcnt){
bitmap_set(&ker_vaddr.btmp,cnt++,1);
}
vaddr_get = ker_vaddr.vaddr_start + bit_start*PAGE_SIZE;
}
else
{
//用户内存池,以后再实现
}
return (void*)vaddr_get;
}
物理地址随着申请虚拟地址时被动分配,每次申请一个页。不一定连续。
void* malloc_page(enum pool_flag pf,uint_32 pcnt)
{
ASSERT(pcnt>0 && pcnt<64000); //按照用户和内核各250MB来计算
uint_32 cnt = pcnt;
struct pool* m_pool = pf==PF_KERNEL? &kernel_pool : &user_pool;
void* vaddr_start = get_vaddr(pf,pcnt);
if (vaddr_start == NULL){
return NULL;
}
void* vaddr = vaddr_start;
while (cnt--)
{
void* paddr = palloc(m_pool);
if (paddr == NULL) {
return NULL;
}
page_table_add(vaddr,paddr);
vaddr =(void*)((uint_32)vaddr + PAGE_SIZE);
}
return vaddr_start;
}
static void* palloc(struct pool* m_pool)
{
uint_32 bit_off = bit_scan(&m_pool->btmp,1);
if (bit_off == -1){
return NULL;
}
bitmap_set(&m_pool->btmp,bit_off,1);
return (void*)(m_pool->paddr_start + bit_off*PAGE_SIZE);
}
我们现在已经进入了分页模式中,要访问某块物理内存必须通过相应的虚拟内存来进行。
取得pte的虚拟地址。从后往前推导,最后12位是在页表中的偏移地址,中间10位是页表在页目录表里的偏移地址。这就够了,所以高10位全填1,页目录表的最后一项指向它自己,因此这会访问到页目录表自身。
取pde的虚拟地址也是如此,最后12位是在页目录表中的偏移量,前面回环就完事了。
uint_32* pte_ptr(uint_32 vaddr)
{
uint_32 v = (0xffc00000 + (PDE_IDX(vaddr)<<12) + (PTE_IDX(vaddr)<<2));
return (uint_32*)v;
}
uint_32* pde_ptr(uint_32 vaddr)
{
uint_32* v = (uint_32*)(0xfffff000 + (PDE_IDX(vaddr)<<2));
return v;
}
添加虚拟地址与物理地址的映射,主要就是往pte写东西。如果连pde都没有需要把pde及其所指向的页表给补上。
新申请到的页大多数情况下不能直接拿来用,因为有可能是脏的。别忘记事前得先memset清零一下~
static void page_table_add(void* _vaddr,void* _paddr)
{
uint_32 vaddr=(uint_32)_vaddr,paddr=(uint_32)_paddr;
uint_32* pde = pde_ptr(vaddr),*pte = pte_ptr(vaddr);
if ((*pde & PG_P) == 1)
{
ASSERT((*pte & PG_P)==0);
*pte = paddr | PG_P | PG_RW_RW | PG_US_U;
}
else
{
void* new_phy_page = palloc(&kernel_pool);
*pde = (uint_32)new_phy_page | PG_P | PG_RW_RW | PG_US_U;
memset((void*)((uint_32)pte & 0xfffff000),0,PAGE_SIZE);
ASSERT((*pte & PG_P)==0);
*pte = paddr | PG_P | PG_RW_RW | PG_US_U;
}
}