2017-03-02

在Linux下的物理内存管理中,对SLAB机制大致做了介绍,对SLAB管理结构对象也做了介绍,但是对于小内存块的分配没有介绍,本节重点介绍下slab对小内存块的管理。


内核中使用全局的kmem_cache数组kmalloc_caches组织不同大小的缓存块,每个缓存块由一个kmem_cache结构描述,缓存块大小一般是按8字节递增,分配时不足8字节按照8字节算,依次向上舍入。内核有两种方式根据size获取对应的阶

1、使用一个size_index数组保存对应内存块的阶,当size小于192时,使用此数组。

static s8 size_index[] = {
, /* 8 */
, /* 16 */
, /* 24 */
, /* 32 */
, /* 40 */
, /* 48 */
, /* 56 */
, /* 64 */
, /* 72 */
, /* 80 */
, /* 88 */
, /* 96 */
, /* 104 */
, /* 112 */
, /* 120 */
, /* 128 */
, /* 136 */
, /* 144 */
, /* 152 */
, /* 160 */
, /* 168 */
, /* 176 */
, /* 184 */
/* 192 */
};

2,、当size大于192时,使用fls函数。

小内存块的总体分配架构如下图所示:

根据请求分配的内存的size,通过size_index_elem(size)获取该size对应的index,上篇文章介绍过,块的大小按照2的阶计算。kmalloc_caches数组中的下标其实也是表示了块的大小即2^n字节。

下面结合内核kmalloc函数执行流程,分析下具体的小内存块的分配。正常情况下会调用到__kmalloc函数,该函数主体可分为两部分:根据size得到对应的缓存块结构kmem_cache;从缓存块中取对象;

前者使用kmalloc_slab函数,而后者调用slab_alloc函数。从这里我们仔细深入分析:

struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
{
int index;
/*如果size大于最大值,则返回NULL*/ if (size > KMALLOC_MAX_SIZE) {
WARN_ON_ONCE(!(flags & __GFP_NOWARN));
return NULL;
} if (size <= ) {
if (!size)
return ZERO_SIZE_PTR; index = size_index[size_index_elem(size)];
} else
index = fls(size - ); #ifdef CONFIG_ZONE_DMA
if (unlikely((flags & GFP_DMA)))
return kmalloc_dma_caches[index]; #endif
return kmalloc_caches[index];
}

kmalloc_slab代码本身比较简单,先对size做了判断,看是否符合要求,如果size小于等于192,则从size_index数组中获取对应的阶;否则调用fls函数获取对应的阶。最后根据此阶作为下标,获取对应的kmem_cache对象。而内存分配的重点在slab_alloc函数,该函数中直接调用了slab_alloc_node函数

static __always_inline void *
slab_alloc_node(struct kmem_cache *cachep, gfp_t flags, int nodeid,
unsigned long caller)
{
unsigned long save_flags;
void *ptr;
int slab_node = numa_mem_id(); flags &= gfp_allowed_mask; lockdep_trace_alloc(flags); if (slab_should_failslab(cachep, flags))
return NULL; cachep = memcg_kmem_get_cache(cachep, flags); cache_alloc_debugcheck_before(cachep, flags);
local_irq_save(save_flags); if (nodeid == NUMA_NO_NODE)
nodeid = slab_node; if (unlikely(!cachep->node[nodeid])) {
/* Node not bootstrapped yet */
ptr = fallback_alloc(cachep, flags);
goto out;
} if (nodeid == slab_node) {
/*
* Use the locally cached objects if possible.
* However ____cache_alloc does not allow fallback
* to other nodes. It may fail while we still have
* objects on other nodes available.
*/
ptr = ____cache_alloc(cachep, flags);
if (ptr)
goto out;
}
/* ___cache_alloc_node can fall back to other nodes */
ptr = ____cache_alloc_node(cachep, flags, nodeid);
out:
local_irq_restore(save_flags);
ptr = cache_alloc_debugcheck_after(cachep, flags, ptr, caller);
kmemleak_alloc_recursive(ptr, cachep->object_size, , cachep->flags,
flags); if (likely(ptr))
kmemcheck_slab_alloc(cachep, flags, ptr, cachep->object_size); if (unlikely((flags & __GFP_ZERO) && ptr))
memset(ptr, , cachep->object_size); return ptr;
}

函数u首先获取了节点ID即slab_node,如果参数中未指定具体的node,默认从当前节点开始分配。中间是复杂的检查机制,而实质性的工作在____cache_alloc函数中

