Exercise1 源代码阅读

1.内存管理部分: kalloc.c vm.c 以及相关其他文件代码

  • kalloc.c:char * kalloc(void)负责在需要的时候为用户空间、内核栈、页表页以及缓冲区分配物理内存,将物理地址转为虚拟地址返回,物理页大小为4k。void kfree(char * v)接收一个虚拟地址,找对对应的物理地址进行释放。xv6使用空闲内存的前部分作为指针域来指向下一页空闲内存,物理内存管理是以页(4K)为单位进行分配的。物理内存空间上空闲的每一页,都有一个指针域(虚拟地址)指向下一个空闲页,最后一个空闲页为NULL ,通过这种方式,kmem只需要保存着虚拟地址空间上的freelist地址即可;
// kalloc.c
// Physical memory allocator, intended to allocate
// memory for user processes, kernel stacks, page table pages,
// and pipe buffers. Allocates 4096-byte pages. void freerange(void *vstart, void *vend);
extern char end[]; // first address after kernel loaded from ELF file struct run {
struct run *next;
}; struct {
struct spinlock lock;
int use_lock;
struct run *freelist;
} kmem;
  • xv6让每个进程都有独立的页表结构,在切换进程时总是需要切换页表,switchkvm设置cr3寄存器的值为kpgdir首地址,kpgdir仅仅在scheduler内核线程中使用。页表和内核栈都是每个进程独有的,xv6使用结构体proc将它们统一起来,在进程切换的时候,他们也往往随着进程切换而切换,内核中模拟出了一个内核线程,它独占内核栈和内核页表kpgdir,它是所有进程调度的基础。switchuvm通过传入的proc结构负责切换相关的进程独有的数据结构,其中包括TSS相关的操作,然后将进程特有的页表载入cr3寄存器,完成设置进程相关的虚拟地址空间环境;
// vm.c

……

// Switch h/w page table register to the kernel-only page table,
// for when no process is running.
void
switchkvm(void)
{
lcr3(v2p(kpgdir)); // switch to the kernel page table
} // Switch TSS and h/w page table to correspond to process p.
void
switchuvm(struct proc *p)
{
pushcli();
cpu->gdt[SEG_TSS] = SEG16(STS_T32A, &cpu->ts, sizeof(cpu->ts)-1, 0);
cpu->gdt[SEG_TSS].s = 0;
cpu->ts.ss0 = SEG_KDATA << 3;
cpu->ts.esp0 = (uint)proc->kstack + KSTACKSIZE;
ltr(SEG_TSS << 3);
if(p->pgdir == 0)
panic("switchuvm: no pgdir");
lcr3(v2p(p->pgdir)); // switch to new address space
popcli();
}
  • 进程的页表在使用前往往需要初始化,其中必须包含内核代码的映射,这样进程在进入内核时便不需要再次切换页表,进程使用虚拟地址空间的低地址部分,高地址部分留给内核,主要接口:

    • pde_t * setupkvm(void)通过kalloc分配一页内存作为页目录,然后将按照kmap数据结构映射内核虚拟地址空间到物理地址空间,期间调用了工具函数mappages;
    • int allocuvm(pde_t * pgdir, uint oldsz, uint newsz)在设置页表的同时分配虚拟地址oldsz到newsz的以页为单位的内存;
    • int deallocuvm(pde_t * pgdir, uint oldsz, uint newsz)则将newsz到oldsz对应的虚拟地址空间内存置为空闲;
    • int loaduvm(pde_t * pgdir, char * addr, struct inode * ip, uint offset, uint sz)将文件系统上的i节点内容读取载入到相应的地址上,通过allocuvm接口为用户进程分配内存和设置页表,然后调用loaduvm接口将文件系统上的程序载入到内存,便能够为exec系统调用提供接口,为用户进程的正式运行做准备;
    • 当进程销毁需要回收内存时,调用void freevm(pde_t * pgdir)清除用户进程相关的内存环境,其首先调用将0到KERNBASE的虚拟地址空间回收,然后销毁整个进程的页表;
    • pde_t * copyuvm(pde_t * pgdir, uint sz)负责复制一个新的页表并分配新的内存,新的内存布局和旧的完全一样,xv6使用这个函数作为fork()底层实现。

Exercise2 带着问题阅读

