内存分配算法 伙伴系统

   伙伴系统是常用的内存分配算法,linux内核的底层页分配算法就是伙伴系统,伙伴系统的优点就是分配和回收速度快,减少外部碎片。算法描述: 

 https://en.wikipedia.org/wiki/Buddy_memory_allocation                                      http://dysphoria.net/OperatingSystems1/4_allocation_buddy_system.html

网上也搜了大牛的实现。 云风:https://github.com/cloudwu/buddy  

对云风的精简版 https://github.com/wuwenbin/buddy2

            这两个版本思想一样都是维护一棵满二叉树,进行分配和回收,云风版的通过标记内存节点状态进行分配,第二个版本是保存当前内存最大的连续可用数,在某些情况下避免了无效的遍历,第二个版本也可以修改为保存最大连续内存数目的阶,内存消耗就会变小。这两个算法分配和回收复杂度都是logn,并且空闲内存必须是2^n个基本分配单位。

      然后又看了一下linux4.8的buddy system实现,linux的buddy system主要进行page分配也是linux最底层的分配,其他的分配算法都是以这个分配为基础,在x86架构下一个page是4KB。linux对内存进行了分区包括低端内存区,高端内存区,dma区,而且还对numa架构做了很多处理,对页面也进行了分类,这些不是讨论的重点,现在主要是提取linux的buddy算法,只提取核心部分,可以在控制台下运行。buddy system的数据结构就是下图所示,看着像哈希表中的拉链法,每个链表保存相同大小的内存块。最大的是10,也就是1024个基本单位,所以linux在x86下一次最多可分配4MB内存。


           顺便学习下linux内核对链表的各种操作,代码实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
//双向链表
struct list_head {
    struct list_head *prev, *next;
};

//拉链法存储空闲内存
typedef struct free_area {
    struct list_head free_list;  //把空闲内存块链接到空闲链表
    unsigned long nr_free;       //每个链表空闲块的个数
} free_area_t;

struct page {
    struct list_head page_link;
    int order;                  //当前页面所处的阶
    unsigned int size;          //如过当前页面是起始页面则size表示连续的页面数,如过不是起始页面则是0
};

typedef unsigned char uint8_t ;

#define   MAX_ORDER    11         //最大阶是10,也就是最大的分配单位是2^10,在此程序中一次最多分配4MB内存
#define   PAGE_SIZE    4096       //基本分配单位
#define   PAGE_NUMS    4096       //页面个数

uint8_t mem_size[PAGE_SIZE * PAGE_NUMS];   //需要管理的内存

struct page mem_map[PAGE_NUMS];

free_area_t free_area[MAX_ORDER];

#define PAGE_TO_PFN(page)    ((unsigned int)(page - mem_map))

#define ADDR_TO_PAGE(addr)   (((unsigned int)((uint8_t*)addr - mem_size)) / PAGE_SIZE)
#define PAGE_TO_ADDR(page)    (unsigned int)(PAGE_TO_PFN(page) * PAGE_SIZE + mem_size)
/**
 *获取成员在结构体中的相对偏移大小
 */
#define offsetof(TYPE, MEMBER)	((unsigned int)&((TYPE *)0)->MEMBER)

/**
 *获取结构体地址
 */
#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})

/**
 *初始化双向链表
 */
#define LIST_HEAD_INIT(name) { &(name), &(name) }

/**
 *用宏快速定义双向链表
 */
#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}


/**
 *在任意两个元素之间插入一个entry
 */
static inline void __list_add(struct list_head *new,
                              struct list_head *prev,
                              struct list_head *next)
{
    new->next = next;
    prev->next = new;
    next->prev = new;
    new->prev = prev;
}


/**
 *在链表头插入一个新元素,相当于头插法
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}


/**
 *在链表尾部插入一个新元素,相当于尾插法
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}

/**
 *删除节点操作,删除两个节点之间的操作
 */
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
    prev->next = next;
    next->prev = prev;
}

/**
 *删除节点
 */
static inline void __list_del_entry(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
}