static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
void *objp;
struct array_cache *ac;
bool force_refill = false; check_irq_off();
/*先从CPU 缓存中取*/
ac = cpu_cache_get(cachep);
/**/
if (likely(ac->avail)) {
ac->touched = ;
objp = ac_get_obj(cachep, ac, flags, false); /*
* Allow for the possibility all avail objects are not allowed
* by the current flags
*/
if (objp) {
STATS_INC_ALLOCHIT(cachep);
goto out;
}
force_refill = true;
} STATS_INC_ALLOCMISS(cachep);
objp = cache_alloc_refill(cachep, flags, force_refill);
/*
* the 'ac' may be updated by cache_alloc_refill(),
* and kmemleak_erase() requires its correct value.
*/
ac = cpu_cache_get(cachep); out:
/*
* To avoid a false negative, if an object that is in one of the
* per-CPU caches is leaked, we need to make sure kmemleak doesn't
* treat the array pointers as a reference to the object.
*/
if (objp)
kmemleak_erase(&ac->entry[ac->avail]);
return objp;
}

可以看到,该函数中首先根据参数中的kmem_cache获取当前CPU对应的array_cache,array_cache结构如下:

struct array_cache {
unsigned int avail;//可用对象的数目
unsigned int limit;//可拥有的最大对象的数目
unsigned int batchcount;//
unsigned int touched;
spinlock_t lock;
void *entry[]; /*主要是为了访问后面的对象
* Must have this definition in here for the proper
* alignment of array_cache. Also simplifies accessing
* the entries.
*
* Entries should not be directly dereferenced as
* entries belonging to slabs marked pfmemalloc will
* have the lower bits set SLAB_OBJ_PFMEMALLOC
*/
};

这个结构per_CPU 缓存,每个CPU都有对应的缓存,其中avail对应当前缓存中可用对象的数目,limit表示可拥有的最大对象的数目,batchcount表示在缓存为空时,需要填充的对象的数量,touched是一个活动位,表示当前缓存的活跃程度,便于后续的收缩操作。entry指向该缓存的对象数组,数组中保存的是对象的地址,这里就表示缓存块的地址。有一点就是从这里分配缓存对象不是从数组起始位置分配,而是从数组末尾分配,avail就表示分配的对象在entry数组中的下标,这里发现设计的还真是巧妙。

回到____cache_alloc函数中,如果当前CPU缓存中有可用对象,则设置首先设置活跃位,然后调用ac_get_obj函数从缓存中获取一个对象。如果没有可用的对象,则调用cache_alloc_refill函数填充缓存,之后再次调用cpu_cache_get获取对象。获取对象之后需要调用kmemleak_erase函数设置entry数组中的对应指针为NULL。到这里发现主要有两个操作,获取对象,填充缓存。

先看获取对象ac_get_obj

static inline void *ac_get_obj(struct kmem_cache *cachep,
struct array_cache *ac, gfp_t flags, bool force_refill)
{
void *objp; if (unlikely(sk_memalloc_socks()))
objp = __ac_get_obj(cachep, ac, flags, force_refill);
else
objp = ac->entry[--ac->avail]; return objp;
}

sk_memalloc_socks意思暂不清楚,从unlikely可以看到这里大部分都可以直接通过entry得到对象,所以获取对象的方式还是挺简单的,直接从根据avail从entry数组中获的一个对象地址即可。并且这里avail应该指向首个为NULL的entry。

如果缓存中没有呢,就需要填充per_CPU 缓存了,这里看cache_alloc_refill函数

