kmem 反编译linux内核

编程入门 行业动态 更新时间:2024-10-28 19:19:59

kmem 反编译linux<a href=https://www.elefans.com/category/jswz/34/1769575.html style=内核"/>

kmem 反编译linux内核

原标题:说出来你可能不信,内核这家伙在内存的使用上给自己开了个小灶!

来源 |开发内功修炼

作者 |张彦飞allen

现在你可能还觉得 node、zone、伙伴系统、slab 这些东东还有那么一点点陌生。别怕, 接下来我们结合动手观察,把它们逐个来展开细说。(下面的讨论都基于 Linux 3.10.0 版本)

一、NODE 划分

在现代的服务器上,内存和 CPU 都是所谓的 NUMA 架构。

CPU 往往不止是一颗。通过 dmidecode 命令看到你主板上插着的 CPU 的详细信息。

Processor Information //第一颗CPU

SocketDesignation: CPU1

Version: Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz

Core Count: 8

Thread Count: 16

Processor Information //第二颗CPU

Socket Designation: CPU2

Version: Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz

Core Count: 8

内存也不只是一条。dmidecode 同样可以查看到服务器上插着的所有内存条,也可以看到它是和哪个 CPU 直接连接的。

//CPU1 上总共插着四条内存

Memory Device

Size: 16384 MB

Locator: CPU1 DIMM A1

Memory Device

Size: 16384 MB

Locator: CPU1 DIMM A2

......

//CPU2 上也插着四条

Memory Device

Size: 16384 MB

Locator: CPU2 DIMM E1

Memory Device

Size: 16384 MB

Locator: CPU2 DIMM F1

......

每一个 CPU 以及和它直连的内存条组成了一个 node(节点)。

在你的机器上,使用 numactl 你可以看到每个 node 的情况。

numactl --hardware

available: 2 nodes (0-1)

node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23

node 0 size: 65419 MB

node 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31

node 1 size: 65536 MB

二、ZONE 划分

每个 node 又会划分成若干的 zone(区域)。zone 表示内存中的一块范围。

ZONE_DMA:地址段最低的一块内存区域,ISA(Industry Standard Architecture)设备 DMA 访问。

ZONE_DMA32:该 Zone 用于支持 32-bits 地址总线的 DMA 设备,只在 64-bits 系统里才有效。

ZONE_NORMAL:在 X86-64 架构下,DMA 和 DMA32 之外的内存全部在 NORMAL 的 Zone 里管理。

为什么没有提 ZONE_HIGHMEM 这个 zone?因为这是 32 位机时代的产物。现在应该没谁在用这种古董了吧。

在每个 zone 下,都包含了许许多多个 Page(页面), 在 linux 下一个 Page 的大小一般是 4 KB。

在你的机器上,你可以通过 使用zoneinfo 查看到你机器上 zone 的划分,也可以看到每个 zone 下所管理的页面有多少个。

# cat /proc/zoneinfo

Node 0, zone DMA

pages free 3973

managed 3973

Node 0, zone DMA32

pages free 390390

managed 427659

Node 0, zone Normal

pages free 15021616

managed 15990165

Node 1, zone Normal

pages free 16012823

managed 16514393

三、基于伙伴系统管理空闲页面

每个 zone 下面都有如此之多的页面,Linux 使用 伙伴系统对这些页面进行高效的管理。在内核中,表示 zone 的数据结构是 struct zone 。其下面的一个数组 free_area 管理了绝大部分可用的空闲页面。这个数组就是 伙伴系统实现的重要数据结构。

//file: include/linux/mmzone.h

# defineMAX_ORDER 11

structzone{

free_area free_area[MAX_ORDER];

......

}

free_area 是一个 11 个元素的数组,在每一个数组分别代表的是空闲可分配连续 4K、8K、16K、......、4M 内存链表。

通过 cat /proc/pagetypeinfo ,你可以看到当前系统的伙伴系统里各个尺寸的可用连续内存块数量。

内核提供分配器函数 alloc_pages 到上面的多个链表中寻找可用连续页面。

struct page * alloc_pages( gfp_tgfp_mask, unsignedintorder)

alloc_pages 是怎么工作的呢?我们举个简单的小例子。假如要申请 8K-连续两个页框的内存。为了描述方便,我们先暂时忽略 UNMOVEABLE、RELCLAIMABLE 等不同类型。

伙伴系统中的伙伴指的是两个内存块,大小相同,地址连续,同属于一个大块区域。

