MIT 6s081 blog 1.xv6内存管理

2024-01-15 16:52
文章标签 内存 管理 blog mit xv6 6s081

本文主要是介绍MIT 6s081 blog 1.xv6内存管理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

xv6内存管理部分

xv6内存布局

内核地址空间

如xv6指导书中图3.3:从0x80000000开始的地址为内核地址空间,CLINT、PLIC、uart0、virtio disk等为I/O设备(内存映射I/O),可以看到xv6虚拟地址到物理地址的映射,大部分是相等的关系。
在这里插入图片描述

在kernel/memlayout.h中对内存分布进行了宏定义

// kernel/memlayout.h
// the kernel expects there to be RAM
// for use by the kernel and user pages
// from physical address 0x80000000 to PHYSTOP.
#define KERNBASE 0x80000000L
#define PHYSTOP (KERNBASE + 128*1024*1024)// map the trampoline page to the highest address,
// in both user and kernel space.
#define TRAMPOLINE (MAXVA - PGSIZE)// map kernel stacks beneath the trampoline,
// each surrounded by invalid guard pages.
#define KSTACK(p) (TRAMPOLINE - ((p)+1)* 2*PGSIZE)

进程地址空间

在这里插入图片描述

在kernel/memlayout.h中对内存分布进行了宏定义

// User memory layout.
// Address zero first:
//   text
//   original data and bss
//   fixed-size stack
//   expandable heap
//   ...
//   TRAPFRAME (p->trapframe, used by the trampoline)
//   TRAMPOLINE (the same page as in the kernel)
#define TRAPFRAME (TRAMPOLINE - PGSIZE)

物理内存分配

xv6对于物理内存的管理使用的是空闲链表,以页为单位进行管理,每次分配/释放都是一页。代码如下:

空闲链表

// kernel/kalloc.cstruct run {struct run *next;
};struct {struct spinlock lock;struct run *freelist;
} kmem;

使用两个结构体维护一个单向链表,kmem作为全局变量,freelist为空闲链表的头节点,每个链表节点为struct run,包含指向下一个链表节点的结构体指针,并为全局变量kmem维护一把锁用于race condition(竞态条件)

申请物理内存

// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
void *
kalloc(void)
{struct run *r;acquire(&kmem.lock);r = kmem.freelist;if(r)kmem.freelist = r->next;release(&kmem.lock);if(r)memset((char*)r, 5, PGSIZE); // fill with junkreturn (void*)r;
}

首先获取kmem的锁,r = freelist,将freelist移到freelist->next,此时r就是申请到的物理内存。(也就是获取空闲链表的头节点作为申请到的物理内存,然后空闲链表的头节点往后移一位)

释放物理内存

void
kfree(void *pa)
{struct run *r;if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)panic("kfree");// Fill with junk to catch dangling refs.memset(pa, 1, PGSIZE);r = (struct run*)pa;acquire(&kmem.lock);r->next = kmem.freelist;kmem.freelist = r;release(&kmem.lock);
}void
freerange(void *pa_start, void *pa_end)
{char *p;p = (char*)PGROUNDUP((uint64)pa_start);for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)kfree(p);
}

将传入的要释放的物理地址pa强转为链表节点,将该节点的next节点指向空闲链表头结点,然后将该节点作为空闲链表头节点(也就是链表的头插法)

freerange为封装的kfree函数,用于释放pa_start到pa_end的物理空间

内核页表

三级页表遍历过程

xv6使用三级页表来节约因为存储页表而耗费的大量内存页

在这里插入图片描述

物理内存地址是56bit,其中44bit是物理page号(PPN,Physical Page Number),剩下12bit是offset完全继承自虚拟内存地址。

每个页表项:63-54为预留位,53-10为物理页号,9-0为标志位

