mmu.h:
/*
o 4G -----------> +----------------------------+------------0x100000000
o | ... | kseg3
o +----------------------------+------------0xe000 0000
o | ... | kseg2
o +----------------------------+------------0xc000 0000
o | Interrupts & Exception | kseg1
o +----------------------------+------------0xa000 0000
o | Invalid memory | /|\
o +----------------------------+----|-------Physics Memory Max
o | ... | kseg0
o VPT,KSTACKTOP-----> +----------------------------+----|-------0x8040 0000-------end
o | Kernel Stack | | KSTKSIZE /|\
o +----------------------------+----|------ |
o | Kernel Text | | PDMAP
o KERNBASE -----> +----------------------------+----|-------0x8001 0000 |
o | Interrupts & Exception | \|/ \|/
o ULIM -----> +----------------------------+------------0x8000 0000-------
o | User VPT | PDMAP /|\
o UVPT -----> +----------------------------+------------0x7fc0 0000 |
o | PAGES | PDMAP |
o UPAGES -----> +----------------------------+------------0x7f80 0000 |
o | ENVS | PDMAP |
o UTOP,UENVS -----> +----------------------------+------------0x7f40 0000 |
o UXSTACKTOP -/ | user exception stack | BY2PG |
o +----------------------------+------------0x7f3f f000 |
o | Invalid memory | BY2PG |
o USTACKTOP ----> +----------------------------+------------0x7f3f e000 |
o | normal user stack | BY2PG |
o +----------------------------+------------0x7f3f d000 |
a | | |
a ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
a . . |
a . . kuseg
a . . |
a |~~~~~~~~~~~~~~~~~~~~~~~~~~~~| |
a | | |
o UTEXT -----> +----------------------------+ |
o | | 2 * PDMAP \|/
a 0 ------------> +----------------------------+ -----------------------------
o
*/
宏定义:
BY2PG 一页大小
PDMAP 4M 由页目录条目映射的字节
PGSHIFT、PDSHIFT 12偏移量
PDX(va) 页目录偏移量31-22 位
PTX(va) 页表偏移量21-12
PTE_ADDR(pte) 将低12位清零,虚实地址转换时用
PPN(va) 右移12位,可以获得第多少页
VPN(va) 和PPN(va)相同
PTE_G 0x0100全局权限
PTE_V 0x0200有效位权限,如果&PTE_V为0,则说明无效
PTE_R 0x0400写权限
PADDR(kva) 虚到实地址
KADDR(pa) 实到虚地址
错误:
E_NO_MEM 4
地址空间:
VPT,KSTACKTOP 0x80400000
ULIM 0x80000000 //从0x80000000开始逐渐往下减
UVPT (ULIM - PDMAP) 0x7fc0 0000
UPAGES (UVPT - PDMAP) 0x7f80 0000
UTOP UENVS UXSTACKTOP (UPAGES - PDMAP) 0x7f40 0000
USTACKTOP (UTOP - 2*BY2PG) 0x7f3f e000是用户栈,内核栈在0x8040 0000)
数据类型:
Pde u_long类型,页目录地址
Pte u_long类型,页表地址
npage u_long类型,页个数
types.h:
ROUND(a,n) 常用ROUND(a, BY2PG)使其页对其
ROUNDDOWN(a,n) (((u_long)(a)~((n)-1))
pmap.h:
数据类型:
pages struct Page*类型
函数:
page2ppn(struct Page *pp) 返回pp - pages(u_long),表示页偏移
page2pa(struct Page *pp) 返回u_long类型,该页的物理地址
pa2page(u_long pa) 返回struct Page *,该物理地址的页
page2kva(struct Page *pp) 返回u_long类型,该页的虚拟地址
va2pa(Pde *pgdir, u_long va) 返回u_long类型,虚拟地址va通过页表对应的物理地址
queue.h
宏函数
LIST_EMPTY(head) 判断链表是否为空
LIST_FIRST(head) 获取链表的表头
LIST_FOREACH(var, head, field) for循环遍历链表
LIST_INIT(head) 初始化链表表头为NULL
LIST_INSERT_AFTER(listelm, elm, field) 在listelm后面插入elm
LIST_INSERT_BEFORE(listelm, elm, field) 在listelm前面插入elm
LIST_INSERT_HEAD(head, elm, field) 头插法插入elm
LIST_INSERT_TAIL(head, elm, field) 尾插法插入elm
LIST_NEXT(elm, field) elm 的next指针
LIST_REMOVE(elm, field) 删除链表中的elm
init.c:
void bcopy(const void *src, void *dst, size_t len)//src内容复制到dst上
void bzero(void *b, size_t len)
pmap.c:
函数:
mips_detect_memory:初始化maxpa、basemem、npage
void *alloc(u_int n, u_int align, int clear):分配 n 字节的空间,同时保证 align 可以 整除初始虚拟地址,若 clear 为真则将对应空间的值清零,否则不清零。返回值为分配空间的初始虚拟地址,freemem为未使用的虚拟地址
mips_vm_init:分配一级页表空间,链式指针空间,进程空间
page_init:页空间分配,即可用页用链表连起来
int page_alloc(struct Page **pp):从链表头部分配空闲空间
void page_free(struct Page *pp):判断 pp 指向内存控制块对应的物理页面引用次数是否为 0,是则把该页回链表
page_decref(struct Page *pp),它的作用是令 pp 对应内存控制块的引用次数减少 1,如果引用次数为 0 则会调用下面的 page_free 函数将对应物理页面重新设置为空闲页面。
Pte *boot_pgdir_walk(Pde *pgdir, u_long va, int create):返回一级页表基地址 pgdir 对应的两级页表结构中,va 这个虚拟地址所在的二级页表项,如果 create 不为 0 且对应的二级页表不存在则会使用 alloc 函数分配一页物理内存用于存放。这里之所以使用 alloc 而不用 page_alloc 是因为这个函数是在内核启动过程中使用的,此时还没有建立好物理内存管理机制。
void boot_map_segment(Pde *pgdir, u_long va, u_long size, u_long pa, int perm)将一级页表基地址 pgdir 对应的两级页表结构做区间地址映射,将虚拟地址区间 [va, va + size − 1] 映射到物理地址区间 [pa, pa + size − 1]
int pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte)如果空间不够允许失败,因此将返回值变为 int,若为 0 代表执行成功否则为一个失败码,同时将原本的返回值 Pte * 放到 ppte 所指的空间上。此外,因为该函数的调用在启动之后,create 不为 0 且对应的二级页表不存在时,它使用 page_alloc 而不使用 alloc 进行物理页面的分配。
int page_insert(Pde *pgdir, struct Page *pp, u_long va, u_int perm),这个函数的作用是将一级页表基地址 pgdir 对应的两级页表结构中 va 这一虚拟地址映射到内存控制块 pp 对应的物理页面,并将页表项权限为设置为 perm。
struct Page * page_lookup(Pde *pgdir, u_long va, Pte **ppte),它的作用是返回一级页表基地址 pgdir 对应的两级页表结构中 va 这一虚拟地址映射对应的物理页面对应的内存控制块,同时将 ppte 指向的空间设为对应的二级页表项地址。
void page_remove(Pde *pgdir, u_long va),它的作用是删除一级页表基地址 pgdir 对应的两级页表结构中 va 这一虚拟地址对物理地址的映射,如果存在这样的映射,那么对应物理页面的引用次数会减少。
void pageout(int va, int context),一级页表基地址 context 对应的两级页表结构中 va 新增这一虚拟地址的映射,对应的物理页面通过函数 page_alloc 获取而不特殊指定,这对应其「被动」的性质。该函数的具体调用流程将在下一部分进行介绍。
e_phoff:此字段指明程序头表(program header table)开始处在文件中的偏移量。如果没
有程序头表,该值应设为 0。
e_phnum:此字段表明程序头表中总共有多少个表项。如果一个目标文件中没有程序头
表,该值应设为 0
e_phentsize:此字段表明在程序头表中每一个表项的大小,以字节为单位
p_offset:此数据成员给出本段内容在文件中的位置,即段内容的开始位置相对于文件开头的偏移量。
p_vaddr:此数据成员给出本段内容的开始位置在进程空间中的虚拟地址。
p_filesz:此数据成员给出本段内容在文件中的大小,单位是字节,可以是 0。
p_memsz:此数据成员给出本段内容在内容镜像中的大小,单位是字节,可以是 0。
初始化:mips_vm_init()/env_init
创建进程:|-env_alloc()-env_set_vm()-page_alloc()
|-load_icode()|-page_alloc()
|-load_elf()-load_icode_mapper()
trap.h:
struct Trapframe {
unsigned long regs[32]; // 32 个通用寄存器
unsigned long cp0_status; // CP0 状态寄存器
unsigned long hi; // 乘(除)法高位(模)寄存器
unsigned long lo; // 乘(除)法低位(商)寄存器
unsigned long cp0_badvaddr; // 异常发生地址
unsigned long cp0_cause; // CP0 cause 寄存器
unsigned long cp0_epc; // 异常返回地址
unsigned long pc; // PC计数器,程序运行的地址
};
kernel_elfloader.c
int is_elf_format(u_char *binary)
int load_elf(u_char *binary, int size, u_long *entry_point, void *user_data,int (*map)(u_long va, u_int32_t sgsize,u_char *bin, u_int32_t bin_size, void *user_data))
ptr_ph_table = binary + ehdr->e_phoff;
ph_entry_count = ehdr->e_phnum;
ph_entry_size = ehdr->e_phentsize;
while (ph_entry_count--) {//程序头数量
phdr = (Elf32_Phdr *)ptr_ph_table;//程序头首地址
if (phdr->p_type == PT_LOAD) {
r = map(phdr->p_vaddr, phdr->p_memsz,binary + phdr->p_offset, phdr->p_filesz, user_data);//映射:程序虚拟地址、内存中大小、文件首地址、文件内容大小
if(r < 0) return r;
}
ptr_ph_table += ph_entry_size;//程序头首地址+每个程序头大小=下一个程序头首地址
}
env.c
envs 进程结构体指针
env_free_list 头部,是一个结构体,表示空闲进程块
env_sched_list[2] 头部,是一个结构体,表示可运行进程块
int load_icode_mapper(u_long va, u_int32_t sgsize,u_char *bin, u_int32_t bin_size, void *user_data)//将各个segment加载到内存中
va(该段需要被加载到的虚地址)、sgsize(该段在内存中的大小)、bin(该段在ELF文件中的内容,即是我们需要加载的段的起始地址)、bin_size(该段在文件中的大小)。user_data就是当前进程。
int page_insert(Pde *pgdir, struct Page *pp, u_long va, u_int perm),这个函数的作用是将一级页表基地址 pgdir 对应的两级页表结构中 va 这一虚拟地址映射到内存控制块 pp 对应的物理页面,并将页表项权限为设置为 perm。
struct Env *env = (struct Env *)user_data;
bcopy((void *)bin, (void *)(page2kva(p) + offset), MIN(bin_size, size));
bcopy((void * )(bin + i), (void *)page2kva(p), MIN(bin_size - i, BY2PG)); //如果是bin_size的最后一页,只要复制到bin截止的内容
void load_icode(struct Env *e, u_char *binary, u_int size)
申请的页面来初始化一个进程的栈page_insert(e->env_pgdir,p,USTACKTOP-BY2PG, perm);(注意栈的增长方向是向下的)
调用load_elf函数把二进制文件加载到内存
设置pc寄存器
我们要运行的进程的代码段预先被载入到了entry_ point为起点的内存中(也就是进程入口地址,运行进程时,CPU将自动从pc所指的位置开始执行二进制码, 因此将pc设为entry_point。e->env_tf.pc = entry_point;
u_int mkenvid(struct Env *e) //生成一个新的进程ID
u_int asid_alloc()//生成一个新的ASID
新的进程ID组成:envid:31-11位asid,10位1,0-9位进程控制块序号(第几个)
其中:asid实际为6位,进程控制块序号为e-envs
int envid2env(u_int envid, struct Env **penv, int checkperm);
//通过envid获取该进程控制块,如果checkperm,则它必须是当前进程或者当前进程的子进程
//当envid是0时,该函数返回当前进程
void env_init(void);//将链表更新,所有进程控制块放到env_free_list中(小地址在头部)
int env_setup_vm(struct Env *e)//初始化新进程地址空间。也即是初始化该进程的页目录。
int env_alloc(struct Env **e, u_int parent_id);
首先,从空闲PCB链表中取出一个空闲PCB。//LIST_FIRST
调用env_setup_vm函数初始化进程的页目录(pgdir和env_cr3)。
为该进程分配一个envid, 设置父进程id, 并且把进程的状态设为ENV_RUNNABLE 。
设置相应的寄存器的值。//cp0_status = 0x10001004;regs[29]=USTACKTOP;(栈寄存器指向用户栈)
该PCB从空闲链表中移除。//LIST_REMOVE
void env_free(struct Env *);//释放该进程
void env_create_priority(u_char *binary, int size, int priority);
void env_create(u_char *binary, int size);
void env_destroy(struct Env *e);
void env_run(struct Env *e);
保存当前进程的上下文信息,设置当前进程上下文中的 pc 为epc。
//如果当前有进程,则保存当前进程的env_tf、env_tf.pc
把当前进程curenv切换为需要运行的进程。//curenv = e;
调用 lcontext 函数,设置全局变量mCONTEXT为当前进程页目录地址,这个值将在TLB重填时用到。context((u_int)curenv->env_pgdir);
调用env_pop_tf函数,恢复现场、异常返回。
env.h
宏定义:
LOG2NENV 10
NENV (1<<LOG2NENV)进程控制块个数
ENV_FREE 0
ENV_RUNNABLE 1处于RUNNABLE 状态的进程可以是正在运行的,也可能不在运行中。
ENV_NOT_RUNNABLE 2
ENVX(envid) ((envid) & (NENV - 1))获取0-9位,即第多少个进程块
GET_ENV_ASID(envid) (((envid)>> 11)<<6) //获取改进程块的ASID
#define ENV_CREATE_PRIORITY(x, y) \
{ \
extern u_char binary_##x##_start[];\
extern u_int binary_##x##_size; \
env_create_priority(binary_##x##_start, \
(u_int)binary_##x##_size, y); \
}
#define ENV_CREATE(x) \
{ \
extern u_char binary_##x##_start[];\
extern u_int binary_##x##_size; \
env_create(binary_##x##_start, \
(u_int)binary_##x##_size); \
}
全局变量:
struct Env {
struct Trapframe env_tf; // Saved registers
LIST_ENTRY(Env) env_link; // Free LIST_ENTRY
u_int env_id; // Unique environment identifier
u_int env_parent_id; // env_id of this env's parent
u_int env_status; // Status of the environment
Pde *env_pgdir; // Kernel virtual address of page dir
u_int env_cr3;
LIST_ENTRY(Env) env_sched_link;
u_int env_pri;
};
struct Env *curenv = NULL; // 当前进程控制块
struct Env *envs; //进程链表头部
struct Env_list env_sched_list[2];//可运行进程链表
hit:
envs指针为进程控制块头部,envs[i]第i个进程控制块,若struct env*p;如果是p = &envs[i],则代表p指针指向了该控制块,如果是*p = envs[i],则代表p指针指向的地址的内容为该控制块(指向的地址不知道)
void ttt(struct a** p,struct a z) {
*p = &z;}
struct a * temp;
ttt(&temp,p[1]);
参数为双指针时,表示该双指针指向了指针的地址。*p表示该指针,且与temp一样,当给*p赋值的时候,就是给temp赋值