static void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags,
bool force_refill)
{
int batchcount;
struct kmem_cache_node *n;
struct array_cache *ac;
int node; check_irq_off();
/*获取当前节点kmem_cache_node*/
node = numa_mem_id();
if (unlikely(force_refill))
goto force_grow;
retry:
ac = cpu_cache_get(cachep);
/*要填充对象的数目*/
batchcount = ac->batchcount;
/*如果当前缓存访问并不频繁,且要填充的数目较多,减少分配的数目*/
if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {
/*
* If there was little recent activity on this cache, then
* perform only a partial refill. Otherwise we could generate
* refill bouncing.
*/
batchcount = BATCHREFILL_LIMIT;
}
/**/
n = cachep->node[node]; BUG_ON(ac->avail > || !n);
/*枷锁*/
spin_lock(&n->list_lock); /* See if we can refill from the shared array */
if (n->shared && transfer_objects(ac, n->shared, batchcount)) {
n->shared->touched = ;
goto alloc_done;
} while (batchcount > ) {
struct list_head *entry;
struct slab *slabp;
/* Get slab alloc is to come from. */
/*首先从slabs_partial链表中分配*/
entry = n->slabs_partial.next;
/*如果半分配的slab链表为空*/
if (entry == &n->slabs_partial) {
n->free_touched = ;
/*从free链表分配*/
entry = n->slabs_free.next;
/*如果free链表满,则增长*/
if (entry == &n->slabs_free)
goto must_grow;
}
/*由entry定位slab的地址*/
slabp = list_entry(entry, struct slab, list);
check_slabp(cachep, slabp);
check_spinlock_acquired(cachep); /*
* The slab was either on partial or free list so
* there must be at least one object available for
* allocation.
*/
BUG_ON(slabp->inuse >= cachep->num);
/*循环从slab获取对象*/
while (slabp->inuse < cachep->num && batchcount--) {
STATS_INC_ALLOCED(cachep);
STATS_INC_ACTIVE(cachep);
STATS_SET_HIGH(cachep);
/*填充缓存*/
ac_put_obj(cachep, ac, slab_get_obj(cachep, slabp,
node));
}
check_slabp(cachep, slabp); /* move slabp to correct slabp list: */
/*把slab移除链表*/
list_del(&slabp->list);
/*根据情况添加到对应的链表*/
if (slabp->free == BUFCTL_END)
list_add(&slabp->list, &n->slabs_full);
else
list_add(&slabp->list, &n->slabs_partial);
} must_grow:
n->free_objects -= ac->avail;
alloc_done:
spin_unlock(&n->list_lock); if (unlikely(!ac->avail)) {
int x;
force_grow:
x = cache_grow(cachep, flags | GFP_THISNODE, node, NULL); /* cache_grow can reenable interrupts, then ac could change. */
ac = cpu_cache_get(cachep);
node = numa_mem_id(); /* no objects in sight? abort */
if (!x && (ac->avail == || force_refill))
return NULL; if (!ac->avail) /* objects refilled by interrupt? */
goto retry;
}
ac->touched = ; return ac_get_obj(cachep, ac, flags, force_refill);
}

代码部分并不难理解,首先获取当前NUMA节点ID,从而在后面获取对应的kmem_cache_node结构,该结构中保存了与其关联的slab,然后获取当前CPU的缓存对象array_cache。根据其中的touched字段判断是否该缓存是否活跃,如果不活跃且要求充填的对象数还比较多,就把填充对象数设置成BATCHREFILL_LIMIT。然后即根据NUMA ID 从kmem_cache结构的node数组中获取kmem_cache_node结构。首先尝试从共享数组中分配,这相当于又添加了一层缓存。如果共享数组为NULL或者从共享数组填充失败,则走正规途径即从slab链表中分配。进入while循环

前面提到过NUMA架构下,每个NODE关联三条SLAB链表,分别表示full,partial,free的slab.这里首先尝试的是从未使用完的链表中分配,如果该链表为NULL,则从free链表中分配,如果free链表也为NULL,则必须must_grow的途径,从伙伴系统申请对象。到这里不管从哪里,已经获取一个slab了,接下来从该slab分配对象,再次进入一个while循环。这里重要的是首先通过slab_get_obj函数从slab中获取一个对象,然后通过ac_put_obj函数把对象填充到CPU关联的缓存数组中。看下两个函数

static void *slab_get_obj(struct kmem_cache *cachep, struct slab *slabp,
int nodeid)
{
void *objp = index_to_obj(cachep, slabp, slabp->free);
kmem_bufctl_t next; slabp->inuse++;
next = slab_bufctl(slabp)[slabp->free];
#if DEBUG
slab_bufctl(slabp)[slabp->free] = BUFCTL_FREE;
WARN_ON(slabp->nodeid != nodeid);
#endif
slabp->free = next; return objp;
}

首先从SLAB中取一个对象,更新计数器,然后获取下一个可用对象的索引,设置到slab的free字段,接着就返回了。

static inline void ac_put_obj(struct kmem_cache *cachep, struct array_cache *ac,
void *objp)
{
if (unlikely(sk_memalloc_socks()))
objp = __ac_put_obj(cachep, ac, objp); ac->entry[ac->avail++] = objp;
}

而put函数就更显简单,直接在对象地址和entry数组建立关联即可。从这里看avail同样是指首个为NULL的索引。这样while循环结束,填充per_CPU 缓存的工作节完成了,接来下要做的就是把SLAB从对应的链表摘下来。如果里面的对象已经用完,则假如到full链表,否则加入到slabs_partial链表。

最后会通过ac_get_obj从当前CPU的缓存entry数组中获取一个对象,返回。而在最终,需要设置entry中分配出去的对象位置为NULL。至此,小缓存块的分配工作就完成了!

总结:

  其实小缓存块的管理和普通对象的管理方式基本类似,都是作为对象存储,不同的是一般对象的大小不确定,而小缓存块的大小由系统初始指定;一般对象的缓存可以自己创建,而小缓存块由系统维护。其余的分配流程、保存方式等都是使用相同的API。

