首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Linux内核内存管理与漏洞利用

Linux内核内存管理与漏洞利用

原创
作者头像
用户8639654
修改于 2021-08-31 02:43:08
修改于 2021-08-31 02:43:08
2.5K00
代码可运行
举报
文章被收录于专栏:云计算运维云计算运维
运行总次数:0
代码可运行

前言

网上已经有很多关于Linux内核内存管理的分析和介绍了,但是不影响我再写一篇:一方面是作为其他文章的补充,另一方面则是自己学习的记录、总结和沉淀。

伙伴系统

伙伴系统即Buddy System,是一种简单高效的内存分配策略。其主要思想是将大块内存按照一定策略去不断拆分(在到达最小的块之前),直至存在满足指定请求大小的最小块。其中块的大小由其相对根块的位置指定,通常称为order(阶)。一个最简单的拆分方式就是以2为指数进行拆分,例如定义最小块的大小为64K,order上限为4,则最大块的大小为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
64K * 2^4 = 1024K

最大块的order为4,最小块的order为0。对于请求大小为k的块,最小块为N,则其order值为align(k)/N。为什么叫buddy system呢?假设一个大块A所分解成的两个小块B和C,其中B和C就相互为彼此的 天使 buddy。只有彼此的buddy才能够进行合并。

使用Buddy算法的的应用有很多,其中Linux内核就是一个,此外jemalloc也是使用Buddy技术的一个现代内存分配器。

Linux内核中的伙伴系统块大小为一页,通常是4096字节。最大的order一般是10,即MAX_ORDER为11。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 /* Free memory management - zoned buddy allocator.  */
  #ifndef CONFIG_FORCE_MAX_ZONEORDER
  #define MAX_ORDER 11
  #else
  #define MAX_ORDER CONFIG_FORCE_MAX_ZONEORDER
  #endif
  #define MAX_ORDER_NR_PAGES (1 << (MAX_ORDER - 1))

在Linux内核中,分配和释放较大内存都是直接通过伙伴系统,即:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);
void free_pages(unsigned long addr, unsigned int order);

order为0-10,指定从哪一阶伙伴中分配,order为n则分配2^n页大小的内存。操作系统中可以通过procfs查看伙伴系统的分配情况,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ cat /proc/buddyinfo
Node 0, zone      DMA      1      1      0      0      2      1      1      0      1      1      3
Node 0, zone    DMA32   3416   4852   3098   3205   3209   3029     33     22     15      7     52
Node 0, zone   Normal  29330 192053 148293  90568  33732   9018   2688    411    942    999   1852
Node 1, zone   Normal    107   1229  18644  76650  46053   8383   4398   5486   1751    497     84

此外,还有/proc/pagetypeinfo也可用于查看内存页的信息。

Slab分配器

上面说到,由于效率原因,伙伴系统中分配内存是以页为单位的,即使所分配的object大小为1byte,也需要分配一页,这样就导致了比较大的内存碎片。因此Linux引入了Slab分配器,加速对object的分配和释放速度,同时也减少碎片空间。

最初接触的时候心里通常有个大大的问号:Slab是什么?理解这个问题至关重要,经过翻阅多种资料和文章,可以大概这么回答:

1.Slab是一种缓存策略

2.Slab是一片缓冲区

3.Slab是一个或者多个连续的page

在内核代码中,我们能看到SLAB、SLOB、SLUB,其实都是兼容SLAB接口的具体分配器

说句题外话,SLOB (Simple List Of Blocks) 可以看做是针对嵌入式设备优化的分配器,通常只需要几MB的内存。其采用了非常简单的first-fit算法来寻找合适的内存block。这种实现虽然去除了几乎所有的额外开销,但也因此会产生额外的内存碎片,因此一般只用于内存极度受限的场景。

数据结构

在本文中,我会尽量少粘贴大段的代码进行分析,但Slub分配器是比较依赖于实现而不是设计的,因此数据结构的介绍是难免的。