/**
 *删除节点,并且将next和prev置空,linux内核中是指向一个指定的地址
 */
static inline void list_del_entry(struct list_head *entry)
{
    __list_del_entry(entry);
    entry->next = NULL;
    entry->prev = NULL;
}

/**
 *替换某个节点
 */
static inline void list_replace(struct list_head *old, struct list_head *new)
{
    new->next = old->next;
    new->prev = old->prev;
    new->prev->next = new;
    old->next->prev = new;
}

/**
 *把其中一个链表的节点移动到另一个链表头
 */
static inline void list_move(struct list_head *list, struct list_head *head)
{
    __list_del_entry(list);
    list_add(list, head);
}

/**
 *把其中一个链表的节点移动到另一个链表末端
 */
static inline void list_move_tail(struct list_head *list, struct list_head *head)
{
    __list_del_entry(list);
    list_add_tail(list, head);
}

/**
 *判断链表是否是最后一个节点
 */
static inline int list_is_last(const struct list_head *list, const struct list_head *head)
{
    return list->next == head;
}

/**
 *判断链表是否为空
 */
static inline int list_empty(const struct list_head *head)
{
    struct list_head *next = head->next;
    return (next == head) && (head->prev == next);
}

/**
 * 获取包含链表节点的结构体
 * ptr   链表节点地址
 * type  结构体类型
 * member 结构体成员
 */
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

/**
 * 获取链表第一个元素的结构体
 * ptr = head
 */
#define list_first_entry(ptr, type, member) \
    list_entry((ptr)->next, type, member)

#define list_first_entry_or_null(ptr, type, member) \
    (ptr)->next == ptr ? NULL : list_first_entry(ptr, type, member)
/**
 * 获取链表最后一个元素的结构体
 */
#define list_last_entry(ptr, type, member) \
    list_entry((ptr)->prev, type, member)

/**
 * 获取下一个元素结构体
 */
#define list_next_entry(pos, member) \
    list_entry((pos)->member.next, typeof(*(pos)), member)

/**
 * 获取上一个元素结构体
 */
#define list_prev_entry(pos, member) \
    list_entry((pos)->member.prev, typeof(*(pos)), member)

/**
 * 遍历链表
 */
#define list_for_each(pos, head) \
    for(pos = (head)->next; pos != (head); pos = pos->next)

/**
 * 遍历链表结构体
 */
#define list_for_each_entry(pos, head, member) \
    for(pos = list_first_entry(head, typeof(*(pos)), member); \
        &(pos->member) != (head);                               \
        pos = list_next_entry(pos, member))

static inline int page_to_pfn(struct page *page)
{
    return PAGE_TO_PFN(page);
}
static inline int fix_size(unsigned int size)
{
    size -= 1;
    size |= size >> 1;
    size |= size >> 2;
    size |= size >> 4;
    size |= size >> 8;
    size |= size >> 16;
    return size + 1;
}
static inline int get_order(unsigned int size)
{
    int i = 0;
    while(i < 32) {
        if(size & (1 << i))
            return i;
        i++;
    }
    return -1;
}

static inline unsigned int
__find_buddy_index(unsigned int page_idx, unsigned int order)
{
	return page_idx ^ (1 << order);
}

