《操作系统真象还原》第12章malloc的实现与原理

完善堆内存管理

咱们这一章的目的是实现堆内存管理:也就是实现malloc,free,可以分成以下几个步骤

  • 系统调用的实现
  • 内存管理系统所需的数据结构实现
  • sys_malloc的实现
  • sys_free的实现

用户调用系统调用实现的流程

image-20210915095236174

添加一个系统调用的具体过程

image-20210915083742968

上述过程说白了就是在syscall_table中添加了一个函数。

系统调用的一些细节

_syscall是汇编实现的,所以传参可以用寄存器,用寄存器的原因是可以更快,不用访问两个栈

实现中断处理程序syscall_handler 时也得和其他的中断一样先做一遍push

eax存储功能号并存储返回值,ebx,ecx,edx,esi,edi 分别为第1到5个参数的传递,参数多了就压栈,然后传一个栈指针进去即可。

printf的实现

image-20210915102120344

printf的原型为:int printf(const char * format , …); …代表的就是不定长的参数,这里的实现很巧妙,这里面涉及的三个宏,我觉得没什么意义,直接写也是一样的,总之就是根据了栈中每个元素占一个字长,然后%个数就是所谓的不定长参数的个数,实际上我们还可以直接根据这个求出参数的个数,就变成定长了。

  • 把这些参数从右到左全部压入到栈中
  • 从左往右遍历format
  • 遇到%,就停下来,去处理栈中对应位置的数据,然后写入到str中,并pop
  • 最后format回读到 ‘\0’ ,那么也就是结束了

下一步再通过write将str写入到某个文件中(fd) ,因为这里没有实现文件系统,所以就直接写入到显示器上了。另外,也可以不调用write,生成str就是sprintf做的事情。

堆内存管理

在这一章我们要对内存进行更细致的分配,前面我们完成的分配主要是针对任务之间的,接下来的内存管理就是针对任务内部的了,因此有很多小内存分配需求需要处理,(咱们都知道内存是分为内核和用户的,但其实从逻辑上来说,这两个没有什么区别,处理的时候加一个判断就可以了),这个内存管理的主要任务是要分配和释放颗粒度更小的内存

内存块描述符

注意:free_list是无限大的,因为blocks_per_arena 只是一个arena能够提供的内存块个数

/* 内存块 */
struct mem_block {	// 用来添加到同规格内存块描述符的free_List中
   struct list_elem free_elem;
};
/* 内存块描述符 */
struct mem_block_desc {
   uint32_t block_size;		 // 内存块大小
   uint32_t blocks_per_arena;	 // 本arena中可容纳此mem_block的数量.  
   struct list free_list;	 // 目前可用的mem_block链表
};

内核和用户进程的 内存块描述符

struct mem_block_desc k_block_descs[DESC_CNT];	// 内核内存块描述符数组

struct mem_block_desc u_block_desc[DESC_CNT];   // 用户进程内存块描述符

内存仓库

下面是arena的定义,以后咱们的每次分配都是分配某个arena中的块。arena中的块,共有8中类型,16,32,64,128,256,512,1024,大于1024时 large 为 1。

当申请内存大小超过1024KB时,直接返回一个页框,不再从arena中划分,下面是arena的结构:

  • 元信息
  • 内存池区域,mem_block
/* 内存仓库arena元信息 */
struct arena {
   struct mem_block_desc* desc;	 // 此arena关联的mem_block_desc
/* large为ture时,cnt表示的是页框数。
 * 否则cnt表示空闲mem_block数量 */
   uint32_t cnt;
   bool large;		   
};

可以看出:arena实际上就分两种

image-20210915174533232

image-20210915184731458

下面我们来看看上面两种arena如何分配内存,假如要分配一个大小为size 字节的内存

对于size>1024KB的内存分配十分简单,直接分配所需要的物理页即可

image-20210915151036454

主要看看第一种,我们先澄清一个概念,在这种方法中实现的arena实际上就是一个普通的物理页,真正的内存管理是mem_block_desc中free_list;

咱们分两步来理解上面那句话的意思:

  1. 首先判断有没有内存,是看的mem_block_desc中的free_list,如果free_list是空,就分配一页arena

image-20210915182056027

并在此之后分配给free_liist cnt 个结点,每个都指向在arena中相应块的地址。

image-20210915182352745

  1. 咱们再看分配内存块,主要有以下两步
    • 获取free_list的头结点,就是分配的块的地址
    • a ->cnt – ;

image-20210915182042864

可以看出实际上arena就是一个计数器,看这个内存管理的代码最重要的还是free_list

哈哈或许你看到这里,会觉得作者为什么要用这种方法呢?其实这又是计算机思想的一大整合,咱们类比一下虚拟地址和物理地址的映射时,分配虚拟地址必须连续,但是物理地址可以不用连续,这样就减少了内存碎片的产生**。作者这样做,也是为了减少内存碎片。**

咱们在这里可以把arena那个物理页看成物理地址,free_list看成虚拟地址,free_list很可能会由很多不连续的arena中的块来一起构成,但是在使用的时候咱们都只看free_list就行啦,这在后面的释放中显得很是方便,可以在O(1)内做到释放和分配,并且内存利用率基本上拉满了,这真是个妙招!!!

回收内存

回收页物理地址

void pfree(uint32_t pg_phy_addr) ;

直接将pg_phy_addr指向的页在位图中清0即可

回收页虚拟地址

将这页虚拟地址在位图中清0即可

去除映射

让此虚拟地址的pte的P位清0并更新tlb

回收虚拟页指向的物理内存

直接映射成物理内存,pfree即可

回收内存

回收内存才是我们的主要函数,也是系统调用的接口,思路还是很清晰的,顺着申请的思路,对size >1kB的直接当成物理页回收即可反之则将其放回到相应规格的free_list中,并将其a ->cnt ++ ;

这里要注意的是:如果a->cnt满了,说明此arena没有存任何东西,也一并释放掉这个物理页,和相应的free_list中删去这些结点。

malloc,free

这就是这一章的主要目的啦,这个实现就很简单啦,直接系统调用即哈哈。

执行结果

image-20210915194820153

总结

从上面的管理我们可以发现,在c语言里,为什么咱们free的时候不需要提供大小了哈哈,只用free§即可了,分两种情况

  • 因为p在free_list中,那么他的大小就是已知的。
  • 因为在p前面有12个字节,这12个字节里就存在着一块arena的内存大小

特别注意:在对内存进行修改的时候一定要关中断!!

64位下的源代码Makefile脚本修改

https://blog.csdn.net/qq_45923646/article/details/120273571

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值