XV6学习笔记(2) :内存管理

在学习笔记1中,完成了对于pc启动和加载的过程。目前已经可以开始在c语言代码中运行了,而当前已经开启了分页模式,不过是两个4mb的大的内存页,而没有开启小的内存页。接下来就可以从main.c的init函数开始

这里会和JOS做一个对比

首先看一下在执行main.c之前的物理内存分布

0x0000-0x7c00     引导程序的栈
0x7c00-0x7d00 引导程序的代码(512字节)
0x10000-0x11000 内核ELF文件头(4096字节)
0xA0000-0x100000 设备区
0x100000-0x400000 Xv6操作系统(未用满)

1. Kinit1函数

1.1 xv6中的kinit1函数

int
main(void)
{
kinit1(end, P2V(4*1024*1024)); // phys page allocator
kvmalloc(); // kernel page table
//....
}

这是main函数的开始。所以我们先从kinit1开始

这里的end地址就是kernel从0x80100000开始。然后是内核的代码段 + 只读数据段+ stab段+ stabstr + 数据段 + .bss段这些之后的起始地址。如下图所示。

void
kinit1(void *vstart, void *vend)
{
initlock(&kmem.lock, "kmem");
kmem.use_lock = 0;
freerange(vstart, vend);
}
  1. 这里的vstart就是end的地址而vend是KERNBASE + 4MB = 0x80400000
  2. 这里就是把[vstart, 0x80400000]的内存按页(4kb大小)进行free
  3. kree这里会把他插入到freelist中
void
freerange(void *vstart, void *vend)
{
char *p;
p = (char*)PGROUNDUP((uint)vstart);
for(; p + PGSIZE <= (char*)vend; p += PGSIZE)
kfree(p);
}
//PAGEBREAK: 21
// Free the page of physical memory pointed at by v,
// which normally should have been returned by a
// call to kalloc(). (The exception is when
// initializing the allocator; see kinit above.)
void
kfree(char *v)
{
struct run *r; if((uint)v % PGSIZE || v < end || V2P(v) >= PHYSTOP)
panic("kfree"); // Fill with junk to catch dangling refs.
memset(v, 1, PGSIZE); if(kmem.use_lock)
acquire(&kmem.lock);
r = (struct run*)v;
r->next = kmem.freelist;
kmem.freelist = r;
if(kmem.use_lock)
release(&kmem.lock);
}

好了这里就可以完成整个free操作了

1.2 jos的boot_alloc函数

和上面的是非常类似的

  1. 当第一次执行的时候nextfree是空这里会进行第一次分配。这里的起始地址也是end的地址
  2. 然后返回这一段分配的地址,并更新nextfree
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result = NULL; // Initialize nextfree if this is the first time.
// 'end' is a magic symbol automatically generated by the linker,
// which points to the end of the kernel's bss segment:
// the first virtual address that the linker did *not* assign
// to any kernel code or global variables.
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end + 1, PGSIZE);
} // Allocate a chunk large enough to hold 'n' bytes, then update
// nextfree. Make sure nextfree is kept aligned
// to a multiple of PGSIZE.
//
// LAB 2: Your code here.
if (n > 0) {
result = nextfree;
nextfree = ROUNDUP(nextfree + n, PGSIZE);
} else if (n == 0) {
result = ROUNDUP(nextfree, PGSIZE);
} else {
panic("boot_alloc(n): n < 0\n");
} cprintf("boot_alloc(): nextfree=%08x\n", nextfree); if ((uintptr_t) nextfree >= KERNBASE + PTSIZE) {
panic("boot_alloc(): out of memory\n");
} return result;
}

2. kvmalloc

void
kvmalloc(void)
{
kpgdir = setupkvm();
switchkvm();
}

这里我们先看一下setupkvm

// Set up kernel part of a page table.
pde_t*
setupkvm(void)
{
pde_t *pgdir;
struct kmap *k; if((pgdir = (pde_t*)kalloc()) == 0) // 分配pgdir
return 0;
memset(pgdir, 0, PGSIZE);
if (P2V(PHYSTOP) > (void*)DEVSPACE)
panic("PHYSTOP too high");
for(k = kmap; k < &kmap[NELEM(kmap)]; k++) //遍历kmap进行映射
if(mappages(pgdir, k->virt, k->phys_end - k->phys_start,
(uint)k->phys_start, k->perm) < 0) {// 如果映射失败则 free掉
freevm(pgdir);
return 0;
}
return pgdir;

当然这里引出了很多函数

1. kalloc函数

从我们的空闲队列中获得一个指针。返回

char*
kalloc(void)
{
struct run *r; if(kmem.use_lock)
acquire(&kmem.lock);
r = kmem.freelist;
if(r)
kmem.freelist = r->next;
if(kmem.use_lock)
release(&kmem.lock);
return (char*)r;
}

2. kmap指明了内核中需要映射的区域:

// This table defines the kernel’s
// 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
};

