[OS64][034] 源码阅读:程序9-1 通用内存分配函数 kmalloc()

学习笔记

使用教材(配书源码以及使用方法)
《一个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[] 手动创建静态存储空间

[OS64][034] 源码阅读:程序9-1 通用内存分配函数 kmalloc()_第1张图片
slab_init():为Kmalloc_cache_size[] 手动创建静态存储空间.png
  • 内存池数组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;
}

分支零:从静态存储空间直接分配内存对象

[OS64][034] 源码阅读:程序9-1 通用内存分配函数 kmalloc()_第2张图片
分支零:从静态存储空间直接分配内存对象.png
  • 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 的内存;

[OS64][034] 源码阅读:程序9-1 通用内存分配函数 kmalloc()_第3张图片
分支一:存储空间不够了,申请者一次需要<=512B 的内存;

1、假设我需要申请一块200字节的内存空间,首先会找到容量>= 200字节最小的内存池Kmalloc_cache_size[3],这个内存池的size256,size是指每次分配的大小,不是什么数组长度;
2、然后,在这个内存池的slab链表中寻找free_count !=0slab结构
3、此时,每个slab相当于管理着一张2MB大小的物理页,总共有三个部分

  • 头部是存储空间(用来满足分配内存的需要);
  • 中间是slab结构体(其中list字段的prev以及next指针可以连接到链表中的下一个slab结构体的list字段,这两个指针实现着链表结构);
  • 尾部是用free_countunsigned long变量组成的color_map(这张位图的每一位都与头部的存储空间每size字节的分配情况一一对应);

4、虽然这里我们只申请200字节的空间,但是实际分配的时候是按照内存池的size进行分配的, 也就是申请200字节,实际上会给size=256字节
5、假设链表的第一个slab管理着的物理空间其字段free_count!=0,那么就在这张物理页的存储空间分配256字节给申请者,同时在对应位图的对应bit上置1,表示分配调一个size=256字节的空间

  • 问:假设我们申请了很多次,每次都是申请200字节,那么对应于size=256slab链表上的已有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

[OS64][034] 源码阅读:程序9-1 通用内存分配函数 kmalloc()_第4张图片
情况一:静态空间可以存放 struct Slab以及color_map

只不过这里直接用了静态存储空间来放 struct Slab以及 color_map
并且新申请到的一整张物理页全部拿来当存储空间

情况二:在动态空间存放 struct Slab以及color_map


[OS64][034] 源码阅读:程序9-1 通用内存分配函数 kmalloc()_第5张图片
情况二:在动态空间存放 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同样会更新

你可能感兴趣的:([OS64][034] 源码阅读:程序9-1 通用内存分配函数 kmalloc())