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

一、NODE 划分

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

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

Processor Information  //第一颗CPUSocketDesignation: CPU1   Version: Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHzCore Count: 8Thread Count: 16
Processor Information  //第二颗CPUSocket Designation: CPU2Version: Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHzCore Count: 8

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

//CPU1 上总共插着四条内存
Memory DeviceSize: 16384 MBLocator: CPU1 DIMM A1
Memory DeviceSize: 16384 MBLocator: CPU1 DIMM A2
......
//CPU2 上也插着四条
Memory DeviceSize: 16384 MBLocator: CPU2 DIMM E1
Memory DeviceSize: 16384 MBLocator: 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      DMApages free     3973managed  3973
Node 0, zone    DMA32pages free     390390managed  427659
Node 0, zone   Normalpages free     15021616managed  15990165
Node 1, zone   Normalpages free     16012823managed  16514393

每个页面大小是4K,很容易可以计算出每个 zone 的大小。比如对于上面 Node1 的 Normal, 16514393 * 4K = 66 GB。

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

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

//file: include/linux/mmzone.h
#define MAX_ORDER 11
struct zone {free_area   free_area[MAX_ORDER];......
}

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

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

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

struct page * alloc_pages(gfp_t gfp_mask, unsigned int order)

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

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

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

四、SLAB管理器

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

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

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

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

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

//file: include/linux/slab_def.h
struct kmem_cache {struct kmem_cache_node **node......
}//file: mm/slab.h
struct kmem_cache_node {struct list_head slabs_partial; struct list_head slabs_full;struct list_head slabs_free;......
}

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

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

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

//file: mm/slab.c
static void *kmem_getpages(struct kmem_cache *cachep, gfp_t flags, int nodeid)
{......flags |= cachep->allocflags;if (cachep->flags & SLAB_RECLAIM_ACCOUNT)flags |= __GFP_RECLAIMABLE;page = alloc_pages_exact_node(nodeid, ...);......
}
//file: include/linux/gfp.h
static inline struct page *alloc_pages_exact_node(int nid, gfp_t gfp_mask,unsigned int order)
{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 机制整体提供的高性能、以及低碎片的效果,这一点点的额外开销还是很值得的。”

说出来你可能不信,内核这家伙在内存的使用上给自己开了个小灶!相关推荐

  1. Linux内核之浅谈内存寻址

    Linux内核之浅谈内存寻址 前言 最近在看内存寻址的内容,略有所得,发此文与大家一起交流.我们知道计算机是由硬件和软件组成,硬件主要包括运算器.控制器.存储器.输入设备和输出设备,软件主要是操作系统 ...

  2. 【Linux 内核】Linux 内核体系架构 ( 进程调度 | 内存管理 | 中断管理 | 设备管理 | 文件系统 )

    文章目录 一.进程调度 二.内存管理 三.中断管理 四.设备管理 五.文件系统 一.进程调度 进程调度 : 进程 是 系统中 进行 资源分配 的 基本单位 ; 每个进程 在 运行时 , 都 感觉自己占 ...

  3. linux内核初始化卡死,armlinux内核启动--内存初始化管理

    linux版本:2.6.36 相关数据结构 arch/arm/include/asm/setup.h #ifdef CONFIG_ARCH_LH7A40X # define NR_BANKS 16 # ...

  4. 【Linux】Linux的内核空间(低端内存、高端内存)

    内核也是程序,也应该具有自己的虚存空间,但是作为一种为用户程序服务的程序,内核空间有它自己的特点. 内核空间与用户空间的关系 在一个32位系统中,一个程序的虚拟空间最大可以是4GB,那么最直接的做法就 ...

  5. Linux内核源代码情景分析-内存管理

    用户空间的页面有下面几种: 1.普通的用户空间页面,包括进程的代码段.数据段.堆栈段.以及动态分配的"存储堆". 2.通过系统调用mmap()映射到用户空间的已打开文件的内容. 3 ...

  6. v15.03 鸿蒙内核源码分析(内存映射) | 映射真是个好东西 | 百篇博客分析HarmonyOS源码

    子曰:"德不孤,必有邻." <论语>:里仁篇 百篇博客系列篇.本篇为: v15.xx 鸿蒙内核源码分析(内存映射篇) | 映射真是个好东西 内存管理相关篇为: v11. ...

  7. v11.03 鸿蒙内核源码分析(内存分配) | 内存有哪些分配方式 | 百篇博客分析HarmonyOS源码

    子曰:"君子周而不比,小人比而不周."<论语>:为政篇 百篇博客系列篇.本篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式 内存管理相关篇为 ...

  8. linux 内核3.1,NVIDIA发布了新的Tegra Linux开发包,内核为3.1.x

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 NVIDIA is pleased to announce that Linux for Tegra release 15 Beta is now ava ...

  9. arm linux内核调试,kgdb在ARM开发板上调试kernel成功

    大概几个注意点: 1.公版的Linux需要打kgdb补丁的,kgdb官方那边可能不支持新版本的内核(我的是2.6.18),可以在下面的网址找到,但是ARM上调试需要打哪些补丁,不是很清楚,网上的几篇中 ...

  10. 阅读 Linux 内核源码——共享内存

    介绍 我看的是linux-4.2.3的源码.参考了<边干边学--Linux内核指导>(鬼畜的书名)第16章内容,他们用的是2.6.15的内核源码. 现在linux中可以使用共享内存的方式有 ...

最新文章

  1. Linux系统中ctrl+c、 ctrl+z、 ctrl+\ 产生的信号名称
  2. java中集合的结构(list和map)
  3. Python(9):函数
  4. Spring4-自动装配Beans-通过注解@Autowired在构造方法上
  5. Android之机端安装apk出现INSTALL_FAILED_INSUFFICIENT_STORAGE错误的解决方法
  6. 如何删除数据库中的所有用户表(表与表之间有外键关系)
  7. 在sql server 发生未指定的错误_一条sql查询是怎么执行的?
  8. centos vmware centos6.6 64位 kvm虚拟化安装配置 第四十二节课
  9. 最大似然估计、MAP、贝叶斯估计
  10. 华硕开机自动进入BIOS解决办法
  11. C++和java的异同点总结
  12. 影院活动管理系统--项目设计阶段.
  13. Project 2013项目管理教程(2):project基础操作概述
  14. UE4(虚幻4)基础:光照需要重建
  15. 2012 Esri 中国开发者大会
  16. 个人开公司的流程,以后用得着
  17. YOLOv5改进、YOLOv7改进IoU损失函数:YOLOv7涨点Trick,改进添加SIoU损失函数、EIoU损失函数、GIoU损失函数、α-IoU损失函数
  18. FIORI ELement list report 细节开发,设置过滤器,搜索帮助object page跳转等
  19. asp.net 提取html div,asp.net利用存储过程和div+css实现分页(类似于博客园首页分页)...
  20. 深度学习机器臂控制_人工智能深度学习算法在工业机械臂上的应用

热门文章

  1. 网络数据库的复制和同步(一)
  2. python大家都是怎么学的_你们都是怎么学 Python 的?
  3. Visual Studio 2017 15.6预览版最新进展
  4. Lync 2010迁移Lync 2013 PART5:支持旧版Lync
  5. 腾讯正式对外开源高性能 RPC 开发框架与微服务平台Tars
  6. 在图层上使用CATransform3D制做三维动画-b
  7. 灭屏取消过温减流功能
  8. 使用Telnet客户端测试Exchange邮件收发
  9. 运用Unity结合PolicyInjection实现拦截器[结合操作日志实例]
  10. getchar()细节