3. mappages函数

  1. 给定虚拟地址va和物理地址pa
  2. 把[va , va + size]和 [pa, pa + size]进行映射。并且以perm位
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;
}

4.walkpgdir函数

这里关于页的操作要补充一些关于页表操作的知识

首先页表目录结构如下。4gb内存32地址的话

//
// +--------10------+-------10-------+---------12----------+
// | Page Directory | Page Table | Offset within Page |
// | Index | Index | |
// +----------------+----------------+---------------------+
// \--- PDX(va) --/ \--- PTX(va) --/ // page directory index
#define PDX(va) (((uint)(va) >> PDXSHIFT) & 0x3FF) // page table index
#define PTX(va) (((uint)(va) >> PTXSHIFT) & 0x3FF) // construct virtual address from indexes and offset
#define PGADDR(d, t, o) ((uint)((d) << PDXSHIFT | (t) << PTXSHIFT | (o)))
#define PTXSHIFT 12 // offset of PTX in a linear address
#define PDXSHIFT 22 // offset of PDX in a linear address
// Address in page table or page directory entry
#define PTE_ADDR(pte) ((uint)(pte) & ~0xFFF)
  1. 这里的操作用到了PTE_ADDR函数

    0xFFF = 1111 | 1111 | 1111
    ~0xFFF = 0000 | 0000 | 0000
    #其实就是把最后12位设置为0
  2. 这里就是获取指定虚拟地址的pte条目

static pte_t *
walkpgdir(pde_t *pgdir, const void *va, int alloc)
{
pde_t *pde;
pte_t *pgtab; pde = &pgdir[PDX(va)];
if(*pde & PTE_P){
pgtab = (pte_t*)P2V(PTE_ADDR(*pde));
} else {
if(!alloc || (pgtab = (pte_t*)kalloc()) == 0)
return 0;
// Make sure all those PTE_P bits are zero.
memset(pgtab, 0, PGSIZE);
// The permissions here are overly generous, but they can
// be further restricted by the permissions in the page table
// entries, if necessary.
*pde = V2P(pgtab) | PTE_P | PTE_W | PTE_U;
}
return &pgtab[PTX(va)];
}

整个映射完的图如下

而在xv6的二级页表条目管理如下

5. switchkvm函数

这个函数就是把kernel的pagedir传输到cr3寄存器中

kenel的pagedir由上面的操作获得

// 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
}

3. kinit2函数

在main函数进入这个函数之前还有好多别的函数。但是这个进入第一个用户进程之前的最后一个函数,

kinit2()将[0x400000, 0xE00000]范围内的物理地址纳入到内存页管理之中。至此,Xv6的内存页管理系统和内核页表已经全部建立完毕。需要注意的是,这个内核页表(kpgdir变量)只会在调度器运行时被使用。对于每一个用户进程,都会拥有自己独自的完整页表,其中也包含了一份一模一样的内核页表。

void
kinit2(void *vstart, void *vend)
{
freerange(vstart, vend);
kmem.use_lock = 1;
}

此时的虚拟内存和物理内存的映射关系如下

虚拟地址 映射到物理地址 内容
[0x80000000, 0x80100000] [0, 0x100000] I/O设备
[0x80100000, 0x80000000+data] [0x100000, data] 内核代码和只读数据
[0x80000000+data, 0x80E00000] [data, 0xE00000] 内核数据+可用物理内存
[0xFE000000, 0] [0xFE000000, 0] 其他通过内存映射的I/O设备

参考1

参考2