Linux下的物理内存管理2-slab缓存的管理的更多相关文章

  1. Linux下2号进程的kthreadd--Linux进程的管理与调度(七)

    2号进程 内核初始化rest_init函数中,由进程 0 (swapper 进程)创建了两个process init 进程 (pid = 1, ppid = 0) kthreadd (pid = 2, ...

  2. linux下使用svn创建版本库和权限管理

    linux上的svn服务端如何和本地的电脑客户端结合使用 Linux上安装SVN服务器: 第一步:检查是否已安装 # rpm -qa subversion 第二步: 通过yum命令安装svnserve ...

  3. Linux 下定时备份数据库以及删除缓存

    一.定时备份数据库 1.在根目录下创建备份文件夹 #mkdir backup 2.进入到该目录下,创建backup.sh文件 3.赋予文件权限让其变成可执行文件 4.在backup.sh中写备份的脚本 ...

  4. Linux下开发python django程序(设置admin后台管理上传文件和前台上传文件保存数据库)

    1.项目创建相关工作参考前面 2.在models.py文件中定义数据库结构 import django.db import modelsclass RegisterUser(models.Model) ...

  5. Linux下开发python django程序(设置admin后台管理模块)

    1.新建项目和项目下APP django-admin startproject csvt03 django-admin startapp app1 2.修改settings.py文件 设置默认安装AP ...

  6. Linux下C/C++程序开发管理(makefile)

    一.引言          从我们刚开始编写一个简单的C/C++ "Hello,World!",到将其编译.运行处结果—这部分工作IDE(集成开发环境)帮我们做了,包括语法错误检查 ...

  7. Linux下Vim工具常用命令

    原文地址: http://www.cnblogs.com/lizhenghn/p/3675011.html 在linux下做开发,甚至是只做管理维护工作,也少不了Vim的使用.作为一个新手,我也是刚刚 ...

  8. Linux 下提高make的编译效率

    Linux下安装程序,一般都通过包管理器安装,但是包管理器或软件商店里的软件往往不是最新版本的,安装最新版软件时通常是下载源代码进行编译. 编译安装源代码时就离不开make了,但是make是单线程的, ...

  9. 【转载】linux下升级npm以及node

    原文:http://blog.csdn.net/qq_16339527/article/details/73008708 npm升级 废话不多说,直接讲步骤.先从容易的开始,升级npm. npm这款包 ...

随机推荐

  1. 基于jquery仿360网站图片选项卡切换代码

    今天给大家分享一款基于jquery仿360网站图片选项卡切换代码.这款实例适用浏览器:IE8.360.FireFox.Chrome.Safari.Opera.傲游.搜狗.世界之窗.效果图如下: 在线预 ...

  2. opencv 摄像头人脸检测

    PYTHON ubuntu16.04 默认安装的Python版本2.7.12,当用pip install opencv-python 安装了opencv for python 3.3.0.10后,运行 ...

  3. Classification / Recognition

    转载 https://handong1587.github.io/deep_learning/2015/10/09/recognition.html#facenet Classification / ...

  4. 在Swing的组件中,基本上都是在AWT组件的名称前面加“J”

    在Swing的组件中,基本上都是在AWT组件的名称前面加“J”. 一般情况下,除了Choise等组件: import javax.swing.*;好要加上:import java.awt.*以及imp ...

  5. 静态内部类定义在类中,任何方法外,用static定义

    静态内部类:(注意:前三种内部类与变量类似,所以可以对照参考变量) 静态内部类定义在类中,任何方法外,用static定义. 静态内部类只能访问外部类的静态成员. 生成(new)一个静态内部类不需要外部 ...

  6. php -- 四种基础排序:冒泡、选择、插入、快速

    冒泡排序 思路分析:法如其名,就是像冒泡一样,每次从数组当中 冒一个最大的数出来. 第一轮:从第一个到最后一个冒泡比较,运行结果:最后一个最大 第二轮:从第一个到倒数第二个冒泡比较, 运行结果:最后一 ...

  7. Windows下基于eclipse的Spark应用开发环境搭建

    原创文章,转载请注明: 转载自www.cnblogs.com/tovin/p/3822985.html 一.软件下载 maven下载安装 :http://10.100.209.243/share/so ...

  8. Linux心得记录

    2014.4.8 linux环境下如何删除一个目录? rm -r linux本身提供删除目录命令——rmdir,但是如果你要删除的目录中含有子目录或者子文件,那么该命令会提示“删除失败:目录非空“也就 ...

  9. LAMP环境搭建博客

    背景: 公司要用到lamp环境,让我装,我就开始着手找资料,一般分为源码装和yum装,源码装很容易出错,所以我选择了yum装,. 服务器:aliyun服务器  centos6.8系统 按照第一个安装完 ...

  10. C#------引用System.Data.Entity后DbContext依然无法继承解决方法

    解决方法: 需要在“引用”->“管理NuGet程序包”里面添加->“EntityFramework.dll”文件即可