// kern/init/init.c
int kern_init(void) {
extern char edata[], end[];
memset(edata, 0, end - edata);
cons_init(); // init the console
const char *message = "(THU.CST) os is loading ...\0";
cputs(message);
print_kerninfo();
idt_init(); // init interrupt descriptor table
pmm_init(); // 新东西!
clock_init(); // init clock interrupt
intr_enable(); // enable irq interrupt
/* do nothing */
while (1)
;
}
// kern/mm/pmm.c
/* pmm_init - initialize the physical memory management */
void pmm_init(void) {
// We need to alloc/free the physical memory (granularity is 4KB or other size).
// So a framework of physical memory manager (struct pmm_manager)is defined in pmm.h
// First we should init a physical memory manager(pmm) based on the framework.
// Then pmm can alloc/free the physical memory.
init_pmm_manager();
// detect physical memory space, reserve already used memory,
// then use pmm->init_memmap to create free page list
page_init();
// use pmm->check to verify the correctness of the alloc/free function in a pmm
check_alloc_page();
extern char boot_page_table_sv39[]; //我们把汇编里定义的页表所在位置的符号声明进来
satp_virtual = (pte_t*)boot_page_table_sv39;
satp_physical = PADDR(satp_virtual);//然后输出页表所在的地址
cprintf("satp virtual address: 0x%016lx\nsatp physical address: 0x%016lx\n", satp_virtual, satp_physical);
}
// libs/defs.h
/* Return the offset of 'member' relative to the beginning of a struct type */
#define offsetof(type, member) \
((size_t)(&((type *)0)->member))
/* *
* to_struct - get the struct from a ptr
* @ptr: a struct pointer of member
* @type: the type of the struct this is embedded in
* @member: the name of the member within the struct
* */
#define to_struct(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
// kern/mm/memlayout.h
/* *
* struct Page - Page descriptor structures. Each Page describes one
* physical page. In kern/mm/pmm.h, you can find lots of useful functions
* that convert Page to other data types, such as physical address.
* */
struct Page {
int ref; // page frame's reference counter
uint64_t flags; // array of flags that describe the status of the page frame
unsigned int property; // the num of free block, used in first fit pm manager
list_entry_t page_link; // free list link
};
/* Flags describing the status of a page frame */
#define PG_reserved 0 // if this bit=1: the Page is reserved for kernel, cannot be used in alloc/free_pages; otherwise, this bit=0
#define PG_property 1 // if this bit=1: the Page is the head page of a free memory block(contains some continuous_addrress pages), and can be used in alloc_pages; if this bit=0: if the Page is the the head page of a free memory block, then this Page and the memory block is alloced. Or this Page isn't the head page.
//这几个对page操作的宏用到了atomic.h的原子操作
#define SetPageReserved(page) set_bit(PG_reserved, &((page)->flags))
#define ClearPageReserved(page) clear_bit(PG_reserved, &((page)->flags))
#define PageReserved(page) test_bit(PG_reserved, &((page)->flags))
#define SetPageProperty(page) set_bit(PG_property, &((page)->flags))
#define ClearPageProperty(page) clear_bit(PG_property, &((page)->flags))
#define PageProperty(page) test_bit(PG_property, &((page)->flags))
// convert list entry to page
#define le2page(le, member) \
to_struct((le), struct Page, member)
/* free_area_t - maintains a doubly linked list to record free (unused) pages */
typedef struct {
list_entry_t free_list; // the list header
unsigned int nr_free; // # of free pages in this free list
} free_area_t;
// kern/mm/pmm.h
/* *
* PADDR - takes a kernel virtual address (an address that points above
* KERNBASE),
* where the machine's maximum 256MB of physical memory is mapped and returns
* the
* corresponding physical address. It panics if you pass it a non-kernel
* virtual address.
* */
#define PADDR(kva) \
({ \
uintptr_t __m_kva = (uintptr_t)(kva); \
if (__m_kva < KERNBASE) { \
panic("PADDR called with invalid kva %08lx", __m_kva); \
} \
__m_kva - va_pa_offset; \
})
/* *
* KADDR - takes a physical address and returns the corresponding kernel virtual
* address. It panics if you pass an invalid physical address.
* */
/*
#define KADDR(pa) \
({ \
uintptr_t __m_pa = (pa); \
size_t __m_ppn = PPN(__m_pa); \
if (__m_ppn >= npage) { \
panic("KADDR called with invalid pa %08lx", __m_pa); \
} \
(void *)(__m_pa + va_pa_offset); \
})
*/
extern struct Page *pages;
extern size_t npage;
// kern/mm/pmm.c
// pages指针保存的是第一个Page结构体所在的位置,也可以认为是Page结构体组成的数组的开头
// 由于C语言的特性,可以把pages作为数组名使用,pages[i]表示顺序排列的第i个结构体
struct Page *pages;
size_t npage = 0;
uint64_t va_pa_offset;
// memory starts at 0x80000000 in RISC-V
const size_t nbase = DRAM_BASE / PGSIZE;
//(npage - nbase)表示物理内存的页数
static void page_init(void) {
va_pa_offset = PHYSICAL_MEMORY_OFFSET; //硬编码 0xFFFFFFFF40000000
uint64_t mem_begin = KERNEL_BEGIN_PADDR;//硬编码 0x80200000
uint64_t mem_size = PHYSICAL_MEMORY_END - KERNEL_BEGIN_PADDR;
uint64_t mem_end = PHYSICAL_MEMORY_END; //硬编码 0x88000000
cprintf("physcial memory map:\n");
cprintf(" memory: 0x%016lx, [0x%016lx, 0x%016lx].\n", mem_size, mem_begin,
mem_end - 1);
uint64_t maxpa = mem_end;
if (maxpa > KERNTOP) {
maxpa = KERNTOP;
}
npage = maxpa / PGSIZE;
extern char end[];
pages = (struct Page *)ROUNDUP((void *)end, PGSIZE);
//把pages指针指向内核所占内存空间结束后的第一页
//一开始把所有页面都设置为保留给内核使用的,之后再设置哪些页面可以分配给其他程序
for (size_t i = 0; i < npage - nbase; i++) {
SetPageReserved(pages + i);//记得吗?在kern/mm/memlayout.h定义的
}
//从这个地方开始才是我们可以自由使用的物理内存
uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * (npage - nbase));
//按照页面大小PGSIZE进行对齐, ROUNDUP, ROUNDDOWN是在libs/defs.h定义的
mem_begin = ROUNDUP(freemem, PGSIZE);
mem_end = ROUNDDOWN(mem_end, PGSIZE);
if (freemem < mem_end) {
//初始化我们可以自由使用的物理内存
init_memmap(pa2page(mem_begin), (mem_end - mem_begin) / PGSIZE);
}
}
page_init()的代码里,我们调用了一个函数init_memmap(), 这和我们的另一个结构体pmm_manager有关。虽然C语言基本上不支持面向对象,但我们可以用类似面向对象的思路,把”物理内存管理“的功能集中给一个结构体。我们甚至可以让函数指针作为结构体的成员,强行在C语言里支持了”成员函数“。可以看到,我们调用的init_memmap()实际上又调用了pmm_manager的一个”成员函数“。如果你不熟悉函数指针的用法,可以读一读《The C Programming Language》的相关章节(待补充:第几章第几节?)。
// kern/mm/pmm.c
// physical memory management
const struct pmm_manager *pmm_manager;
// init_memmap - call pmm->init_memmap to build Page struct for free memory
static void init_memmap(struct Page *base, size_t n) {
pmm_manager->init_memmap(base, n);
}
// kern/mm/pmm.h
#ifndef __KERN_MM_PMM_H__
#define __KERN_MM_PMM_H__
#include <assert.h>
#include <atomic.h>
#include <defs.h>
#include <memlayout.h>
#include <mmu.h>
#include <riscv.h>
// pmm_manager is a physical memory management class. A special pmm manager -
// XXX_pmm_manager
// only needs to implement the methods in pmm_manager class, then
// XXX_pmm_manager can be used
// by ucore to manage the total physical memory space.
struct pmm_manager {
const char *name; // XXX_pmm_manager's name
void (*init)(
void); // 初始化XXX_pmm_manager内部的数据结构(如空闲页面的链表)
void (*init_memmap)(
struct Page *base,
size_t n); //知道了可用的物理页面数目之后,进行更详细的初始化
struct Page *(*alloc_pages)(
size_t n); // 分配至少n个物理页面, 根据分配算法可能返回不同的结果
void (*free_pages)(struct Page *base, size_t n); // free >=n pages with
// "base" addr of Page
// descriptor
// structures(memlayout.h)
size_t (*nr_free_pages)(void); // 返回空闲物理页面的数目
void (*check)(void); // 测试正确性
};
extern const struct pmm_manager *pmm_manager;
void pmm_init(void);
struct Page *alloc_pages(size_t n);
void free_pages(struct Page *base, size_t n);
size_t nr_free_pages(void); // number of free pages
#define alloc_page() alloc_pages(1)
#define free_page(page) free_pages(page, 1)