xv6使用的是三级页表,首先从satp寄存器中获取最高级页目录的地址,从L2(38:30位)得到索引,根据索引在最高级页目录中找到物理页号(也就是中间级页目录的地址),根据L1(29:21位)得到索引,根据索引在中间级页目录中找到物理页号(也就是最低级页目录的地址),根据L0(20:12位)得到索引,根据索引在最低级页目录中找到物理页号,将最低级的物理页号和offset(11:0位)合并得到最终的56位物理地址。(三级页表的查询方式和每个页表项的结构如上图所示)。

初始化

在kernel/vm.c中声明了内核页表,每个pagetable_t(通过kalloc分配一页空间)包含512个页表项(4096 * 8 / 64 = 512)

typedef uint64 pte_t;
typedef uint64 *pagetable_t; // 512 PTEs
pagetable_t kernel_pagetable; // 内核页表

在main.c中调用kvminit函数创建内核页表,kvminit调用了kvmmake函数,在kvmmake函数中为一些启动必备的区域添加映射到内核页表中,如UART0、VIRTIO0、PLIC、etext、TRAMPOLINE等,

// kernel/vm.c
pagetable_t kpgtbl;kpgtbl = (pagetable_t) kalloc();
memset(kpgtbl, 0, PGSIZE);// uart registers
kvmmap(kpgtbl, UART0, UART0, PGSIZE, PTE_R | PTE_W);// virtio mmio disk interface
kvmmap(kpgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);
...

以及为每个进程的内核栈建立映射(在每一个进程的生命周期中,必然会通过到系统调用陷入内核。在执行系统调用陷入内核之后,这些内核代码所使用的栈并不是原先进程用户空间中的栈,而是一个单独内核空间的栈,这个称作进程内核栈。)

// Allocate a page for each process's kernel stack.
// Map it high in memory, followed by an invalid
// guard page.
void proc_mapstacks(pagetable_t kpgtbl) {struct proc *p;for(p = proc; p < &proc[NPROC]; p++) {char *pa = kalloc();if(pa == 0)panic("kalloc");uint64 va = KSTACK((int) (p - proc));kvmmap(kpgtbl, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);}
}

添加映射

添加映射使用的函数是kvmmap,将添加va->pa、长度为sz、标志位为perm的映射(PTE)

kvmmap->mappages->walk

// kvmmap调用了mappages
void kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm)
{if(mappages(kpgtbl, va, sz, pa, perm) != 0)panic("kvmmap");
}// 向pagetable页表中添加长度为sz,va->pa,标志为perm的映射
int mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
{uint64 a, last;pte_t *pte;a = PGROUNDDOWN(va);last = PGROUNDDOWN(va + size - 1);for(;;){if((pte = walk(pagetable, a, 1)) == 0) //  mappages使用walk找到最低一级的pte,alloc = 1return -1;if(*pte & PTE_V)panic("remap");*pte = PA2PTE(pa) | perm | PTE_V; // 修改pteif(a == last)break;a += PGSIZE;pa += PGSIZE;}return 0;
}// 根据va返回最低一级的页表项,alloc选项用于当pte无效时,是否创建pte,本质上是软件模拟硬件MMU的过程
pte_t * walk(pagetable_t pagetable, uint64 va, int alloc)
{if(va >= MAXVA)panic("walk");for(int level = 2; level > 0; level--) {pte_t *pte = &pagetable[PX(level, va)];if(*pte & PTE_V) {pagetable = (pagetable_t)PTE2PA(*pte);} else {if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)return 0;memset(pagetable, 0, PGSIZE);*pte = PA2PTE(pagetable) | PTE_V;}}return &pagetable[PX(0, va)];
}

设置/切换页表

  • 内核初始化时/进入内核态时切换为内核页表
  • 进入用户态/进程调度后,切换为对应进程的页表

kvminithart函数,这个函数首先设置了SATP寄存器,kernel_pagetable变量来自于kvminit第一行。所以这里实际上是内核告诉MMU来使用刚刚设置好的page table(在此之前,内核在kernel/start.c中使用w_satp(0);来关闭分页机制)。

