[自制操作系统] 连续页分配释放&kmalloc/kfree
本文将在JOS上实现连续内存、释放,提供内核的kmalloc与kfree,并在分配frambuffer的时候进行测试。
Github : https://github.com/He11oLiu/MOS
在lab2中实现的内存管理只是针对单页建立freelist,list中用链表连接起来的都是代表单页的结构体struct PageInfo。且每次释放页,都是丢在这个free_list的头。这样有几个问题:
- 不能分配大于
4k的连续空间(后面做frambuf的时候要用到) - 不断地加到空闲列表的头会使内存空间十分的混乱。不利于内存管理。
所以先要设计一种能够支持分配连续空间的机制。
一种简单的实现
最简单的想法就是保持现有的不动,freelist保证从高地址到低地址。
这要求在page_free的时候做一下手脚,放到合适的位置。
在npages_alloc的时候,找到连续的空闲的页即可。
Free
既然最主要的是在free的时候需要维护freelist按照地址的大小排列,那么就先简单将page_free重新写一下,找到合适的位置再进行插入操作。
特别要注意是否刚好应该插入到free list的头的情况:
如果刚好是最高的地址,那么就需要修改page_free_list
if (page2pa(page_free_list) < page2pa(pp))
{
cur = page_free_list;
page_free_list = pp;
pp->pp_link = cur;
return;
}
否则需要遍历来查找位置插入
cur = page_free_list;
prev = page_free_list;
while (page2pa(cur) > page2pa(pp))
{
prev = cur;
if ((cur = cur->pp_link) == NULL)
break;
}
prev->pp_link = pp;
pp->pp_link = cur;
写完简单的free之后,我们可以确保freelist的顺序问题了。
npages_alloc
再来看主要的alloc,其核心思想则是检查是否刚好有连续的空间能够分配出去,这里用consecutive来记录累计连续的页数。
通过pageInfo在pages的数组的偏移即可知道其对应的地址,如果这个偏移是连续的,则代表着一块连续的空间:
(int)(cur - pages) == (int)(prev - pages) - 1
其中cur为当前遍历到的pageInfo,而prev是上次遍历的,通过上面的表达式可以判断是否为连续。
如果找到了合适的一块空间,则需要
维护
freelist,将这块空间前的最后一页连接到分配走的后面一页。同样注意是否有存在需要换头的情况
if (pp == page_free_list)
page_free_list = cur;
else
pp_prev->pp_link = cur;初始化页属性与空间
if (alloc_flags & ALLOC_ZERO)
memset(page2kva(prev), 0, n * PGSIZE);
// clear pp link
for (i = 0; i < n; i++)
(prev + i)->pp_link = NULL;
return prev;完整的
npages_alloc见下:
struct PageInfo *npages_alloc(unsigned int n, int alloc_flags)
{
struct PageInfo *cur;
struct PageInfo *prev;
struct PageInfo *pp;
struct PageInfo *pp_prev;
unsigned int i;
unsigned int consecutive = 1;
if (page_free_list == NULL)
return NULL;
pp = page_free_list;
pp_prev = page_free_list;
prev = page_free_list;
cur = page_free_list->pp_link;
while (consecutive < n && cur != NULL)
{
if ((int)(cur - pages) != (int)(prev - pages) - 1)
{
consecutive = 1;
pp_prev = prev;
pp = cur;
}
else
consecutive++;
prev = cur;
cur = cur->pp_link;
}
if (consecutive == n)
{
// alloc flags
if (alloc_flags & ALLOC_ZERO)
memset(page2kva(prev), 0, n * PGSIZE);
// update page_free_list
if (pp == page_free_list)
page_free_list = cur;
else
pp_prev->pp_link = cur;
// clear pp link
for (i = 0; i < n; i++)
(prev + i)->pp_link = NULL;
return prev;
}
return NULL;
}
kmalloc
实现了npages_alloc,再来实现malloc就简单了,主要两个问题
- 需要分配多少页? 通过
ROUNDUP后再除以页大小即可。 - 其对应的虚拟地址是多少? 利用
page2kva转换。
完整的kmalloc如下。
void *kmalloc(size_t size)
{
struct PageInfo *pp;
int npages;
size = ROUNDUP(size, PGSIZE);
npages = size / PGSIZE;
if ((pp = npages_alloc(npages, 1)) == NULL)
return NULL;
return page2kva(pp);
}
此时已经可以测试是否基本正确。
npages_free
为了实现free,还需要实现npages_free,这个和之前实现的思路相同。
主要注意如何连接起freelist。
prev->pp_link = pp + n - 1;
pp->pp_link = cur;
for (i = 1; i < n; i++)
(pp + i)->pp_link = pp + i - 1;
其中prev是合适位置的之前一个,cur是合适位置的下一个。
npages_free完整实现见下。
void npages_free(struct PageInfo *pp, unsigned int n)
{
struct PageInfo *cur, *prev;
unsigned int i;
for (i = 0; i < n; i++)
{
if ((pp + i)->pp_ref)
panic("npages_free error: (pp+%d)->pp_ref != 0", i);
if ((pp + i)->pp_link != NULL)
panic("npages_free error: (pp+%d)->pp_link != NULL", i);
}
if (page2pa(page_free_list) < page2pa(pp))
{
cur = page_free_list;
page_free_list = pp + n - 1;
pp->pp_link = cur;
for (i = 1; i < n; i++)
(pp + i)->pp_link = pp + i - 1;
return;
}
cur = page_free_list;
prev = page_free_list;
while (page2pa(cur) > page2pa(pp))
{
prev = cur;
if ((cur = cur->pp_link) == NULL)
break;
}
// test use
cprintf("find prev %d cur %d\n", (int)(prev - pages), (int)(cur - pages));
prev->pp_link = pp + n - 1;
pp->pp_link = cur;
for (i = 1; i < n; i++)
(pp + i)->pp_link = pp + i - 1;
return;
}
kfree
和kmalloc类似,算出大小释放即可
void kfree(void *kva, size_t size)
{
struct PageInfo *pp = pa2page(PADDR(kva));
int npages;
size = ROUNDUP(size, PGSIZE);
npages = size / PGSIZE;
npages_free(pp, npages);
}
兼容单页分配
为了和已经写的兼容,直接调用npages_xx即可
struct PageInfo *page_alloc(int alloc_flags)
{
return npages_alloc(1, alloc_flags);
}
void page_free(struct PageInfo *pp)
{
npages_free(pp, 1);
}
测试
首先取消有关内存的几个check,有几个原因导致:
free后不是放在头的check page中做了mmio映射,从而kern pgdir中对应的pd就是PTE_P
所以check除了第一个全部取消。
freelist free后顺序
首先是单页free后的顺序测试
struct PageInfo *alloc1 = page_alloc(1);
struct PageInfo *alloc2 = page_alloc(1);
struct PageInfo *alloc3 = page_alloc(1);
cprintf("alloc 1 at %d\n", (int)(alloc1 - pages));
cprintf("alloc 2 at %d\n", (int)(alloc2 - pages));
cprintf("alloc 3 at %d\n", (int)(alloc3 - pages));
page_free(alloc1);
page_free(alloc3);
刚才free中写了测试语句,输出如下:
alloc 1 at 1023
alloc 2 at 1022
alloc 3 at 1021
find prev 1023 cur 1020
malloc/free 测试
之前在写显存的双缓冲的时候,委曲求全选择了静态分配。这里重新使用动态分配并进行测试:
void init_framebuffer(){
void *malloc_free_test;
if((framebuffer = (uint8_t *) kmalloc((size_t)(graph.scrnx*graph.scrny)))== NULL)
panic("kmalloc error!");
malloc_free_test = framebuffer;
kfree(framebuffer,(size_t)(graph.scrnx*graph.scrny));
if((framebuffer = (uint8_t *) kmalloc((size_t)(graph.scrnx*graph.scrny)))== NULL)
panic("kmalloc error!");
if(malloc_free_test == framebuffer)
cprintf("kmalloc/kfree check success\n");
else
panic("kmalloc/kfree error!\n");
// framebuffer = tmpbuf;
if(framebuffer == NULL)
panic("Not enough memory for framebuffer!");
}
测试输出正确。
更高效的空闲链表设计
上面说过,原来的空闲链表是连接的一个一个页的信息。但是由于JOS在设计的时候希望能够每个页有一个对应的页信息。并利用此来从pageinfo找到kva。所以设计的新的pageinfo结构体仍然是每个页拥有一个。
Free Area
新增一个概念:Free Area。所谓Free Area则是空闲页连接起来的一片区域,直到下一个被使用的页。
这将涉及到两个重要的信息:
FreeArea的第一个页是谁FreeArea的大小是多少
以及分配,释放的策略的不同。这里使用First fit的策略来分配页。
新的PageInfo结构体
设计的新的PageInfo结构体:
#define FIRSTPAGE 0x1
struct PageInfo {
// Old:Next page on the free list.
// New:Fist page in next free area.
struct PageInfo *pp_link;
// some infomation about this page
uint8_t flags;
// size of this free area.
uint32_t freesize;
// pp_ref is the count of pointers (usually in page table entries)
// to this page, for pages allocated using page_alloc.
// Pages allocated at boot time using pmap.c's
// boot_alloc do not have valid reference count fields.
uint16_t pp_ref;
};
具体分配策略与释放策略
npages_alloc
分配一个大小为n pages的页,要遍历free list。
这里的free list保存的不再是一页页的链表,而是Free area的链表。
找到第一个freesize > n的区域,分配出去,并且设置后面的一页为新的Area头。
npages_free
释放一个大小为n pages的页,也要遍历free list,找到地址在其前的进的看能不能加入它的area,找到地址在其后的,看看能否加入。不能的话需要插入一个新的独立的area。
类似的思路在ucore中写过了,在JOS中由于init的时候比较复杂,链表的链接问题比较严重,这里就不尝试了。关于ucore的实现可见我的CSDN 博客。
[自制操作系统] 连续页分配释放&kmalloc/kfree的更多相关文章
- kmalloc/kfree,vmalloc/vfree函数用法和区别
http://blog.csdn.net/tigerjibo/article/details/6412881 kmalloc/kfree,vmalloc/vfree函数用法和区别 1.kmalloc ...
- 内存分配方法 kmalloc()、vmalloc()、__get_free_pages()
Copyright: 该文章版权由潘云登所有.可在非商业目的下任意传播和复制. 对于商业目的下对本文的任何行为需经作者同意. kmalloc #include <linux/slab.h> ...
- Linux kmalloc/kfree 源码解读
kmalloc/kfree用于划分和回收内核空间低区内存的方法.改组方法没有直接通过伙伴系统进行内存的划分,通过slab算法进行分配的.同时也为每个CPU提供一个阵列缓存,用于提高分配效率.下面对改组 ...
- 自制操作系统小样例——参考部分linux0.11内核源码
详细代码戳这里. 一.启动引导 采用软件grub2进行引导,基于规范multiboot2进行启动引导加载.multiboot2的文档资料戳这里. 二.具体内容 开发环境 系统环境:Ubuntu 14. ...
- 《30天自制操作系统》笔记(06)——CPU的32位模式
<30天自制操作系统>笔记(06)——CPU的32位模式 进度回顾 上一篇中实现了启用鼠标.键盘的功能.屏幕上会显示出用户按键.点击鼠标的情况.这是通过设置硬件的中断函数实现的,可以说硬件 ...
- 《30天自制操作系统》笔记(03)——使用Vmware
<30天自制操作系统>笔记(03)——使用Vmware 进度回顾 在上一篇,实现了用IPL加载OS程序到内存,然后JMP到OS程序这一功能:并且总结出下一步的OS开发结构.但是遇到了真机测 ...
- 《30天自制操作系统》笔记(02)——导入C语言
<30天自制操作系统>笔记(02)——导入C语言 进度回顾 在上一篇,记录了计算机开机时加载IPL程序(initial program loader,一个nas汇编程序)的情况,包括IPL ...
- 《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!
<30天自制操作系统>笔记(01)——hello bitzhuwei's OS! 最初的OS代码 ; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序的装载地址 ; 以 ...
- 自制操作系统(七) 加快中断处理,和加入FIFO缓冲区
参考书籍<30天自制操作系统>.<自己动手写操作系统> 2016-05-26.2016-07-09 主要是加快中断处理,和加入FIFO缓冲区. 因为之前是将打印字符的代码放在了 ...
随机推荐
- golang 之验证码api
知识一:如何返回一个json数据? 先定义一个结构体ResponseData,2个参数,并返回的是json数据,key就是json后定义的名称 type ResponseData struct { S ...
- 安徽省2016“京胜杯”程序设计大赛_K_纸上谈兵
纸上谈兵 Time Limit: 1000 MS Memory Limit: 65536 KB Total Submissions: 3 Accepted: 1 Description 战国时 ...
- HTML的正确入门姿势——基本结构与基本标签
一.什么是HTML HTML是超文本标签语言,即网页的源码.而浏览器就是翻译解释HTML源码的工具. 二.HTML文档的结构 HTML文档主要包括三大部分:文档声明部分.<head>头部部 ...
- BPM与OA的区别及联系
BPM与OA的区别及联系 近年来,在企业管理信息系统一些名词反复被提及,然而外行人对于这些名词则是一头雾水,网上的解释又鱼龙混杂,没有绝对权威的文献可供参考,因此也就让我们对这些名词的认识越来越模糊. ...
- Android -- 深入了解自定义属性
1,相信我们写过自定义控件的同学都会有一个疑问,自定义属性到底是怎么工作的,为什么要使用自定义属性呢,接下来结带着大家一起来学习学习,在学习这一篇的时候,可以下看看我的上一篇<从源码的角度一步步 ...
- 【算法与数据结构】Java实现字符串的全排列及组合
注:本文记录了代码编写及调试过程,想直接浏览正确答案的请移步文章结尾. 一.字符串的全排列问题 1. 下面是最初的代码(答案有错误-重复输出) import java.util.Scanner; pu ...
- python自动化运维四:nmap端口扫描
p { margin-bottom: 0.25cm; line-height: 120% } a:link { } 端口扫描器: Python的第三方模块python-nmap可以实现高效的端口扫描. ...
- ffmpeg音频播放代码示例-avcodec_decode_audio4
一.概述 最近在学习ffmpeg解码的内容,参考了官方的教程http://dranger.com/ffmpeg/tutorial03.html,结果发现这个音频解码的教程有点问题.参考了各种博客,并同 ...
- Spring Web MVC(二)
[toc] 五大核心组件 Controller (处理器.控制器) 控制器的概念是MVC设计模式的一部分(确切地说,是MVC中的C).应用程序的行为通常被定义为服务接口, 而控制器使得用户可以访问应用 ...
- .Net Framework下对Dapper二次封装迁移到.Net Core2.0遇到的问题以及对Dapper的封装介绍
今天成功把.Net Framework下使用Dapper进行封装的ORM成功迁移到.Net Core 2.0上,在迁移的过程中也遇到一些很有意思的问题,值得和大家分享一下.下面我会还原迁移的每一个过程 ...