Last updated
Last updated
什么是first-fit 连续物理内存分配算法
首次适应(first fit, FF)算法,要求空闲分区链以地址递增的次序链接。在分配内存时,从链首开始顺序查找,直至找到一个大小能满足要求的空闲分区为止。然后再按照作业的大小,从该分区划出一块内存空间,分配给请求者,余下的空闲分区仍留在空闲链中。若从链首直至链尾都不能找到一个能满足要求的分区,则表明系统中已没有足够大的内存分配给该进程,内存分配失败,返回。
first fit
算法的特点是什么
该算法倾向于优先利用内存中低地址部分的空闲分区,从而保留了高址部分的大空闲区。这为以后到达的大作业分配大的内存空间创造了条件。其缺点是低址部分不断被划分,会留下许多难以利用的、很小的空闲分区,称为碎片。而每次查找又是从低址部分开始的,这无疑又会增加查找可用分区时的开销。
分区分配的操作是什么
分配内存:
系统应利用某种分配算法,从空闲分区链(表)中找到所需大小的分区。设请求的分区大小为u.size
,表中每个空闲分区的大小可表示为m.size
,若m.size-u.size<=size
(size是事先规定的不再切割的剩余分区的大小),说明多余部分太小,可不再切割,将整个分区分配给请求者。否则(即多余部分超过size
),便从该分区中按请求的大小划分出一块内存空间分配出去,余下的部分仍留在空闲分区链(表)中。然后,将分配区的首址返回给调用者。
回收内存
当进程运行完毕释放内存时,系统根据回收区的首址,从空闲区链(表)中找到相应的插入点,此时可能出现以下四种情况之一:
回收区与插入点的前一个空闲分区F1相邻接。此时应将回收区与插入点的前一分区合并,不必为回收分区分配新表项,而只需修改前一分区F1的大小。
回收分区与插入点的后一空闲分区F2相邻接。此时也可将两份区合并,形成新的空闲分区,但用回收区的首地址作为新空闲区的首址,大小为两者之和。
回收区同时与插入点的前、后两个分区邻接。此时将三个分区合并,使用F1的表项和F1的首址,取消F2的表项,大小为三者之和。
回收区既不与F1邻接,又不与F2邻接。这时应为回收区单独建立一个新表项,填写回收区的首址和大小,并根据其首地址插入到空闲链中的适当位置。
结合 清华大学出版社 严蔚敏 《数据结构》,第196~198页,第8.2节,了解First Fit算法,并重写以下函数:default_init
,default_init_memmap
,default_alloc_pages
,default_free_pages
。
其大意为:
准备
熟悉list.h
中的结构体list
及其接口,并了解le2page
等宏的作用;
default_init
你可以重复使用default_init
函数初始化free_list
和nr_free
。free_list
用来记录空闲的内存块。nr_free
是空闲内存块的总数。
default_init_memmap
调用路径: kern_init
--> pmm_init
--> page_init
--> init_memmap
--> pmm_manager
--> init_memmap
.
该函数被用来初始化一个空闲内存块。
default_alloc_pages
在空闲块的链表内搜索第一个空闲的内存块,并重新设置该块的尺寸。
default_free_pages
将内存页重新连接到空闲块的链表中,有可能会将几个小的空闲内存块合并为一个大的内存块。
default_init
在这之前,我们先根据要求,打开libs/list.h
并认真查看。然后,我们再来看一遍注释:
这里告诉我们,我们需要做的是初始化free_list
,然后将nr_free
设为0
。
可以看到,在该函数之前,曾定义了free_area
与两个相关的宏free_list
,nr_free
:
其中,free_area_t
的定义为
显然,在default_pmm.c
中,宏free_list
和nr_free
即为变量free_area
的两个成员,这样写调用更为简便。
那么,如何对free_list
进行初始化呢?我们把目光转向list.h
,发现有这样一段代码:
显然,这就是用来初始化list
的。好了,问题解决了,最终的代码如下:
default_init_memmap
还是先来看注释:
该函数的功能是初始化一个内存块,
为了初始化一个空闲的内存块,首先你需要初始化该空闲的内存块中每一页。这个过程包括:
设置p->flags
的PG_property
位,这意味着这一页是可用的。P.S. 在函数pmm_init
(在pmm.c
中),PG_reserved
位已经被设置好。
如果该页是可用的而且不是一个空闲内存块的第一页,那么p->property
应该被设置为0
。
如果该页是可用的而且是一个空闲内存块的第一页,那么p->property
应该被设置为该块的总页数。
p->ref
应该被设为0
,因为现在p
是可用的而且不含有引用。
在这之后,我们可以使用p->page_link
来将该页连接到free_list
。(例如:list_add_before(&free_list, &(p->page_link));
)
最后,我们应该更新空闲内存块的总数:nr_free += n
。
以上为注释的含义。那么,在此需要提起的是,在memlayout.h
中定义了两个宏以及对其的一系列操作:
这两个宏是内存块所在page
的flag,其中,PG_reserved
是flag的第0位,当其值为1
时表示该页是内核预留页,不能被alloc
或free_pages
使用;PG_property
是flag的第一位,当其值为1
时表示该页是一个空闲内存块的第一个页,可以被alloc_pages
使用,当其值为0
时,如果此页是一个空闲内存块的第一个页,那么该页以及该内存块已经被分配。
以下几个宏(函数)均为对这两个flag的操作,不再做赘述。
那么根据提示,我们很容易就可以写出以下代码:
需要说明的是,最后的list_add
过程,注释中只是举了例子,并不是真的要这样写,否则代码会无法正常运行。
default_alloc_pages
老规矩,先看注释:
(4) default_alloc_pages
:
在空间的内存链表中搜索第一个可用的内存块(块尺寸>=n)并重新设置发现块的尺寸,然后该块作为被malloc
调用得到的地址被返回。
(4.1) 所以你应该在空闲的内存链表中像这样搜索:
(4.1.1) 在while
循环中,获得结构体page
并检查该块所含空闲页数(p->property
)是否大于等于n
(4.1.2) 如果我们找到这个p
,这就意味着我们找到了一个尺寸大于等于n
的空闲内存块,它的前n
页可以被申请。该页的一些标志位应该被设置为:PG_reserved = 1
,PG_property = 0
。然后,取消该页与free_list
的链接。
(4.1.2.1) 如果p->property
大于n
,我们应该重新计算该内存块的剩余页数。(例如:le2page(le, page_link)->property = p->property - n;
)
(4.1.3) 重新计算nr_free
(剩余所有空闲内存页的数量)(译者注:原翻译为块)。
(4.1.4) 返回p
。
(4.2) 如果我们没有找到尺寸大于等于n
的空闲内存块,那么返回NULL
。
据解释可知,该函数作用为分配内存,即在空闲块的链表内搜索第一个空闲的内存块,并重新设置该块的尺寸。结合以上讲解很容易理解,代码也很容易写出:
default_free_pages
(5) default_free_pages
:
重新将页连接到空闲内存列表中,有可能将一些小的内存块合并为一个大的内存块。
(5.1) 通过独立内存块的base
地址,搜索它正确的位置(从低到高),并插入该页。(可能使用list_next
,le2page
,list_add_before
)
(5.2) 重置这些页的参数,例如p->ref
和p->flags
(PageProperty
)。
(5.3) 尝试合并低或高地址的内存块。注意:应该正确修改一些页的p->property
。
这部分代码的顺序可能和注释不太一样,但无伤大雅。难点在于内存块的合并。
提示一下,当合并的时候,对于某一内存块中的第一页p
,当p + p->property == base
或者base + base->property == p
时就该将这两块合并了。
好了,话不多说,上代码。
数据结构(C语言版),严蔚敏,清华大学出版社
计算机操作系统(第四版),汤小丹,西安电子科技大学出版社