static inline int page_is_buddy(struct page *buddy, int order)
{
    return buddy->order == order;
}
static inline void expand(unsigned int low, unsigned int high,
                          struct page *page, struct free_area *area)
{
    unsigned int size;
    while(low < high) {
        high--;
        area--;
        size = 1 << high;
        list_add(&page[size].page_link, &area->free_list);
        area->nr_free++;
        page[size].order = high;
        page[size].size = size;
    }

}
static inline struct page *__alloc_pages(unsigned int order)
{
    unsigned int current_order;
	struct free_area *area;
	struct page *page;
	for(current_order = order; current_order < MAX_ORDER; current_order++) {
        area = &(free_area[current_order]);
        page = list_first_entry_or_null(&area->free_list, struct page, page_link);
        if(!page)
            continue;
        list_del_entry(&(page->page_link));
        page->order = -1;
        page->size = 1 << order;
        area->nr_free--;
        expand(order, current_order, page, area);
        return page;
	}
	return NULL;

}
static struct page *alloc_pages(unsigned int size)
{
    struct page *page;
    unsigned int alloc_size = fix_size(size);
    unsigned int order = get_order(alloc_size);
    if(order < 0)
        assert(0);
    if(order >= MAX_ORDER)
        assert(0);
    page = __alloc_pages(order);
    if(page)
        return page;
    return NULL;
}
static void __free_pages(struct page *page, int order)
{
    unsigned int page_idx;
    unsigned int combined_idx;
	unsigned int buddy_idx;
	unsigned int pfn;
	struct page *buddy;

	pfn = page_to_pfn(page);
	page->size = 0;
	page_idx = pfn & ((1 << MAX_ORDER) - 1);
	while(order < MAX_ORDER - 1) {
        buddy_idx = __find_buddy_index(page_idx, order);
        buddy = page + (buddy_idx - page_idx);
        //buddy可能会越界如果有4097个页面,则idx为4096的页面的buddy越界
        if(buddy > (mem_map + PAGE_NUMS - 1))
            break;
        if(!page_is_buddy(buddy, order))
            break;
        list_del_entry(&buddy->page_link);         //buddy从当前链表删除
        free_area[order].nr_free--; //当前链表空闲数减一
        buddy->order = -1;
        buddy->size = 0;
        combined_idx = buddy_idx & page_idx;
        page = page + (combined_idx - page_idx);
        page_idx = combined_idx;
        order++;
	}
	page->order = order;
	page->size = 1 << order;
	list_add(&(page->page_link), &(free_area[order].free_list));
	free_area[order].nr_free++;
}
static void free_pages(struct page *page)
{
    int order = get_order(page->size);
    __free_pages(page, order);
}

static void buddy_free(void *addr)
{
    struct page *page = &mem_map[ADDR_TO_PAGE(addr)];
    free_pages(page);
}

static void *buddy_malloc(unsigned int size)
{
    struct page *page = alloc_pages(size);
    if(page)
        return mem_size + (PAGE_TO_PFN(page) * PAGE_SIZE);
    return NULL;
}

static void init_free_area()
{
    int cur_order = 0;
    for(; cur_order < MAX_ORDER; cur_order++)
        INIT_LIST_HEAD(&(free_area[cur_order].free_list));
}
static void init_pages()
{
    int i;
    for(i = 0; i < PAGE_NUMS; i++) {
        mem_map[i].order = -1;
        mem_map[i].size = 0;
    }
}
static void free_mem_to_buddy()
{
    int i;
    for(i = 0; i < PAGE_NUMS; i++)
        __free_pages(&mem_map[i], 0);
}

static void init_mm()
{
    init_free_area();
    init_pages();
    free_mem_to_buddy();
}
/*
 * 打印空闲链表的状态
 */
