Lab5: Page Bookkeeping

Translation Function

Kernel Virtual Address, Physical Address, Page Frame Number 之間的轉換函式

include/mm.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef _MM_H
#define _MM_H

#define KERNEL_VIRT_BASE 0xFFFF000000000000
#define PAGE_TABLE_SIZE 4096
#define PAGE_SHIFT 12
#define PAGE_SIZE (1UL << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE - 1))
#define PAGE_NUM (0x40000000 / PAGE_SIZE)

#ifndef __ASSEMBLER__

#include <include/types.h>

#define KVA_TO_PA(addr) ((uint64_t) addr << 16 >> 16)
#define PA_TO_KVA(addr) ((uint64_t) addr | KERNEL_VIRT_BASE)
#define PA_TO_PFN(addr) ((uint64_t) addr >> PAGE_SHIFT)
#define PFN_TO_PA(idx) ((uint64_t) idx << PAGE_SHIFT)

#endif
#endif

Bookkeeping

include/mm.h

1
2
3
4
5
6
7
8
enum page_flag { PAGE_USED = 1 << 0 };

typedef struct {
uint32_t flag;
void *physical;
} page_t;

extern page_t page[PAGE_NUM];

kernel/mm.c

kernel image 所佔據的 page、kernel stack 所佔據的 page (0x8000 前)必須標示成使用中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
page_t page[PAGE_NUM];

bool is_set(uint32_t target, uint32_t val)
{
return !!(target & val);
}

void set(uint32_t *target, uint32_t val)
{
*target |= val;
}

void unset(uint32_t *target, uint32_t val)
{
*target &= ~val;
}

void page_init()
{
extern char _kernel_end;
int i = 0;
for (; i < PA_TO_PFN(KVA_TO_PA(&_kernel_end)); ++i) {
// kernel stack + kernel image
page[i].physical = (void *) ((physaddr_t) i << PAGE_SHIFT);
page[i].flag = 0;
set(&page[i].flag, PAGE_USED);
}
for (; i < PA_TO_PFN(KVA_TO_PA(MMIO_BASE)); ++i) {
page[i].flag = 0;
}
for (; i < PAGE_NUM; ++i) {
// MMIO
page[i].physical = (void *) ((physaddr_t) i << PAGE_SHIFT);
page[i].flag = 0;
set(&page[i].flag, PAGE_USED);
}
}

kernel/start.S

稍微試算page_t page[PAGE_NUM]的佔用空間

  • PAGE_NUM=1G/4K
  • sizeof(page_t)=8 Bytes
  • sizeof(page[PAGE_NUM])=16 MB

發現已經遠超過 adr 的定址範圍:+-1MB,需要將 .bss 初始化程式更改一下

1
2
3
4
5
6
7
8
9
10
    // clear bss
- adr x0, __bss_start
- adr x1, __bss_end
+ ldr x0, =__bss_start
+ ldr x1, =__bss_end
sub x1, x1, x0
bl memzero

+ bl page_init
bl main

page_alloc, page_free

kernel/mm.h

1
2
3
4
page_t *get_free_page();
void *page_alloc_kernel();
void *page_alloc_user();
void page_free(void *virt_addr);

kernel/mm.c

page_alloc_kernelpage_alloc_user 取得閒置的 page 並返回 page 的 virtual address 而非 physical address,原因在於一開始我們已啟用 virtual memory。

一旦分配 page,會在 task_struct 紀錄。page_alloc_kernelpage_alloc_user 的差別僅紀錄該 page 是分配給 kernel space 或是 user space,但這部份尚未實作,因此兩個函式目前一樣。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
page_t *get_free_page()
{
extern char _kernel_end;
int pfn_start = PA_TO_PFN(KVA_TO_PA(&_kernel_end)),
pfn_end = PA_TO_PFN(KVA_TO_PA(MMIO_BASE));

// find an available page
for (; pfn_start < pfn_end && is_set(page[pfn_start].flag, PAGE_USED);
++pfn_start)
;
if (pfn_start == pfn_end)
return NULL;

// initialize the page
physaddr_t phy_addr = (physaddr_t) pfn_start << PAGE_SHIFT;
uintptr_t virt_addr = phy_addr | KERNEL_VIRT_BASE;
memset((void *) virt_addr, 0, PAGE_SIZE);

// save to page struct
page[pfn_start].physical = (void *) phy_addr;
set(&page[pfn_start].flag, PAGE_USED);

// return pointer to page struct
return &page[pfn_start];
}

// get a page for kernel space
void *page_alloc_kernel()
{
page_t *page_ptr = get_free_page();
if (!page_ptr)
return NULL;

physaddr_t phy_addr = (physaddr_t) page_ptr->physical;
uintptr_t virt_addr = phy_addr | KERNEL_VIRT_BASE;

return (void *) virt_addr;
}

// get a page for user space
void *page_alloc_user()
{
page_t *page_ptr = get_free_page();
if (!page_ptr)
return NULL;

physaddr_t phy_addr = (physaddr_t) page_ptr->physical;
uintptr_t virt_addr = phy_addr | KERNEL_VIRT_BASE;

return (void *) virt_addr;
}

void page_free(void *virt_addr)
{
int pfn = PA_TO_PFN(KVA_TO_PA((uintptr_t) virt_addr));
unset(&page[pfn].flag, PAGE_USED);
page[pfn].physical = NULL;
}