page

描述一个页的数据结构就是struct page。为了节约空间,page使用了大量的union结构,针对不同用处的页使用不同的字段。

Slab是一个或者多个连续页组成的内存空间,那么本质上指向一个Slab的数据结构不是别的,就是struct page *,对应Slab中的信息可以通过第一个page的某些字段描述。记住这点对后面的理解很重要。

【文章福利】【Linux内核内存管理专题训练营】火热开营!!

最新Linux内核技术详解

独家Linux内核内存管理干货分享

入营地址:inux内核内存管理专题训练营

两天持续技术输出: -------------------- 第一天: 1.物理内存映射及空间划分 2.ARM32/64页表的映射过程 3.分配物理页面及Slab分配器 4.实战:VMA查找/插入/合并 -------------------- 第二天: 5.实战:mallocap系统调用实现 6.缺页中断处理/反向映射 7.回收页面/匿名页面生命周期 8.KSM实现/Dirty COW内存漏洞

kmem_cache

kmem_cache是Slab的主要管理结构,申请和释放对象都需要经过该结构操作,部分重要字段如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 /*
   * Slab cache management.
   */
  struct kmem_cache {
      struct kmem_cache_cpu __percpu *cpu_slab;
      /* Used for retriving partial slabs etc */
      unsigned long flags;
      unsigned long min_partial;
      int size;       /* The size of an object including meta data */
      int object_size;    /* The size of an object without meta data */
      int offset;     /* Free pointer offset. */
  #ifdef CONFIG_SLUB_CPU_PARTIAL
      int cpu_partial;    /* Number of per cpu partial objects to keep around */
  #endif
      ...
      struct kmem_cache_node *node[MAX_NUMNODES];
}

重点关注cpu_slabnode

cpu_slab包含当前CPU的Slab。这是个__percpu的对象,什么意思呢?我的理解是内核为了加速当前CPU的访问,会对每个CPU保存一个变量,这样在当前CPU访问该变量时候就可以免去加锁的开销。在调试中发现该变量的值是个类似0x18940这样比较小的数,这个地址是没有映射的,访问percpu变量需要通过raw_cpu_ptr宏去获取实际的地址。

node数组中包括其他CPU的Slab。为什么叫做node?其实这是NUMA系统中的node概念。NUMA是为了多核优化而产生的架构,可以令某个CPU访问某段内存的速度更快。node的定义是“一段内存,其中每个字节到CPU的距离相等”,更加通俗的解释是:“在同一物理总线上的内存+CPUs+IO+……”,

kmem_cache_cpu

cpu_slab是kmem_cache_cpu结构,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 struct kmem_cache_cpu {
      void **freelist;    /* Pointer to next available object */
      unsigned long tid;  /* Globally unique transaction id */
      struct page *page;  /* The slab from which we are allocating */
  #ifdef CONFIG_SLUB_CPU_PARTIAL
      struct page *partial;   /* Partially allocated frozen slabs */
  #endif
  #ifdef CONFIG_SLUB_STATS
      unsigned stat[NR_SLUB_STAT_ITEMS];
  #endif
  };

freelist指向第一个空闲的对象(假设为x),page指向x所在slab(的第一页)。这里的page有以下特点:

  • objects = slab的对象数
  • inuse = objects
  • frozen = 1
  • freelist = NULL

partial主要包含本地部分分配的slab。partial指向的page有以下特点:

  • next指向下一个slab(page)
  • freelist指向slab中第一个空闲object
  • inuse = slab中已使用的object个数
  • frozen = 1

其中第一个page的pbojects记录了partial objects数。

kmem_cache_node

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct kmem_cache_node {
    spinlock_t list_lock;
    ...
#ifdef CONFIG_SLUB
    unsigned long nr_partial;
    struct list_head partial;
    ..
#endif
};