1.XV6初始化之后到执行main.c时,内存布局是怎样的(其中已有哪些内容)?

  • 内核代码存在于物理地址低地址的0x100000处,页表为main.c文件中的entrypgdir数组,其中虚拟地址低4M映射物理地址低4M,虚拟地址 [KERNBASE, KERNBASE+4MB) 映射到 物理地址[0, 4MB);

  • 紧接着调用kinit1初始化内核末尾到物理内存4M的物理内存空间为未使用,然后调用kinit2初始化剩余内核空间到PHYSTOP为未使用。kinit1调用前使用的还是最初的页表(也就是是上面的内存布局),所以只能初始化4M,同时由于后期再构建新页表时也要使用页表转换机制来找到实际存放页表的物理内存空间,这就构成了自举问题,xv6通过在main函数最开始处释放内核末尾到4Mb的空间来分配页表,由于在最开始时多核CPU还未启动,所以没有设置锁机制。kinit2在内核构建了新页表后,能够完全访问内核的虚拟地址空间,所以在这里初始化所有物理内存,并开始了锁机制保护空闲内存链表;

  • 然后main函数通过调用void kvmalloc(void)函数来实现内核新页表的初始化;

  • 最后内存布局和地址空间如下:内核末尾物理地址到物理地址PHYSTOP的内存空间未使用,虚拟地址空间KERNBASE以上部分映射到物理内存低地址相应位置。

// kalloc.c
// Initialization happens in two phases.
// 1. main() calls kinit1() while still using entrypgdir to place just
// the pages mapped by entrypgdir on free list.
// 2. main() calls kinit2() with the rest of the physical pages
// after installing a full page table that maps them on all cores.
void
kinit1(void *vstart, void *vend)
{
initlock(&kmem.lock, "kmem");
kmem.use_lock = 0;
freerange(vstart, vend);
} void
kinit2(void *vstart, void *vend)
{
freerange(vstart, vend);
kmem.use_lock = 1;
} // kmap.c
……
// This table defines the kernel's mappings, which are present in
// every process's page table.
static struct kmap {
void *virt;
uint phys_start;
uint phys_end;
int perm;
} kmap[] = {
{ (void*)KERNBASE, 0, EXTMEM, PTE_W}, // I/O space
{ (void*)KERNLINK, V2P(KERNLINK), V2P(data), 0}, // kern text+rodata
{ (void*)data, V2P(data), PHYSTOP, PTE_W}, // kern data+memory
{ (void*)DEVSPACE, DEVSPACE, 0, PTE_W}, // more devices
};
……

2.XV6 的动态内存管理是如何完成的? 有一个kmem(链表),用于管理可分配的物理内存页。(vend=0x00400000,也就是可分配的内存页最大为4Mb)

详见“Exercise 1 源代码阅读”部分,已经作出完整解答。

3.XV6的虚拟内存是如何初始化的? 画出XV6的虚拟内存布局图,请说出每一部分对应的内容是什么。见memlayout.h和vm.c的kmap上的注释?

  • main函数通过调用void kinit1(void * vstart, void * vend), void kinit2(void * vstart, void * vend), void kvmalloc(void)函数来实现内核新页表的初始化。虚拟地址与物理地址的转换接口:
// memlayout.h
// Memory layout #define EXTMEM 0x100000 // Start of extended memory
#define PHYSTOP 0xE000000 // Top physical memory
#define DEVSPACE 0xFE000000 // Other devices are at high addresses // Key addresses for address space layout (see kmap in vm.c for layout)
#define KERNBASE 0x80000000 // First kernel virtual address
#define KERNLINK (KERNBASE+EXTMEM) // Address where kernel is linked #ifndef __ASSEMBLER__ static inline uint v2p(void *a) { return ((uint) (a)) - KERNBASE; }
static inline void *p2v(uint a) { return (void *) ((a) + KERNBASE); } #endif #define V2P(a) (((uint) (a)) - KERNBASE)
#define P2V(a) (((void *) (a)) + KERNBASE) #define V2P_WO(x) ((x) - KERNBASE) // same as V2P, but without casts
#define P2V_WO(x) ((x) + KERNBASE) // same as V2P, but without casts
  • 内存布局:

4.关于XV6 的内存页式管理。发生中断时,用哪个页表? 一个内存页是多大? 页目录有多少项? 页表有多少项? 最大支持多大的内存? 画出从虚拟地址到物理地址的转换图。在XV6中,是如何将虚拟地址与物理地址映射的(调用了哪些函数实现了哪些功能)?

  • 发生中断时,将换入cpu的进程的页表首地址存入cr3寄存器;一个内存页为4k;XV6页表采用的二级目录,一级目录有\(2^{10}\)条,二级目录有\(2^{10} * 2^{10}\)条;页表项为\(2^2\)Bytes,故页表有\(2^{12} / 2^2 = 2^{10} = 1024\)项;最大支持4G内存;

  • 物理内存页的申请与释放,虚拟地址与物理地址如何映射等在“Exercise 1 源代码阅读”都已经详述了,在此主要说下mappages接口,虚拟地址 * va与物理地址 * pa映射size个字节,同时赋予该页的权限perm,如下:
// vm.c
……
// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa. va and size might not
// be page-aligned.
static int
mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm)
{
char *a, *last;
pte_t *pte; a = (char*)PGROUNDDOWN((uint)va);
last = (char*)PGROUNDDOWN(((uint)va) + size - 1);
for(;;){
if((pte = walkpgdir(pgdir, a, 1)) == 0)
return -1;
if(*pte & PTE_P)
panic("remap");
*pte = pa | perm | PTE_P;
if(a == last)
break;
a += PGSIZE;
pa += PGSIZE;
}
return 0;
}
……