// Switch h/w page table register to the kernel's page table,
// and enable paging.
void
kvminithart()
{w_satp(MAKE_SATP(kernel_pagetable)); // 设置satp寄存器,将对应位进行设置sfence_vma(); // 刷新快表
}

进程页表

在进程的结构体中有一个字段为pagetable_t pagetable(User page table),为进程的页表,在用户态时,使用的页表是独立的,符合了操作系统的隔离性,不同进程的虚拟地址所指向的物理地址空间是不同的,进程A无法访问到进程B的内存空间。

// Per-process state
struct proc {struct spinlock lock;// p->lock must be held when using these:enum procstate state;        // Process statestruct proc *parent;         // Parent processvoid *chan;                  // If non-zero, sleeping on chanint killed;                  // If non-zero, have been killedint xstate;                  // Exit status to be returned to parent's waitint pid;                     // Process ID// these are private to the process, so p->lock need not be held.uint64 kstack;               // Virtual address of kernel stackuint64 sz;                   // Size of process memory (bytes)pagetable_t pagetable;       // User page tablestruct trapframe *trapframe; // data page for trampoline.Sstruct context context;      // swtch() here to run processstruct file *ofile[NOFILE];  // Open filesstruct inode *cwd;           // Current directorychar name[16];               // Process name (debugging)
};

riscv.h一些声明

这些宏定义在页表操作中被使用到,如PA2PTE将物理地址转换为页表项,PGROUNDUP表示对sz / PGSIZE的结果进行上取整。

#define PGSIZE 4096 // bytes per page
#define PGSHIFT 12  // bits of offset within a page#define PGROUNDUP(sz)  (((sz)+PGSIZE-1) & ~(PGSIZE-1))
#define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1))#define PTE_V (1L << 0) // valid
#define PTE_R (1L << 1)
#define PTE_W (1L << 2)
#define PTE_X (1L << 3)
#define PTE_U (1L << 4) // 1 -> user can access// shift a physical address to the right place for a PTE.
#define PA2PTE(pa) ((((uint64)pa) >> 12) << 10)#define PTE2PA(pte) (((pte) >> 10) << 12)#define PTE_FLAGS(pte) ((pte) & 0x3FF)// extract the three 9-bit page table indices from a virtual address.
#define PXMASK          0x1FF // 9 bits
#define PXSHIFT(level)  (PGSHIFT+(9*(level)))
#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK)// one beyond the highest possible virtual address.
// MAXVA is actually one bit less than the max allowed by
// Sv39, to avoid having to sign-extend virtual addresses
// that have the high bit set.
#define MAXVA (1L << (9 + 9 + 9 + 12 - 1))typedef uint64 pte_t;
typedef uint64 *pagetable_t; // 512 PTEs

vm.c主要函数

ptb:pagetable; va : 虚拟地址; pa:物理地址; pte:页表项

与内核页表相关:

  • kvminit:创建内核页表的一些固定映射

  • kvminithart:使用内核页表

  • kvmmap:向内核页表中添加va->pa,flag为perm|PTE_V的映射

  • kvmpa:根据内核页表将va转换成pa

与进程页表相关:

  • uvmunmap:在ptb中移除va开始的npages个页的叶子级别映射(最低一级页表的映射),并根据do_free参数选择是否释放对应的物理空间(与freewalk配合使用来释放页表)
  • uvmcreate:创建一个空的页表
  • uvminit:用于加载第一个进程的initcode到va = 0处
  • uvmalloc:在进程页表中创建新的PTE和分配对应的物理内存,用于将进程空间从oldsz增大到newsz
  • uvmdealloc:在进程页表中修改进程空间,从oldsz到newsz(用于回收用户页表中的页面)
  • uvmfree:把进程页表空间释放,把最低一级PTE的条目清空,并删除对应的物理页(uvmunmap)。并将最高级和中间级的PTE条目也清空,并清空物理页(freewalk)
  • uvmcopy:将old ptb的sz个页空间拷贝到new ptb(用于fork系统调用,将父进程的页表映射复制到子进程的页表映射,虚拟地址对应的物理地址不同,但内容相同)
  • uvmclear:将用户页表的最低一级PTE条目的PTE_U标志去除,用于exec函数设置守护页