这个数据结构根据配置的SL[OAU]B分配器而异,对于SLUB而言,使用的字段就只有两个,nr_partial和partial。其中partial是Linux内核中可插拔式通用双链表结构,使用内核中双链表的接口进行操作。nr_partial表示partial双链表中的元素个数,即slab的个数。

partial->next指向的page结构,用于该结构的page有如下特点:

  • frozon = 0
  • freelist指向slab中第一个空闲object
  • inuse表示对应slab使用中的object个数
  • 通过lru字段索引链表中的下一个/前一个page

前三点没什么好说的,大家都差不多。需要关注的是第四点,这里不像cpu partial那样通过next指针连接页表,而是通过lru字段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct page {
...
      /*
       * Third double word block
       *
       * WARNING: bit 0 of the first word encode PageTail(). That means
       * the rest users of the storage space MUST NOT use the bit to
       * avoid collision and false-positive PageTail().
       */
      union {
          struct list_head lru;   /* Pageout list, eg. active_list
                       * protected by zone_lru_lock !
                       * Can be used as a generic list
                       * by the page owner.
                       */
     ...

分配和释放

终于讲到了重点。关于slub的分配和释放有很多文章介绍过,而且风格不同,有的是对着代码逐行分析,有的是画图介绍,这里我仅按照我自己的理解去说,如有谬误欢迎指出。

对象的分配和释放涉及到几个指针,分别是:

  • p1: 对象的虚拟地址(void *)
  • p2: 对象地址所对应的page(struct page*)
  • p3: 对象所属的slab(struct page*)
  • p4: 对象所属的cache控制体(struct kmem_cache*)

一个虚地址所对应的页首地址是是通过PAGE_MASK,因为页是对齐的,但需要注意页首地址并不是page指针所指向的地方。p1->p2的转换通过virt_to_page实现。

p2->p4可以通过page->slab_cache得到,这也是p1->p4函数virt_to_cache的操作。

分配

对象的分配,不考虑特殊情况的话(比如超过N页的对象直接通过伙伴系统分配),一般流程如下:

1.kmem_cache_cpu->freelist不为空,直接出链返回;

2.kmem_cache_cpu->page->freelist不为空,则出链,更新cpu_slab->freelist,然后返回;

3.kmem_cache_cpu->partial不为空,取出第一个slab,更新cpu_slab的freelist和page,取出对象然后返回;

4.kmem_cache_node->partial不为空,取出第一个,类似3更新cpu_slab的freelist和page并返回;

5.上面都是空的,则通过伙伴系统分配新的slab,挂到kmem_cache_cpu中,然后goto 1;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Linux内核的内存管理与漏洞利用案例分析
本文主要介绍Buddy System、Slab Allocator的实现机制以及现实中的一些漏洞利用方法,从攻击者角度加深对Linux内核内存管理机制的理解。
evilpan
2023/02/12
1.3K0
Linux内核的内存管理与漏洞利用案例分析
从内核源码看 slab 内存池的创建初始化流程
在上篇文章 《细节拉满,80 张图带你一步一步推演 slab 内存池的设计与实现 》中,笔者从 slab cache 的总体架构演进角度以及 slab cache 的运行原理角度为大家勾勒出了 slab cache 的总体架构视图,基于这个视图详细阐述了 slab cache 的内存分配以及释放原理。
bin的技术小屋
2023/10/30
8200
从内核源码看 slab 内存池的创建初始化流程
深入理解 slab cache 内存分配全链路实现
在经过上篇文章 《从内核源码看 slab 内存池的创建初始化流程》 的介绍之后,我们最终得到下面这幅 slab cache 的完整架构图:
bin的技术小屋
2023/10/30
5710
深入理解 slab cache 内存分配全链路实现
SLUB分配一个object的流程分析
在上一节 我们清晰的知道了当调用kmem_cache_create之后系统会为我们分配一个名为slub_test的一个slab。这时候只是分配了kmem_cache,kmem_cache_cpu,kmem_cache_node结构,同时设置针对此object需要多少个page之类。
DragonKingZhu
2020/04/30
1.6K0
SLUB分配一个object的流程分析
Slub分配器的来龙去脉
slab分配器设计的需求 在Linux内核的内存子系统中,伙伴系统无疑处于内存管理的核心地带,但是如果将内存管理从逻辑上分层,它的位置则处于最底层。Buddy是所有物理内存的管家,不论使用何种接口申请内存都要经由伙伴系统进行分配。但是,伙伴系统管理的物理内存是以页为单位,以4K页为例,它也包含了4096个字节。但是无论是内核自己还是用户程序,在日常的使用中都很少会需要使用四千多字节大小的内存。试想如果我们仅需要为10个字符的字符串分配内存,但是伙伴系统却给了我们一页,那这一页剩余没有使用的内存就浪费了,而且
刘盼
2022/07/18
1.2K0
Slub分配器的来龙去脉
linux内存源码分析 - SLAB分配器概述
之前说了管理区页框分配器,这里我们简称为页框分配器,在页框分配器中主要是管理物理内存,将物理内存的页框分配给申请者,而且我们知道也可页框大小为4K(也可设置为4M),这时候就会有个问题,如果我只需要1KB大小的内存,页框分配器也不得不分配一个4KB的页框给申请者,这样就会有3KB被白白浪费掉了。为了应对这种情况,在页框分配器上一层又做了一层SLAB层,SLAB分配器的作用就是从页框分配器中拿出一些页框,专门把这些页框拆分成一小块一小块的小内存,当申请者申请的是小内存时,系统就会从SLAB中获取一小块分配给
233333
2018/07/04
2.1K0
图解slub
在Linux中,伙伴系统(buddy system)是以页为单位管理和分配内存。但是现实的需求却以字节为单位,假如我们需要申请20Bytes,总不能分配一页吧!那岂不是严重浪费内存。那么该如何分配呢?slab分配器就应运而生了,专为小内存分配而生。slab分配器分配内存以Byte为单位。但是slab分配器并没有脱离伙伴系统,而是基于伙伴系统分配的大内存进一步细分成小内存分配。
233333
2019/05/25
1.3K0
深度解析 slab 内存池回收内存以及销毁全流程
在上篇文章 《深入理解 slab cache 内存分配全链路实现》 中,笔者详细地为大家介绍了 slab cache 进行内存分配的整个链路实现,本文我们就来到了 slab cache 最后的一部分内容了,当申请的内存使用完毕之后,下面就该释放内存了。
bin的技术小屋
2023/10/30
5800
深度解析 slab 内存池回收内存以及销毁全流程
Linux slab分配器
在Linux中,伙伴系统是以页为单位分配内存。但是现实中很多时候却以字节为单位,不然申请10Bytes内存还要给1页的话就太浪费了。slab分配器就是为小内存分配而生的。slab分配器分配内存以Byte为单位。但是slab分配器并没有脱离伙伴系统,而是基于伙伴系统分配的大内存进一步细分成小内存分配。
刘盼
2021/04/13
2.2K0
Linux slab分配器
SLUB结构体创建及创建slab分析
在上一篇文章中我们通过一个简单的例子大概描述了如何创建SLUB缓存,如何分配一个object。本文详细描述下涉及的结构体,从结构体的描述中就可以大概理解slub的工作原理了。
DragonKingZhu
2020/04/30
1.9K1
SLUB结构体创建及创建slab分析
Linux内核内存管理算法Buddy和Slab
有了前两节的学习相信读者已经知道CPU所有的操作都是建立在虚拟地址上处理(这里的虚拟地址分为内核态虚拟地址和用户态虚拟地址),CPU看到的内存管理都是对page的管理,接下来我们看一下用来管理page
刘盼
2018/04/24
4K0
Linux内核内存管理算法Buddy和Slab
说出来你可能不信,内核这家伙在内存的使用上给自己开了个小灶!
现在你可能还觉得node、zone、伙伴系统、slab这些东东还有那么一点点陌生。别怕,接下来我们结合动手观察,把它们逐个来展开细说。(下面的讨论都基于Linux 3.10.0版本)
开发内功修炼
2022/03/28
5880
说出来你可能不信,内核这家伙在内存的使用上给自己开了个小灶!
Linux-3.14.12内存管理笔记【构建内存管理框架(5)】
前面已经分析了内存管理框架的构建实现过程,有部分内容未完全呈现出来,这里主要做个补充。
233333
2019/10/08
6810
Linux-3.14.12内存管理笔记【构建内存管理框架(5)】
Linux内存管理
谈到内存管理,最先想到的就是分段和分页机制。计算机刚出现的时候,并没有这些,刚开始是直接使用的物理地址,也就是代码中操作的地址是可以直接和物理地址对应上的,可是后来随着多进程调度的需求,以及有限的物理内存,于是人们就开始做规定,比如对于一块内存,某个范围是属于内核,然后另外一个范围属于进程A,再另外一个范围属于进程B,如下图所示
一只小虾米
2023/03/19
14.3K0
Linux内存管理
SLUB的引入及举例说明
我们都知道Buddy分配器是按照页的单位分配的(Buddy系统分配器实现),如果我们需要分配几十个字节,几百个字节的时候,就需要用到SLAB分配器。
DragonKingZhu
2020/04/30
1.6K0
SLUB的引入及举例说明
linux内核分析———SLAB原理及实现
注:SLAB,SLOB,SLUB都是内核提供的分配器,其前端接口都是一致的,其中SLAB是通用的分配器,SLOB针对微小的嵌入式系统,其算法较为简单(最先适配算法),SLUB是面向配备大量物理内存的大规模并行系统,通过也描述符中未使用的字段来管理页组,降低SLUB本身数据结构的内存开销。
233333
2020/03/18
3.5K0
页框分配器【转】
也就是我们实际中编码时遇到的内存地址并不是对应于实际内存上的地址,我们编码中使用的地址是一个逻辑地址,会通过分段和分页这两个机制把它转为物理地址。而由于linux使用的分段机制有限,可以认为,linux下的逻辑地址=线性地址。也就是,我们编码使用的是线性地址,之后只需要经过一个分页机制就可以把这个地址转为物理地址了。所以我们更重要的可能是去说明一下linux的分页模型。
233333
2019/05/25
7320
Kmalloc申请内存源码分析
再上一节了解了SLUB是如何申请一个object的,其中涉及了从当前的freelist申请,以及kmem_cache_cpu->partital链表申请,以及到最后的kmem_cache_cpu→node中申请,如果上述三个步骤都没有申请到的话,就会重新创建一个新的slab,然后设置好freelist的指针,返回object使用。
DragonKingZhu
2020/04/30
2.2K0
Linux内存描述之内存页面page--Linux内存管理(四)
分页单元可以实现把线性地址转换为物理地址, 为了效率起见, 线性地址被分为固定长度为单位的组, 称为”页”, 页内部的线性地址被映射到连续的物理地址. 这样内核可以指定一个页的物理地址和其存储权限, 而不用指定页所包含的全部线性地址的存储权限.
233333
2018/12/19
9.3K0
linux内存管理slab算法之slab初始化
业余时间写的玩具操作系统,准备把内存管理部分加强一下,伙伴系统分配页面算法已经完成,下面就要开始写更加细粒度的内存分配,毕竟伙伴系统是按照页为基本单位分配的,参考内核版本linux2.6.30,没分析高版本的源码,算法基本思想应该差不多。
用户4415180
2022/06/23
1.1K0
相关推荐
Linux内核的内存管理与漏洞利用案例分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档