参考文献

[1] xv6虚拟内存-博客园

[2] xv6 virtual memory-hexo

[3] xv6内存管理-简书

[4] xv6内存管理-CSDN

XV6源代码阅读-虚拟内存管理的更多相关文章

  1. XV6源代码阅读-文件系统

    Exercise1 源代码阅读 文件系统部分 buf.h fcntl.h stat.h fs.h file.h ide.c bio.c log.c fs.c file.c sysfile.c exec ...

  2. XV6源代码阅读-中断与系统调用

    Exercise1 源代码阅读 1.启动部分: bootasm.S bootmain.c 和xv6初始化模块:main.c bootasm.S 由16位和32位汇编混合编写成的XV6引导加载器.boo ...

  3. XV6源代码阅读-进程线程

    Exercise1 源代码阅读 1.基本头文件:types.h param.h memlayout.h defs.h x86.h asm.h mmu.h elf.h types.h:仅仅是定义uint ...

  4. XV6源代码阅读-同步机制

    Exercise1 源代码阅读 锁部分:spinlock.h/spinlock.c以及相关其他文件代码 // Mutual exclusion lock. struct spinlock { uint ...

  5. 【转】Tomcat总体结构(Tomcat源代码阅读系列之二)

    本文是Tomcat源代码阅读系列的第二篇文章,我们在本系列的第一篇文章:在IntelliJ IDEA 和 Eclipse运行tomcat 7源代码一文中介绍了如何在intelliJ IDEA 和 Ec ...

  6. 淘宝数据库OceanBase SQL编译器部分 源代码阅读--Schema模式

    淘宝数据库OceanBase SQL编译器部分 源代码阅读--Schema模式 什么是Database,什么是Schema,什么是Table,什么是列,什么是行,什么是User?我们能够能够把Data ...

  7. CI框架源代码阅读笔记3 全局函数Common.php

    从本篇開始.将深入CI框架的内部.一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说.全局函数具有最高的载入优先权.因此大多数的框架中BootStrap ...

  8. 【转】Tomcat源代码阅读系列

    在IntelliJ IDEA 和 Eclipse运行tomcat 7源代码(Tomcat源代码阅读系列之一) Tomcat总体结构(Tomcat源代码阅读系列之二) Tomcat启动过程(Tomcat ...

  9. 非常好!!!Linux源代码阅读——内核引导【转】

    Linux源代码阅读——内核引导 转自:http://home.ustc.edu.cn/~boj/courses/linux_kernel/1_boot.html 目录 Linux 引导过程综述 BI ...

随机推荐

  1. 计算机网络 - TCP/IP模型

    图片来自网上资料

  2. 操作系统OS,Python - 多进程(multiprocessing)、多线程(multithreading)

    多进程(multiprocessing) 参考: https://docs.python.org/3.6/library/multiprocessing.html 1. 多进程概念 multiproc ...

  3. Lesson 15 Secrecy in industry

    Why is secrecy particularly important in the chemical industries? Two factors weigh heavily against ...

  4. Unable to instantiate Action, xxxAction, defined for 'xxx' in namespace '/'xxxAction解决方案

    出现这个问题的原因主要有两个 1.如果项目没有使用Spring,则struts.xml配置文件中,这个action的class属性的路径没有写完整,应该是包名.类名 2.如果项目使用了Spring,那 ...

  5. 十二 Spring的AOP开发入门,整合Junit单元测试(AspectJ的XML方式)

    创建web项目,引入jar包 引入Spring配置文件

  6. Windows驱动开发-IRP超时处理

    IRP被送到底层驱动程序以后,由于硬件设备的问题,IRP不能得到及时处理,甚至有可能永远不会被处理,这时候需要对IRP超时情况进行处理,一旦在规定时间内,IRP没有被处理,操作系统就会进入到IRP的处 ...

  7. FCN训练注意事项

    1.如果是类别受两类,需要把标签图二值化为0,1

  8. 支持USB4的Linux 5.6,有望在今年4月份推出

    导读 根据外媒Phoronix的报道,Linux 5.6将支持USB4,. USB4的规范在去年9月份发布,基于雷电3,并与之向后兼容.英特尔的开源部门在去年10月份添加了USB4的初始补丁. 据报道 ...

  9. JS html页面

    js窗口置顶 if (window != top) top.location.href = location.href; js打开新窗口 js window.open()弹出窗口参数说明及居中设置 f ...

  10. 基于Qt 5.9.8,编译Qtxlsx

    1.源码下载地址:https://github.com/dbzhang800/QtXlsxWriter 2.下载并安装编译时需要的工具 Perl:https://www.perl.org/get.ht ...