探究Linux内核空间的slab分配模式:理解其原理与实际应用
csdn 2023-12-05

内核在运行时,经常需要在内核空间3G~3G+high_memory这个内存空间申请动态内存,以存放一些结构类型的数据。例如,在创建一个程序时,它就要为该程序控制块task_struct申请一段内存空间;在撤销这个程序时,又要释放这个空间。在内核中,由于这种结构体类型数据的数量相当大,而数据所占的内存空间又不可能刚好是一个或多个页框,所以在以页框为最小分配单位的分配方法里,这种数据产生的碎片就相当多,内存空间浪费比较惊人。

于是,这就促使人们在不破坏页管理机制的条件下,考虑更小的内存分配粒度。所以自从Linux2.2开始,设计者在Linux系统中采用了一个叫做slab的小对象分配模式。

这里区分一下外部碎片和内部碎片:

外部碎片:什么是外部碎片呢?我们通过一个图来解释:

假设这是一段连续的页框,阴影部分表示已经被使用的页框,现在需要申请一个连续的5个页框。这个时候,在这段内存上不能找到连续的5个空闲的页框,就会去另一段内存上去寻找5个连续的页框,这样子,久而久之就形成了页框的浪费。称为外部碎片。内核中使用伙伴算法的迁移机制很好的解决了这种外部碎片。

内部碎片:当我们申请几十个字节的时候,内核也是给我们分配一个页,这样在每个页中就形成了很大的浪费。称之为内部碎片。内核中引入了slab机制去尽力的减少这种内部碎片。


缓冲区和slab的概念

slab模式是20世纪90年代提出的一个为小数据分配内存空间的方法。在设计slab模式时,人们看到:内核的这些小数据虽然量很大,但是种类并不多,于是就提出了这样一个思想:把若干的页框合在一起形成一大存储块——slab,并在这个slab中只存储同一类数据,这样就可以在这个slab内部打破页的界限,以该类型数据的大小来定义分配粒度,存放多个数据,这样就可以尽可能地减少页内碎片了。在Linux中,多个存储同类数据的slab的集合叫做一类对象的缓冲区——cache。注意,这不是硬件的那个cache,只是借用这个名词而已。

采用slab模式的另一个考虑就是:一些内核数据不但需要为其分配内存,而且还经常需要对它们进行一些比较费时的初始化操作。这样,当这些数据在内核运行时频繁地创建和撤销,就消耗了大量的CPU时间。而slab模式恰好就能解决这个问题。

但是目前,Linux中的缓冲区只才在用了前一个功能,并没有使用构造函数和析构函数。

slab分配机制:slab分配器是基于对象进行管理的,所谓的对象就是内核中的数据结构(例如:task_struct、file_struct 等)。相同类型的对象归为一类,每当要申请这样一个对象时,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免外部碎片。

Linux在一个缓冲区中,用链表来管理多个slab,而且把这些slab分成三段:第一段是所有已经被对象充满的slab;第二段是半满的slab;第三段是空闲的slab。slab模式的示意图如下:

也就是说,Linux 的slab 可有三种状态:

  • 满的:slab 中的所有对象被标记为使用;
  • 空的:slab 中的所有对象被标记为空闲;
  • 部分:slab 中的对象有的被标记为使用,有的被标记为空闲。

slab 分配器首先从部分空闲的slab 进行分配。如没有,则从空的slab 进行分配。如没有,则从物理连续页上分配新的slab,并把它赋给一个cache ,然后再从新slab 分配空间。

总之,一个缓冲区是一个特殊的内存区,其中有多个slab,一个slab占用的内存空间必须为整数页并且是连续的,但在slab内部却没有页的概念;而一个slab所占用的页框的数目,则是在以保证产生的页内碎片最小为目标,由负责创建缓冲区的内核函数kmem_cache_create()调用slab分配器经过计算得出的。

在文件mm/slab.c中定义的slab的数据结构如下:

struct slab {
	struct list_head list;        //链表结构
	unsigned long colouroff;        //对象区的起点与slab起点之间的偏移
	void *s_mem;		/* 对象区在slab中的起点 */
	unsigned int inuse;	/* 记录已分配对象空间数目的计数器 */
	kmem_bufctl_t free;        //指向了对象链中的第一个空闲对象
	unsigned short nodeid;
};

同一个缓冲区的slab按对象空间已满、半满和全空顺序组成了链表。为了解slab中有多少对象空间已经被占用,在链表的结构中有成员inuse;为给内核新申请的对象分配空间,在结构中有成员free。