kvm开头和uvm开头的函数都在内核态中运行,使用的都是内核页表,但操作的对象不同

工具方法:

  • walk:根据ptb和va,返回对应的最低一级PTE

  • walkaddr:根据ptb和va,找到对应的pte,并使用PTE2PA,返回pa

  • freewalk:递归释放指定页表的所有项,叶子节点在调用之前必须已经被清空,否则会陷入freewalk leaf

  • mappages:根据给定的ptb, va, pa, size, perm在三级页表中创建映射

  • copyout:将内核空间的n个字节拷贝至用户空间

  • copyin:将用户空间的n个字节拷贝至内核空间

  • copyinstr:将用户空间的max个字符串(必须以‘\0’结尾)拷贝至内核空间

copyin

int copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{uint64 n, va0, pa0;while(len > 0){va0 = PGROUNDDOWN(srcva);pa0 = walkaddr(pagetable, va0);if(pa0 == 0)return -1;n = PGSIZE - (srcva - va0);if(n > len)n = len;memmove(dst, (void *)(pa0 + (srcva - va0)), n);len -= n;dst += n;srcva = va0 + PGSIZE;}return 0;
}

从给定进程页表的虚拟地址srcva中拷贝len字节到内核地址dst中,由于此时运行在内核态中,因此char* dst会通过硬件MMU自动翻译成对应的物理地址(使用内核页表),而scrva对应的物理地址要使用walkaddr(软件模拟MMU的方法)从进程页表中找到对应的物理地址进行拷贝

copyout

int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{uint64 n, va0, pa0;while(len > 0){va0 = PGROUNDDOWN(dstva);pa0 = walkaddr(pagetable, va0);if(pa0 == 0)return -1;n = PGSIZE - (dstva - va0);if(n > len)n = len;memmove((void *)(pa0 + (dstva - va0)), src, n);len -= n;src += n;dstva = va0 + PGSIZE;}return 0;
}

与copyin类似,只不过拷贝方向相反。

这篇关于MIT 6s081 blog 1.xv6内存管理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/609582

相关文章

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

安全管理体系化的智慧油站开源了。

AI视频监控平台简介 AI视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。用户只需在界面上进行简单的操作,就可以实现全视频的接入及布控。摄像头管理模块用于多种终端设备、智能设备的接入及管理。平台支持包括摄像头等终端感知设备接入,为整个平台提

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Sentinel 高可用流量管理框架

Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。 Sentinel 具有以下特性: 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

JVM 常见异常及内存诊断

栈内存溢出 栈内存大小设置:-Xss size 默认除了window以外的所有操作系统默认情况大小为 1MB,window 的默认大小依赖于虚拟机内存。 栈帧过多导致栈内存溢出 下述示例代码,由于递归深度没有限制且没有设置出口,每次方法的调用都会产生一个栈帧导致了创建的栈帧过多,而导致内存溢出(StackOverflowError)。 示例代码: 运行结果: 栈帧过大导致栈内存

理解java虚拟机内存收集

学习《深入理解Java虚拟机》时个人的理解笔记 1、为什么要去了解垃圾收集和内存回收技术? 当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术实施必要的监控和调节。 2、“哲学三问”内存收集 what?when?how? 那些内存需要回收?什么时候回收?如何回收? 这是一个整体的问题,确定了什么状态的内存可以

NGINX轻松管理10万长连接 --- 基于2GB内存的CentOS 6.5 x86-64

转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=190176&id=4234854 一 前言 当管理大量连接时,特别是只有少量活跃连接,NGINX有比较好的CPU和RAM利用率,如今是多终端保持在线的时代,更能让NGINX发挥这个优点。本文做一个简单测试,NGINX在一个普通PC虚拟机上维护100k的HTTP