static void __dump()
{
    int order;
    for(order = 0; order < MAX_ORDER; order++) {
        struct page *page;
        if(list_empty(&free_area[order].free_list)) {
            printf("order: %d is NULL\n\n", order);
        } else {
            printf("order: %d, nr_free: %d :\n", order, free_area[order].nr_free);
            list_for_each_entry(page, &free_area[order].free_list, page_link)
                printf("pfn: %d, page_addr: 0x%p, mem_addr: 0x%p, size: %d\n\n",PAGE_TO_PFN(page), page, PAGE_TO_ADDR(page), page->size);
        }
    }
}
static void buddy_test()
{
    uint8_t *p1 = buddy_malloc(1);
    assert(free_area[MAX_ORDER - 1].nr_free == 3 && free_area[0].nr_free == 1);
    __dump();
    buddy_free(p1);
    __dump();
    assert(free_area[MAX_ORDER - 1].nr_free == 4 && free_area[0].nr_free == 0);
    uint8_t *p2 = buddy_malloc(1024);
    uint8_t *p3 = buddy_malloc(1024);
    uint8_t *p4 = buddy_malloc(2);
    assert(p2 == p1);
    assert(free_area[MAX_ORDER - 1].nr_free == 1);
    assert(free_area[0].nr_free == 0);
    buddy_free(p2);
    buddy_free(p4);
    uint8_t *p5 = buddy_malloc(1024);
    uint8_t *p6 = buddy_malloc(1);
    assert(p5 == p4);
    assert(p6 == p2);
    buddy_free(p5);
    buddy_free(p6);
    assert(free_area[MAX_ORDER - 1].nr_free == 3);
    buddy_free(p3);
    assert(free_area[MAX_ORDER - 1].nr_free == 4 && free_area[0].nr_free == 0);
    p1 = buddy_malloc(3);
    p2 = buddy_malloc(4);
    assert(p1 + PAGE_SIZE * 4 == p2);
    p3 = buddy_malloc(16);
    assert(p2 + PAGE_SIZE * 12 == p3);
    p4 = buddy_malloc(16);
    buddy_free(p3);
    buddy_free(p4);
    assert(free_area[4].nr_free == 1);
    p3 = buddy_malloc(512);
    p4 = buddy_malloc(1);
    buddy_free(p1);
    p1 = buddy_malloc(1);
    assert(p1 == p4 + PAGE_SIZE);
    buddy_free(p1);
    buddy_free(p3);
    buddy_free(p4);
    buddy_free(p2);
    assert(free_area[MAX_ORDER - 1].nr_free == 4);

}
int main()
{
    init_mm();
    buddy_test();
    return 0;
}




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这个内存分配器需要是非入侵式的,即不在要分配内存块中写 cookie 。 而我的需求中,需要被管理的内存块都是很规则的,成 2 的整数次幂的长度。buddy memory allocation 刚好适用。 算法很简单,就是每次把一个正内存块对半切分,一直切到需要的大小分配出去。回收的时候,如果跟它配对的块也是未被使用的,就合并成一个大的块。标准算法下,分配和释放的时间复杂度都是 O(log N) ,N 不会特别大。算法的优点是碎片率很小。而且很容易做成非入侵式的,不用在被管理的内存上保存 cookie 。只需要额外开辟一个二叉树记录内存使用状态即可。 我吃完饭简单 google 了一下,没有立刻找到满足我要求的现成代码。心里估算了一下,C 代码量应该在 200 行以下,我大概可以在 1 小时内写完。所以就毫不犹豫的实现了一份。 然后,自然是开源了。有兴趣的同学可以去 github 拿一份。这样就省得到再需要时再造轮子了。嘿嘿。 btw, 当然这块代码有许多值得优化的地方,比如可以把里面的递归优化成循环回溯。这个算法我读初中时经常写。因为初一那个时候参加信息学奥赛时用的 basic 不支持局部变量,全部变量都是全局的,很难实现递归。所以早期我都不用递归遍历二叉树的,感觉写起来好麻烦。 不过循环回溯遍历树应该是比递归快不少的,因为减少了许多不必要的环境变量压栈,对不支持 closure 的 C 语言尤其是。 这个库用起来很简单。它并不实际管理内存(它不侵入被管理的内存)。你可以设想你另外有一大块内存是由许多最小单位块合起来的。你可以假设最小单位是 1K 。那么用 buddy_new(10) 就可以帮你管理 1024K 内存。 buddy_alloc 可以请求若干个最小单位块,返回一个序号。然后用户可以自己去大内存上索引出来用。用完调用 buddy_free 归还即可。 为了调试方便,我还提供了 buddy_dump 打印二叉树的细节,可以直观的看出那些内存区域未被使用,哪些已经被占用。 ps. 果然,写这篇 blog 花掉的时间比完成这些代码时间更长。代码也如我所料的没有超过 200 行。看看,把东西描述清楚就是比实现一个东西要花更长的时间,这就是项目人多反而做的慢的原因之一吧。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值