Linux用数据结构kmem_cache_t描述一个缓冲区,并管理该缓冲区中的slab链表。

slab的优点:

  • 内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配。slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,从而避免了常见的碎片问题;
  • slab 分配器还支持通用对象的初始化,从而避免了为同一目的而对一个对象重复进行初始化;
  • slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能。

slab的缺点:

  • 较多复杂的队列管理。在slab分配器中存在众多的队列,例如针对处理器的本地缓存队列,slab中空闲队列,每个slab处于一个特定状态的队列之中。所以,管理太费劲了。
  • slab管理数据和队列的存储开销比较大。每个slab需要一个struct slab数据结构和一个管理者kmem_bufctl_t型的数组。当对象体积较小时,该数组将造成较大的开销(比如对象大小为32字节时,将浪费1/8空间)。同时,缓冲区针对节点和处理器的队列也会浪费不少内存。
  • 缓冲区回收、性能调试调优比较复杂。

slab缓冲区有专用缓冲区和通用缓冲区之分。


专用缓冲区

slab专用缓冲区主要用于内核频繁使用的一些数据结构,例如task_struct、mm_struct、vm_area_struct、file、dentry、inode等。

调用内核函数kmem_cache_create()可以创建一个专用缓冲区。该函数的原型如下:

struct kmem_cache * kmem_cache_create (
            const char *name,             //缓冲区名称
            size_t size,                 //对象大小
            size_t offset,                //偏移量
	        unsigned long c_flags,         //标志
            void (*ctor)(void *objp,...),    //构造函数
            void (*ctor)(void *objp,...))    //析构函数
{
	...
}

前面提到,Linux没有对缓冲区中的对象进行构造和析构,因此在参数中的构造函数和析构函数都被置为NULL。

函数调用成功后,其返回值为创建的缓冲区的指针。

如果要在缓冲区中申请一个对象的控件,则需调用内核函数kmem_chche_alloc()。

一个对象在使用之后,还要调用内核函数kmem_cache_free()将其占有的空间归还缓冲区并由缓冲区来管理。注意,是归还缓冲区,使已经被释放的对象空间成为一个空闲对象空间,而不是归还内存。

当认为某一个缓冲区确实不需要使用了,则需调用内核函数kmem_cache_distory()把缓冲区的控件归还内存。但一般不需要这样做,因为内核的守护程序kswapd会定时对slab中的空闲对象进行必要的回收工作。

这样看来,缓冲区有点像内存的一个特区。


通用缓冲区

对于内核的一些初始化工作量不大的数据结构可以使用通用缓冲区。在Linux中通用缓冲区按可分配对象空间的大小,分为可以容纳32字节大小对象的缓冲区、64字节大小对象的缓冲区……128KB对象大小的缓冲区。自通用缓冲区申请对象空间的内核函数为kmalloc(),而释放对象空间的函数为kfree()。

因为在本文开头提到:在内核空间3G~3G+high_memory这个内存空间分配空间。


Linux内存管理的总貌

分页及页交换技术是实现虚拟内存的基础,页表及MMU是实现虚拟内存的保障。

Linux内存管理的总貌如下图所示:

交换模块swap

这个模块负责页的换入和换出,从物理内存中淘汰最近没有访问的页面,保存近来访问的页面。该模块由以下几个部分组成:

  • page_io.c:读写交换文件的功能函数;
  • swap_state.c:修改高速缓存的函数;
  • swapfile.c:完成换入换出的系统调用;
  • swap.c:定义交换所需要的数据结构和常量;
  • kswapd:一个内核程序,周期性地处理物理内存与虚拟内存之间的交换。

核心内存管理模块core

这个模块负责核心内存的管理,即对页的管理。该模块的管理功能函数可以被核心的其他子系统调用。该模块的功能函数有:

  • page_alloc.c:负责处理页的释放、回收和分配;
  • memory.c:负责申请页面。

结构特定的模块

这个模块是在不同的硬件平台上实现虚拟内存的基础,通过执行命令来改变硬件MMU的虚拟地址映射,在发生页错误时提供公有的方法来通知其他内核子系统。实现该模块的源程序有:

  • mm/fault.c:处理页异常;
  • mm/init.c:内存初始化。


