Linux中伙伴算法的源码分析

伙伴系统

1.前言

Linux内核内存管理的一项重要工作就是如何在频繁申请释放内存的情况下,避免碎片的产生。Linux采用伙伴系统解决外部碎片的问题,采用slab解决内部碎片的问题,在这里我们先讨论外部碎片问题。避免外部碎片的方法有两种:一种是利用非连续内存的分配;另外一种则是用一种有效的方法来监视内存,保证在内核只要申请一小块内存的情况下,不会从大块的连续空闲内存中截取一段过来,从而保证了大块内存的连续性和完整性。显然,前者不能成为解决问题的普遍方法,一来用来映射非连续内存线性地址空间有限,二来每次映射都要改写内核的页表,进而就要刷新TLB,这使得分配的速度大打折扣,这对于要频繁申请内存的内核显然是无法忍受的。因此Linux采用后者来解决外部碎片的问题,也就是著名的伙伴系统。

2.伙伴系统

伙伴算法的分配原理

大小相同,物理地址连续的两个两个页框就被称为伙伴。Linux的伙伴算法把所有的空闲页面分成多个块链表(链表个数默认为10个),每个链表中的一个块含有2的幂次个页面,即页块或简称块。每个链表中的页块的大小是不一样的。如果分配阶为n的页框块,那么先从第n条页框块链表中查找是否存在这么大小的空闲页块。如果有则分配,否则在第n+1条链表中继续查找,直到找到为止。
伙伴系统的宗旨就是用最小的内存块来满足内核的对于内存的请求。在最初,只有一个块,也就是整个内存,假如为1M大小,而允许的最小块为64K,那么当我们申请一块200K大小的内存时,就要先将1M的块分裂成两等分,各为512K,这两分之间的关系就称为伙伴,然后再将第一个512K的内存块分裂成两等分,各位256K,将第一个256K的内存块分配给内存,这样就是一个分配的过程。

在这里插入图片描述

3.数据结构

zone

struct zone 定义在 inlcude/linux/mmzone.h

struct zone {
   
	/* Fields commonly accessed by the page allocator */

/* zone watermarks, access with *_wmark_pages(zone) macros */
unsigned long watermark[NR_WMARK];

 /*

  * 每个 zone 在系统启动时会计算出 3 个水位值, 分别为 WMAKR_MIN, WMARK_LOW, WMARK_HIGH 水位, 这在
    * 页面分配器和 kswapd 页面回收中会用到
      */
      /*
     * When free pages are below this point, additional steps are taken
     * when reading the number of free pages to avoid per-cpu counter
     * drift allowing watermarks to be breached
       */
       unsigned long percpu_drift_mark;

/*

 * We don't know if the memory that we're going to allocate will be freeable
 * or/and it will be released eventually, so to avoid totally wasting several
 * GB of ram we must reserve some of the lower zone memory (otherwise we risk
 * to run OOM on the lower zones despite there's tons of freeable ram
 * on the higher zones). This array is recalculated at runtime if the
 * sysctl_lowmem_reserve_ratio sysctl changes.
   */
   unsigned long		lowmem_reserve[MAX_NR_ZONES];

 //zone 中预留的内存, 为了防止一些代码必须运行在低地址区域,所以事先保留一些低地址区域的内存

#ifdef CONFIG_NUMA
	int node;
	/*

  * zone reclaim becomes active if more unmapped pages exist.
    */
    unsigned long		min_unmapped_pages;
    unsigned long		min_slab_pages;
    #endif
    struct per_cpu_pageset __percpu *pageset;
      /*
     * page管理的数据结构对象,内部有一个page的列表(list)来管理。每个CPU维护一个page list,避免
     * 自旋锁的冲突。这个数组的大小和NR_CPUS(CPU的数量)有关,这个值是编译的时候确定的
       */
       /*
     * free areas of different sizes
       */
       spinlock_t		lock;
        //对zone并发访问的保护的自旋锁
       int                     all_unreclaimable; /* All pages pinned */
       #ifdef CONFIG_MEMORY_HOTPLUG
       /* see spanned/present_pages for more description */
       seqlock_t		span_seqlock;
       #
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux内核采用了伙伴算法来管理内存。伙伴算法是一种二叉树算法,它将可用内存块组织成一个二叉树,每个节点表示一块内存区域。当一个内存块被分配时,它会被划分成两个等大小的块,其一个块被分配给请求者,另一个块成为伙伴块。如果伙伴块也是空闲的,那么这两个伙伴块会被合并成一个更大的块,这个过程会一直持续到不能再合并为止。 伙伴算法的实现主要包括以下几个步骤: 1. 初始化内存池,将整个可用内存块作为一棵二叉树。 2. 当有内存请求时,从根节点开始遍历二叉树,找到第一个大小合适的空闲块。 3. 如果找到的空闲块比请求的内存大,就将它划分成两个等大小的块,并将其一个块分配给请求者,另一个块成为伙伴块。 4. 如果伙伴块也是空闲的,那么这两个伙伴块会被合并成一个更大的块,这个过程会一直持续到不能再合并为止。 5. 当内存释放时,将该内存块标记为空闲状态,并检查它的伙伴块是否也为空闲状态,如果是,则将这两个伙伴块合并成一个更大的块。 下面是一个简单的伙伴算法的实现示例: ```c struct buddy_node { int size; // 内存块大小 int used; // 是否已被使用 struct buddy_node *next; // 指向下一个空闲块 }; struct buddy_node *buddy_pool; // 内存池 int pool_size; // 内存池大小 void buddy_init(int size) { pool_size = size; buddy_pool = (struct buddy_node *)malloc(sizeof(struct buddy_node) * pool_size); buddy_pool[0].size = pool_size; buddy_pool[0].used = 0; buddy_pool[0].next = NULL; } int buddy_alloc(int size) { int node_size = 1; while (node_size < size) { node_size <<= 1; } for (int i = 0; i < pool_size; i++) { if (buddy_pool[i].size == node_size && !buddy_pool[i].used) { buddy_pool[i].used = 1; return i; } } for (int i = 0; i < pool_size; i++) { if (buddy_pool[i].size > node_size && !buddy_pool[i].used) { int left = i; int right = i + node_size; buddy_pool[left].used = 1; buddy_pool[right].size = buddy_pool[left].size - node_size; buddy_pool[right].used = 0; buddy_pool[right].next = buddy_pool[left].next; buddy_pool[left].next = &buddy_pool[right]; buddy_pool[left].size = node_size; return left; } } return -1; } void buddy_free(int index) { buddy_pool[index].used = 0; struct buddy_node *buddy = &buddy_pool[index ^ buddy_pool[index].size]; while (buddy->next != &buddy_pool[index]) { buddy = buddy->next; } buddy->next = buddy_pool[index].next; buddy_pool[index ^ buddy_pool[index].size].size += buddy_pool[index].size; buddy_pool[index ^ buddy_pool[index].size].used = 0; buddy_pool[index].next = NULL; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值