学习笔记
使用教材(配书源码以及使用方法)
《一个64位操作系统的设计与实现》
http://www.ituring.com.cn/book/2450
https://www.jianshu.com/p/28f9713a9171
源码结构
- 配书代码包 :第9章 \ 程序 \ 程序9-1
为什么要有函数 kmalloc() ?
- 内核想要申请空间,但是不想要一整页那么多的,只想申请一些少的空间时用到
kmalloc
; - 这里的"少"是针对一张物理页2MB的容量来讲的, 比
2MB
的数值小在这里就叫做字节数少; -
kmalloc()
就负责分配这些一小块、一小块的内存空间;
一、slab_init():为Kmalloc_cache_size[] 手动创建静态存储空间
- 内存池数组
Kmalloc_cache_size[]
位于内核层; - 在
memory_management_struct.end_of_struct
开始,创建与数组元素一一对应的struct Slab结构体、填充空间及其color_map位图,一共是16
个; - 从物理地址2MB开始,创建与数组元素一一对应的存储空间,一张物理页2MB全部空间都拿来做存储空间,每个内存对象的大小(即每次分配的字节数)是
Kmalloc_cache_size[i].size
字节;
unsigned long slab_init()
{
. . .
内存池 [i] 已经分配了多少个内存对象,这里初始化置 0
kmalloc_cache_size[i].cache_pool->using_count = 0;
内存池 [i] 还有多个内存对象可以分配,每分配一次,free_count--
kmalloc_cache_size[i].cache_pool->free_count
= PAGE_2M_SIZE / kmalloc_cache_size[i].size;
color_map 中每一位对应一个内存对象的分配情况
已分配的内存对象,位图对应bit 置 1
需要用 “color_length 个 unsigned long 变量” 表示 整张位图
kmalloc_cache_size[i].cache_pool->color_length
=((PAGE_2M_SIZE / kmalloc_cache_size[i].size + sizeof(unsigned long) * 8 - 1) >> 6) << 3;
用来记录最初有多个空闲对象(这里设置初始化之后,不会再变化)
kmalloc_cache_size[i].cache_pool->color_count
= kmalloc_cache_size[i].cache_pool->free_count;
. . .
}
二、kmalloc() 以及 kmalloc_create()
kmalloc( )
会返回申请到的存储空间地址(线性地址)kmalloc _create( )
会返回创建的slab
的线性地址,一个新建的slab
意味着带来新鲜的许多空闲对象可分配存储空间包括:
slab_init() 创建的静态空间 + 随后由kmalloc_create()创建的动态空间
;分支零:于
slab_init()
时期,手动创建的静态存储空间还够用,那就直接从这些静态存储空间分配内存对象给申请者;分支一:存储空间不够了,申请者一次需要
<=512B
的内存;分支二:存储空间不够了,申请者一次需要
>512B & <= 1MB
的内存;
void * kmalloc(unsigned long size,unsigned long gfp_flages)
{
1、遍历16个内存池,找到容量刚刚好比申请的size大的最小的内存池,找到那个管理存储空间的那个slab
for(i = 0;i < 16;i++)
if(kmalloc_cache_size[i].size >= size)
break;
slab = kmalloc_cache_size[i].cache_pool;
2、是否有空闲?有就进入if{ },没有就进入else{ }
2.1 一进入if{ } ,就马上执行do{ } , 即先看静态创建的存储空间(kmalloc_cache_size[i].cache_pool)是否还有空闲?
很明显,当只有静态存储空间时,全部的list都只有一个元素,那就是静态创建的Slab
if(kmalloc_cache_size[i].total_free != 0)
{
do
{
if(slab->free_count == 0)
slab = container_of(list_next(&slab->list),struct Slab,list);
else
break;
}while(slab != kmalloc_cache_size[i].cache_pool);
}
2.2 存储空间不够时,进入else { },申请分配动态空间
else
{
slab = kmalloc_create(kmalloc_cache_size[i].size);
if(slab == NULL)
{
color_printk(BLUE,BLACK,"kmalloc()->kmalloc_create()=>slab == NULL\n");
return NULL;
}
kmalloc_cache_size[i].total_free += slab->color_count;
color_printk(BLUE,BLACK,"kmalloc()->kmalloc_create()<=size:%#010x\n",kmalloc_cache_size[i].size);///////
list_add_to_before(&kmalloc_cache_size[i].cache_pool->list,&slab->list);
}
3、如果有空闲slab->free_count != 0 就设置位图、修改对应参数
for(j = 0;j < slab->color_count;j++)
{
if(*(slab->color_map + (j >> 6)) == 0xffffffffffffffffUL)
{
j += 63;
continue;
}
if( (*(slab->color_map + (j >> 6)) & (1UL << (j % 64))) == 0 )
{
*(slab->color_map + (j >> 6)) |= 1UL << (j % 64);
slab->using_count++;
slab->free_count--;
kmalloc_cache_size[i].total_free--;
kmalloc_cache_size[i].total_using++;
return (void *)((char *)slab->Vaddress + kmalloc_cache_size[i].size * j);
}
}
- 位图的修改原理参见
[C语言]模拟color_map位图分配
https://www.jianshu.com/p/b4ab98c3d837
kmalloc_create() 函数的结构
struct Slab * kmalloc_create(unsigned long size)
{
1、申请一张2MB的物理页
page = alloc_pages(ZONE_NORMAL,1, 0);
page_init(page,PG_Kernel);
switch(size)
{
////////////////////slab + map in 2M page
case 32:
case 64:
case 128:
case 256:
case 512:
分支一:
三个部分全都放在这张page里面!
///////////////////kmalloc slab and map,not in 2M page anymore
case 1024: //1KB
case 2048:
case 4096: //4KB
case 8192:
case 16384:
//////////////////color_map is a very short buffer.
case 32768:
case 65536:
case 131072: //128KB
case 262144:
case 524288:
case 1048576: //1MB
分支二:
这张page完全用于内存分配
管理用的 slab 结构体 以及 color_map 放在其他内存池(其他物理页中)
default:
return NULL;
}
return slab;
}
分支零:从静态存储空间直接分配内存对象
-
kmalloc(50) :50 <= 64 && 50 > 32
选中内存池[1]
申请到64b
字节 -
kmalloc(64) :64 <= 64 && 64 > 32
选中内存池[1]
申请到64b
字节 -
kmalloc(600) :600 <= 1024 && 600 > 512
选中内存池[5]
申请到1024b
字节 -
kmalloc(800) :800 <= 1024 && 800 > 512
选中内存池[5]
申请到1024b
字节 -
kmalloc(1020) :1024 <= 1024 && 1024 > 512
选中内存池[5]
申请到1024b
字节
分支一:存储空间不够了,申请者一次需要<=512B 的内存;
1、假设我需要申请一块
200
字节的内存空间,首先会找到容量>= 200字节
最小的内存池Kmalloc_cache_size[3]
,这个内存池的size
是256
,size
是指每次分配的大小,不是什么数组长度;
2、然后,在这个内存池的slab链表中寻找free_count !=0
的slab
结构
3、此时,每个slab
相当于管理着一张2MB大小的物理页
,总共有三个部分:
- 头部是存储空间(用来满足分配内存的需要);
- 中间是
slab
结构体(其中list
字段的prev
以及next
指针可以连接到链表中的下一个slab
结构体的list
字段,这两个指针实现着链表结构);- 尾部是用
free_count
个unsigned long
变量组成的color_map
(这张位图的每一位都与头部的存储空间每size字节的分配情况一一对应);4、虽然这里我们只申请
200
字节的空间,但是实际分配的时候是按照内存池的size进行分配的, 也就是申请200
字节,实际上会给size=256字节
5、假设链表的第一个slab
管理着的物理空间其字段free_count!=0
,那么就在这张物理页的存储空间分配256
字节给申请者,同时在对应位图的对应bit
上置1
,表示分配调一个size=256
字节的空间
- 问:假设我们申请了很多次,每次都是申请
200
字节,那么对应于size=256
的slab链表上的已有slab们的存储空间都被填满之后,这时候如何还要继续申请的还是200
字节的空间会怎么样?
答:会增加一个新的
slab结构
,并被链接到属于size=256
的slab链表上去。
(1)所谓新增的slab结构
不仅仅是struct Slab
,而其实是新申请了一整张2MB物理页(头部+中间+尾部),用list字段做链表连接;
(2)可以看见,这个新增的slab结构
可以再满足很多次对于200
字节的申请,因为存储空间即便是以256字节
作为分割单位来一次次分配,也是可以分很多次的,不要混淆成,每申请一次200
字节,就要创建一个新的slab结构
;
(3)并且只要申请的是200
字节,就是一定在Kmalloc_cache_size[3].size = 256
这个内存池里找,如果人家链表已有的slab结构
的存储空间都分掉了,就需要在这个内存池的链表增加结点,不会无缘无故跑去其他内存池,比如不会无缘无故跑去size=32
字节的内存池,因为256
就是刚刚好第一个比200
大的数字,可以满足申请需求,也就是为了提高物理页的利用率才搞了那么多不同大小的内存池。
分支二:存储空间不够了,申请者一次需要>512B & <= 1MB的内存;
需要将
struct Slab
以及color_map
与存储空间分开放,即不能放在同一张屋物理页里面;struct Slab
大小是72
字节,固定会选中内存池[2].size=128
color_map
最大不过是32
字节,固定会选中内存池[0].size=32
具体分配时有很多种情况,需要看:
32字节的内存池静态空间 有空闲 还是 满?
128字节的内存静态空间 有空闲 还是 满?
带申请的size是 <=512 还是 > 512 <=1MB ?
待申请的size选中的那个内存池的静态空间时 有空闲 还是 满?
以下举两个情况的例子:
情况一:静态空间可以存放
struct Slab
以及color_map
只不过这里直接用了静态存储空间来放struct Slab
以及color_map
并且新申请到的一整张物理页全部拿来当存储空间
情况二:在动态空间存放 struct Slab以及color_map
struct Slab以及color_map 全部放在动态空间里
仍旧是申请了一张新的物理页来全部当存储空间
- 以前,在申请的内存比较少时,是把用于实际分配空间的存储空间、管理分配情况的
slab结构体
以及color_map位图
这三部分都放在同一张物理页里面;- 考虑,最极端的情况,申请1MB的内存,如果仍旧把三个部分都放在一张物理页里面,那么这张物理页的存储空间部分就只能满足一次申请需要,会造成内存的巨大浪费;
- 因此,在一次申请的字节数
> 512 且 <= 1MB
这种情况下,会把slab结构体
以及color_map位图
拿出来
1、首先,slab结构体
的大小是固定的72字节,它会被放到size=128字节
的内存池中;
2、其次,color_map位图
虽然大小不确定,但是由于是申请比较大的空间,color_map位图
不会太大的,如果是申请1MB,那么color_map位图
其实只需要一个unsigned long
变量就足够了,它会被放到size=32
字节的内存池中;
3、最后,一张完整的2MB大小的物理页,全部都作为存储空间,全部可以用来做分配,刚好可以满足两次都是1MB的申请需要,内存利用率非常高。
三、kfree()
unsigned long kfree(void * address)
{
int i;
int index;
struct Slab * slab = NULL;
1、计算出待释放的内存对象所在的物理页的线性地址
void * page_base_address = (void *)((unsigned long)address & PAGE_2M_MASK);
for(i = 0;i < 16;i++)
{
slab = kmalloc_cache_size[i].cache_pool;
do
{
2、找到管理这个物理页(存储空间)的slab结构体
if(slab->Vaddress == page_base_address)
{
3、计算待释放的内存对象是存储空间的第几个分配项
index = (address - slab->Vaddress) / kmalloc_cache_size[i].size;
4、位图对应bit置 0
*(slab->color_map + (index >> 6)) ^= 1UL << index % 64;
5、更新相关参数
slab->free_count++;
slab->using_count--;
kmalloc_cache_size[i].total_free++;
kmalloc_cache_size[i].total_using--;
6、如果本次释放导致,管理该存储空间全部被释放了,
看内存池剩余空闲空间是否满足kmalloc_cache_size[i].total_free >= slab->color_count * 3 / 2,
并且当前slab是管理动态存储空间的slab,而不是管理静态存储空间的slab,就再释放这个slab结构体
if((slab->using_count == 0) && (kmalloc_cache_size[i].total_free >= slab->color_count * 3 / 2) && (kmalloc_cache_size[i].cache_pool != slab))
{
switch(kmalloc_cache_size[i].size)
{
////////////////////slab + map in 2M page
应对分支一:
case 32:
case 64:
case 128:
case 256:
case 512:
list_del(&slab->list);
kmalloc_cache_size[i].total_free -= slab->color_count;
page_clean(slab->page);
free_pages(slab->page,1);
break;
应对分支二:
default:
list_del(&slab->list);
kmalloc_cache_size[i].total_free -= slab->color_count;
kfree(slab->color_map);
page_clean(slab->page);
free_pages(slab->page,1);
kfree(slab);
break;
}
}
return 1;
}
else
slab = container_of(list_next(&slab->list),struct Slab,list);
}while(slab != kmalloc_cache_size[i].cache_pool);
}
color_printk(RED,BLACK,"kfree() ERROR: can`t free memory\n");
return 0;
}
- 应对分支二这里,有两句非常妙的调用
kfree(slab->color_map);
以及kfree(slab);
,因为分支二时,这两个部分不和存储空间处于同一张物理页,因此需要嵌套调用kfree
,把它们看做是一个内存对象去释放,参见分支二的两张图示,这两个部分所占用的存储空间被释放后,管理这两处存储空间的slab以及color_map同样会更新;