基于伙伴系统的内存分配中,有可能需要将大块内存拆分成两个小伙伴。在释放中,可能会将两个小伙伴合并再次组成更大块的连续内存。

四、SLAB 管理器

说到现在,不知道你注意到没有。目前我们介绍的内存分配都是 以页面(4KB)为单位的。

对于各个内核运行中实际使用的对象来说,多大的对象都有。有的对象有 1K 多,但有的对象只有几百、甚至几十个字节。如果都直接分配一个 4K 的页面来存储的话也太败家了,所以伙伴系统并不能直接使用。

在伙伴系统之上, 内核又给自己搞了一个专用的内存分配器, 叫 slab 或 slub。这两个词老混用,为了省事,接下来我们就统一叫 slab 吧。

这个分配器最大的特点就是,一个 slab 内只分配特定大小、甚至是特定的对象。这样当一个对象释放内存后,另一个同类对象可以直接使用这块内存。通过这种办法极大地降低了碎片发生的几率。

slab 相关的内核对象定义如下:

//file: include/linux/slab_def.h

structkmem_cache{

structkmem_cache_node** node

......

}

// file: mm/slab.h

structkmem_cache_node{

structlist_headslabs_partial;

structlist_headslabs_full;

structlist_headslabs_free;

......

}

每个 cache 都有满、半满、空三个链表。每个链表节点都对应一个 slab,一个 slab 由 1 个或者多个内存页组成。

在每一个 slab 内都保存的是同等大小的对象。一个 cache 的组成示意图如下:

当 cache 中内存不够的时候,会调用基于伙伴系统的分配器(__alloc_pages函数)请求整页连续内存的分配。

//file: mm/slab.c

staticvoid* kmem_getpages(struct kmem_cache *cachep,

gfp_tflags, intnodeid)

{

......

flags |= cachep->allocflags;

if(cachep->flags & SLAB_RECLAIM_ACCOUNT)

flags |= __GFP_RECLAIMABLE;

page = alloc_pages_exact_node(nodeid, ...);

......

}

//file: include/linux/gfp.h

staticinlinestruct page * alloc_pages_exact_node( intnid,

gfp_tgfp_mask, unsignedintorder)

{

return__alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));

}

内核中会有很多个 kmem_cache 存在。它们是在 linux 初始化或者是运行的过程中分配出来的。它们有的是专用的,有的是通用的。

上图中,我们看到 socket_alloc 内核对象都存在 TCP 的专用 kmem_cache 中。

通过查看 /proc/slabinfo ,我们可以查看到所有的 kmem cache。

另外 linux 还提供了一个特别方便的命令 slabtop 来按照占用内存从大往小进行排列。这个命令用来分析 slab 内存开销非常的方便。

无论是 /proc/slabinfo ,还是 slabtop 命令的输出,里面都包含了每个 cache 中 slab 的如下两个关键信息:

objsize:每个对象的大小;

objperslab:一个 slab 里存放的对象的数量。

在 /proc/slabinfo 还多输出了一个 pagesperslab。展示了一个 slab 占用的页面的数量,每个页面 4K,这样也就能算出每个 slab 占用的内存大小。

最后,slab 管理器组件提供了若干接口函数,方便自己使用。举三个例子:

kmem_cache_create:方便地创建一个基于 slab 的内核对象管理器。

kmem_cache_alloc:快速为某个对象申请内存。

kmem_cache_free:归还对象占用的内存给 slab 管理器。

在内核的源码中,可以见到 大量kmem_cache 开头函数的使用。

总结

通过上面描述的几个步骤,内核高效地把内存用了起来。

前三步是基础模块,为应用程序分配内存时的请求调页组件也能够用到。但第四步,就算是内核的小灶了。内核根据自己的使用场景,量身打造的一套自用的高效内存分配管理机制。

# cat /proc/slabinfo | grep TCP

TCP 288 384 1984 16 8

“可以看到 TCP cache 下每个 slab 占用 8 个 Page,也就是 8* 4096 = 32768KB。该对象的单个大小是 1984 字节,每个 slab 内放了 16 个对象,1984*16=31744”

“这个时候再多放一个 TCP 对象又放不下,剩下的 1K 内存就只好“浪费”掉了。但是鉴于 slab 机制整体提供的高性能、以及低碎片的效果,这一点点的额外开销还是很值得的。”

责任编辑:

更多推荐

kmem 反编译linux内核

本文发布于:2024-03-06 11:52:55,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1715262.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:内核   反编译   kmem   linux

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!