XV6学习笔记(2) :内存管理的更多相关文章

  1. linux kernel学习笔记-5内存管理_转

    void * kmalloc(size_t size, gfp_t gfp_mask); kmalloc()第一个参数是要分配的块的大小,第一个参数为分配标志,用于控制kmalloc()的行为. km ...

  2. COCOS学习笔记--Cocod2dx内存管理(三)-Coco2d-x内存执行原理

    通过上两篇博客.我们对Cocos引用计数和Ref类.PoolManager类以及AutoreleasePool类已有所了解,那么接下来就通过举栗子来进一步看看Coco2d-x内存执行原理是如何的. / ...

  3. 嵌入式linux学习笔记1—内存管理MMU之虚拟地址到物理地址的转化

    一.内存管理基本知识 1.S3C2440最多会用到两级页表:以段的方式进行转换时只用到一级页表,以页的方式进行转换时用到两级页表.页的大小有三种:大页(64KB),小页(4KB),极小页(1KB).条 ...

  4. Linux内核学习笔记——内核内存管理方式

    一 页 内核把物理页作为内存管理的基本单位:内存管理单元(MMU)把虚拟地址转换为物理 地址,通常以页为单位进行处理.MMU以页大小为单位来管理系统中的也表. 32位系统:页大小4KB 64位系统:页 ...

  5. ios学习笔记之内存管理

    一,内存管理类型定义      1,基本类型  任何C的类型,eg:      int,short,char,long,long long,struct,enum,union等属于基本类型或结构体   ...

  6. Cocos2D-X2.2.3学习笔记3(内存管理)

    本章节介绍例如以下: 1.C/C++内存管理机制 2.引用计数机制 3.自己主动释放机制 1.C/C++内存管理机制 相信仅仅要懂oop的都知道NEW这个keyword吧,这个通俗点说事实上就是创建对 ...

  7. arm-linux学习笔记3-linux内存管理与文件操作

    配置好linux系统之后需要vim配置一下,有助于我们的编程,主要的配置如下 在/etc/vim/vimrc文件中 "显示行号 set number "自动缩进 set autoi ...

  8. 《C#高级编程》学习笔记----c#内存管理--栈VS堆

    本文转载自Netprawn,原文英文版地址 尽管在.net framework中我们不太需要关注内存管理和垃圾回收这方面的问题,但是出于提高我们应用程序性能的目的,在我们的脑子里还是需要有这方面的意识 ...

  9. Linux System Programming 学习笔记(九) 内存管理

    1. 进程地址空间 Linux中,进程并不是直接操作物理内存地址,而是每个进程关联一个虚拟地址空间 内存页是memory management unit (MMU) 可以管理的最小地址单元 机器的体系 ...

随机推荐

  1. base64文件解码

    $str = str_replace(' ', '+', $str); //替换空字符串为+$str = str_replace('\n', '',$str); //置空换行符$str = str_r ...

  2. 剖析虚幻渲染体系(06)- UE5特辑Part 2(Lumen和其它)

    目录 6.5 Lumen 6.5.1 Lumen技术特性 6.5.1.1 表面缓存(Surface Cache) 6.5.1.2 屏幕追踪(Screen Tracing) 6.5.1.3 Lumen光 ...

  3. 从新建文件夹开始构建UtopiaEngine(2)

    本篇序言 摸了两个月的鱼,又一次拾起了自己引擎的框架,开始完善引擎系统,如果非要用现实中的什么东西比喻的话,那么我们目前实现的框架连个脚手架都不是.把这项目这样晾着显然不符合本人的风格,而且要作为毕业 ...

  4. Java+Selenium3.3.1环境搭建

    一.背景和目的 selenium从2.0开始,加入了webdriver,实际上,我们说的selenium自动化测试,大部分情况都是在使用webdriver的API.现在去Selenium官网,发现最新 ...

  5. Activiti7 回退与会签

    1.  回退(驳回) 回退的思路就是动态更改节点的流向.先遇水搭桥,最后再过河拆桥. 具体操作如下: 取得当前节点的信息 取得当前节点的上一个节点的信息 保存当前节点的流向 新建流向,由当前节点指向上 ...

  6. centos7 PostgreSQL_12.7安装-TimeScaleDB_2.01插件安装

    一.安装psql的yum源 sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64 ...

  7. Scala学习——基础入门

    基础语法 1)注意点 1)区分大小写 -  Scala是大小写敏感的,这意味着标识Hello 和 hello在Scala中会有不同的含义. 2)类名 - 对于所有的类名的第一个字母要大写. 3)方法名 ...

  8. java网络编程基础——基本网络支持

    基本网络支持 java.net包主要为网络编程提供支持. 1.InetAddress InetAddress类代表IP地址,还有两个子类:Inet4Address.Inet6Address. pack ...

  9. 40.qt quick- 高仿微信实现局域网聊天V4版本(支持gif动图表情包、消息聊天、拖动缩放窗口)

    在上章37.qt quick- 高仿微信实现局域网聊天V3版本(添加登录界面.UDP校验登录.皮肤更换.3D旋转),我们已经实现了: 添加登录界面. UDP校验登录. 皮肤更换. 3D旋转(主界面和登 ...

  10. P5591 小猪佩奇学数学

    P5591 小猪佩奇学数学 知识点 二项式定理 \[(x+1)^n=\sum_{i=0}^n\binom nix^i \] 单位根反演 \[[n\mid k]=\frac 1n\sum_{i=0}^{ ...