声明: 本文转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们及时删除。(联系我们,邮箱:evan.li@aspencore.com )
0
评论
  • 【7.24 深圳】2025国际AI+IoT生态发展大会/2025全球 MCU及嵌入式技术论坛


  • 相关技术文库
  • 单片机
  • 嵌入式
  • MCU
  • STM
  • 3AT89C51单片机引脚说明及引脚图

    AT89C51是一种带4K字节闪烁可编程可擦除只读存储器的低电压,高性能CMOS8位微处理器,俗称单片机。该器件采用ATMEL高密度非易失存储器制造技术制造,与工业标准的MCS-51指令集和输出管脚相兼容。由于将多功能8位CPU...

    昨天
  • 51单片机对LCD1602液晶的驱动设计

    51单片机——LCD1602 1、1602液晶读写时序 (1)、读状态 RS=L,R/W=H,E=H。(判断忙完毕后释放总线) (2)、读数据 RS=H,R/W=H,E=H。 (3)、写指令 RS=L,R/W=L,D0~D7=指令码,E=高脉冲 (4)、写数据 RS=H,R/W=L,D0~D...

    昨天
  • 单片机串口如何接收不定长数据的?

    我们在使用其他STM32的单片机的时候,会发现有些困难,会发现常用的方法并不能用,在还没有接收完数据的时候,就解决不了。于是,只能用通用的方法来解决了。 这个通用的方法,其实原理和使用IDLE的原理一样:...

    昨天
  • ARM处理器的选型原则

    鉴于ARM微处理器的众多优点,随着国内外嵌入式应用领域的逐步发展,ARM微处理器必然会获得广泛的重视和应用。但是,由于ARM微处理器有多达十几种的内核结构,几十个芯片生产厂家,以及千变万化的内部功能配置组合,...

    前天
  • 有哪些低功耗设计方法?单片机系统低功耗设计要点介绍

    功耗,已经是一个老生常谈的话题了。对于功耗,大家多多少少有所了解。目前,很多产品的宣传里便带有低功耗噱头。为增进大家对功耗的认识,本文将基于两点介绍功耗:1.低功耗主要设计方法,2.单片机系统低功耗设计...

    前天
  • 8位32位MCU如何选择?如何选择合适的MCU?

    MCU,对于普通人而言,是一个高大上的存在。但是,在工业中,MCU确实常见产品。为增进大家对MCU的认识,本文将基于两点介绍MCU:1.8位MCU和32位MCU如何选择?2.如何选择合适的MCU。如果你对MCU具有兴趣,不妨继续往...

    07-09
  • ARM开发:一 ARM微处理器概述

    1.1ARM-Advanced RISC Machines ARM(Advanced RISC Machines),既可以认为是一个公司的名字,也可以认为是对一类微处理器的通称,还可以认为是一种技术的名字。 1991年ARM公司成立于英国剑桥,主要出售芯片设计技术...

    07-08
  • 分析C51单片机的一些误区和注意事项

    简介:常看见初学者要求使用_at_,这是一种谬误,把C当作ASM看待了。在C中变量的定位是编译器的事情,初学者只要定义变量和变量的作 用域,编译器就把一个固定地址给这个变量。怎么取得这个变量的地址?要用指针。 1) C...

    07-08
  • 51单片机几个延时程序

    简介:51单片机几个精确延时程序:在精确延时的计算当中,最容易让人忽略的是计算循环外的那部分延时,在对时间要求不高的场合,这部分对程序不会造成影响. 一. 500ms延时子程序(晶振12MHz,一个机器周期1us.) 程...

    07-08
  • 总结单片机软件抗干扰的几种办法

    简介:在提高硬件系统抗干扰能力的同时,软件抗干扰以其设计灵活、节省硬件资源、可靠性好越来越受到重视。下面以MCS-51单片机系统为例,对微机系统软件抗干扰方法进行研究。 1、软件抗干扰方法的研究 在工程实践中...

    07-08
  • 基于C51单片机实现汽车座椅自动控制系统的软硬件设计

    引言 随着人们生活水平的提高,对汽车座椅的舒适性要求也越来越高,要求对汽车座椅地调节能够更加简单、方便、快捷。目前,汽车座椅位置的调节多采用基于手动调节方式的机械和电动控制两种方式。汽车座椅位置的调节...

    07-02
  • MCS51单片机程序设计时堆栈的计算方法解析

    用C语言进行MCS51系列单片机程序设计是单片机开发和应用的必然趋势。Keil公司的C51编译器支持经典8051和8051派生产品的版本,通称为Cx51。应该说,Cx51是C语言在MCS51单片机上的扩展,既有C语言的共性,又有它自己...

    07-02
下载排行榜
更多
评测报告
更多
广告