1.slabinfoԴ?源码?
2.Linux三大分配器之浅析slab基本原理
3.深入理解 slab cache 内存分配全链路实现
4.Linux内核:内存管理——Slab分配器
5.Linux中的内存分配--slab(1)
6.linux查看cpu占用率的方法:
slabinfoԴ??
本文深入剖析了Linux内核源码中的内存管理机制,重点关注内存分配与释放的源码关键函数,通过分析4.9版本的源码源码,详细介绍了slab算法及其核心代码实现。源码在内存管理中,源码slab算法通过kmem_cache结构体进行管理,源码牛市分享源码利用数组的源码形式统一处理所有的kmem_cache实例,通过size_index数组实现对象大小与kmem_cache结构体之间的源码映射,从而实现高效内存分配。源码其中,源码关键的源码计算方法是通过查找输入参数的最高有效位序号,这与常规的源码0起始序号不同,从1开始计数。源码
在找到合适的源码kmem_cache实例后,下一步是源码通过数组缓存(array_cache)获取或填充slab对象。若缓存中有可用对象,则直接从缓存分配;若缓存已空,会调用cache_alloc_refill函数从三个slabs(free/partial/full)中查找并填充可用对象至缓存。在对象分配过程中,array_cache结构体发挥了关键作用,它不仅简化了内存管理,还优化了内存使用效率。
对象释放流程与分配流程类似,涉及数组缓存的管理和slab对象的回收。在cache_alloc_refill函数中,关键操作是检查slab_partial和slab_free队列,寻找空闲的对象以供释放。整个过程确保了内存资源的高效利用,避免了资源浪费。
总结内存操作函数概览,栈与堆的区别是显而易见的。栈主要存储函数调用参数、局部变量等,而堆用于存放new出来的对象实例、全局变量、静态变量等。由于堆的动态分配特性,它无法像栈一样精准预测内存使用情况,导致内存碎片问题。为了应对这一挑战,Linux内核引入了buddy和slab等内存管理算法,以提高内存分配效率和减少碎片。
然而,即便使用了高效的内存管理算法,内存碎片问题仍难以彻底解决。在C/C++中,没有像Java那样的自动垃圾回收机制,导致程序员需要手动管理内存分配与释放。如果忘记释放内存,将导致资源泄漏,影响系统性能。为此,业界开发了如ZGC和Shenandoah等垃圾回收算法,以提高内存管理效率和减少内存碎片。
ZGC算法通过分页策略对内存进行管理,并利用“初始标记”阶段识别GC根节点(如线程栈变量、jq日历插件源码静态变量等),并查找这些节点引用的直接对象。此阶段采用“stop the world”(STW)策略暂停所有线程,确保标记过程的准确性。接着,通过“并发标记”阶段识别间接引用的对象,并利用多个GC线程与业务线程协作提高效率。在这一过程中,ZGC采用“三色标记”法和“remember set”机制来避免误回收正常引用的对象,确保内存管理的精准性。
接下来,ZGC通过“复制算法”实现内存回收,将正常引用的对象复制到新页面,将旧页面的数据擦除,从而实现内存的高效管理。此外,通过“初始转移”和“并发转移”阶段进一步优化内存管理过程。最后,在“对象重定位”阶段,完成引用关系的更新,确保内存管理过程的完整性和一致性。
通过实测,ZGC算法在各个阶段展现出高效的内存管理能力,尤其是标记阶段的效率,使得系统能够在保证性能的同时,有效地管理内存资源。总之,内存管理是系统性能的关键因素,Linux内核通过先进的算法和策略,实现了高效、灵活的内存管理,为现代操作系统提供稳定、可靠的服务。
Linux三大分配器之浅析slab基本原理
Linux内核中的内存分配管理涉及到Buddy和SLAB两种机制。Buddy分配器虽提供了page级别的接口,但颗粒度依然偏大,因此需要SLAB来进一步细化管理。SLAB分配器主要分为slab、slub和slob,其中slob适用于内存紧张的嵌入式系统,而slab因其效率和通用性,是Linux内核中的核心部分。
SLAB的管理涉及两个关键结构:kmem_cache(缓存)和slab本身,它们之间通过cache_chain相连。在内核初始化时,会根据kmalloc_sizes.h中的定义创建特定大小obj的slab和kmem_cache,称为通用缓存,供kmalloc使用。kmalloc通过查找最接近输入size的kmem_cache来分配内存,可能导致内存浪费。
SLAB分配器的复杂性体现在其两个重要数据结构:per node和per cpu的kmem_list3和array_cache。kmem_list3管理slab的分配状态,而array_cache负责按cpu缓存obj。slab结构分为内嵌式和外挂式,管理数据kmem_bufctl_t记录当前和下一个可用obj的spring could 源码log位置。分配流程优先从array_cache中LIFO原则获取,释放时则优先回放在array_cache,超出limit后才会移动到kmem_list3。
深入理解SLAB分配器的原理和管理逻辑对于优化内存使用和性能至关重要。对于更深入的学习资源,可以加入作者推荐的Linux内核技术交流群获取更多信息和资料,包括内核源码、内存调优等内容。
深入理解 slab cache 内存分配全链路实现
本文基于内核5.4版本探讨slab cache内存分配的全链路实现。在深入理解slab cache架构图之后,我们将从内核源码角度拆解实现细节。首先,slab cache如何进行内存分配?以内核从task_struct_cachep中申请task_struct对象为例,解析内存分配流程。 内核使用fork()系统调用创建进程时,需要管理task_struct结构,为此设置专属slab cache(task_struct_cachep)。接下来,我们将聚焦于如何在slab cache中分配内存。 内核通过fork.c文件中的dup_task_struct函数为进程申请并初始化task_struct对象。同时,kmem_cache_alloc_node函数指示slab cache从指定的NUMA节点分配对象。slab cache的快速分配路径
slab cache初始进入快速分配路径(fastpath),首先尝试从cpu本地缓存(kmem_cache_cpu->freelist)获取对象。在获取kmem_cache_cpu结构时,需确保它是当前执行进程的cpu缓存。在配置了CONFIG_PREEMPT的情况下,允许优先级更高的进程抢占当前cpu,导致进程调度到其他cpu执行。此时,用于快速分配的对象可能与当前cpu的缓存不一致,内核通过循环判断tid一致性以防止此情况。 内核从cpu缓存slab中获取第一个空闲对象。若无空闲对象或NUMA节点不匹配,则进入慢速分配路径(slowpath)。slab cache的慢速分配路径
在慢速路径(slowpath)中,内核关闭中断并重新获取cpu本地缓存,防止进程在中断关闭前被抢占。随后,检查本地cpu缓存的slab容量,确保有空闲对象。若本地缓存为空,跳转至new_slab分支,进入慢速路径。若非空,内核再次检查kmem_cache_cpu->freelist,以防止进程中断后其他进程释放对象到缓存中。若此时有空闲对象,则直接从kmem_cache_cpu->freelist分配。若无空闲对象,则检查slab本身的freelist。分配内存流程
内核在redo分支确认本地cpu缓存无空闲对象后,开始分配内存。首先在本地cpu缓存查找,若无空闲对象,分类筛选搜索源码则转至NUMA节点缓存的partial列表。在partial列表中查找可分配的slab,将其提升为本地cpu缓存,并更新kmem_cache_cpu->freelist,分配内存对象。 若所有列表均为空,内核将跨NUMA节点查找,并尝试申请新slab。在成功申请slab后,内核填充相关属性,初始化freelist链表,并根据配置选择顺序或随机方式初始化。slab freelist初始化
slab freelist初始化有两种方式:顺序或随机。顺序初始化根据内存地址顺序串联空闲对象,而随机初始化则以随机顺序连接。顺序初始化有助于直观理解,而随机初始化则用于安全考虑,避免预测。 内核按照kmem_cache->size指定尺寸划分物理内存页,使用setup_object函数初始化内存区域并进行内存布局。在完成对象内存区域初始化后,slab freelist指针指向第一个初始化的空闲对象,重复此过程直至所有空闲对象串联,最后设置freelist的末尾为null。总结
通过本文的探讨,我们深入了解了slab cache内存分配的完整流程,包括快速和慢速路径、slab对象初始化和内存页详细初始化。理解了这些关键点有助于深入掌握slab cache的内存管理机制。Linux内核:内存管理——Slab分配器
深入解析Linux内核:内存管理的艺术——SLAB分配器
在Linux内核的世界里,内存管理是一项至关重要的任务。其中,SLAB分配器扮演着关键角色,它解决了页框分配器的大页框浪费问题,通过专用SLAB(如TCP)和普通SLAB(如kmalloc-8, kmalloc-等)实现了高效而灵活的内存管理。通过执行`cat /proc/slabinfo`,我们可以窥探SLAB的运行状态。
SLAB的核心理念在于对象大小的固定性,这有助于减少内存碎片,提高内存使用效率。kmem_cache(SLAB缓存)是其最高层级的数据结构,它负责描述和管理SLAB及其对象。内核模块通过kmem_cache_create定制化的SLAB,确保内存管理的灵活性。
kmem_cache结构内部,对象大小(object_size)与SLAB的全局配置如gfporder和num保持同步。每个NUMA节点的SLAB管理由struct kmem_cache_node数组负责,它支持分布式内存管理,确保了内存的均衡分配。
在kmem_cache的内部结构中,SLAB链表是关键部分,包括slabs_partial、slabs_full和slabs_free。slabs_partial存储部分使用的SLAB描述符,slabs_full则是路边停车app源码所有对象的链表,而slabs_free则记录空闲的SLAB。这些链表通过spinlock_t lock进行同步,确保了在分配和回收过程中的线程安全。
SLAB设计巧妙,如SLUB(Simple Low Overhead Buffering)和SLOB(Simplified Low Overhead Buffering)结构,它们结合了计数器、活跃对象和动态链表,以实现内存的高效分配。SLAB描述符还包括页标志、对象地址指针和空闲对象链表,这些细节都在CONFIG_SLUB配置中有所体现。
SLAB描述符中的freelist和填充区域的优化,以及对象地址的着色设计,都是提高内存利用率的重要手段。内存着色通过添加偏移量避免同一行内存冲突,提升了性能。本地CPU和共享链表的组合,形成了SLAB分配器的高效运作框架,优先级分配原则保证了快速响应。
了解这些细节后,我们发现SLAB分配器是Linux内核内存管理的精髓所在,它在内存分配和回收的过程中,巧妙地平衡了效率与灵活性。通过深入研究这些内部机制,我们可以更好地理解和优化我们的系统内存使用。
推荐阅读
1. Linux文件系统详解
2. Linux进程管理:实时调度
3. Linux内核内存管理 - 缺页异常 & brk系统调用
原文作者:tolimit
原文地址:linux内存源码分析 - SLAB分配器概述
---
经过上述的精炼与重构,文章内容更加清晰,突出了SLAB分配器在Linux内核内存管理中的核心作用和关键细节,为读者提供了深入理解内存管理的窗口。
Linux中的内存分配--slab(1)
在Linux中,当内存分配遇到小于一页的需求时,为避免浪费和内碎片问题,slab分配器应运而生。slab分配器的核心机制是kmem_cache,它为每个对象类别维护一个"cache",分配和释放对象时都从对应的cache中进行,提高了效率。cache的内存来源于buddy伙伴系统,通过分页并按照对象大小划分,确保物理内存的连续性。
每个kmem_cache由若干slabs组成,每个slab由一个或多个页框构成,大小由gfporder定义。为了优化CPU缓存利用,slab引入了coloring机制,通过调整slab中的偏移量,确保相同对象号的对象不会对齐,从而减少缓存替换操作。
kmem_cache_node负责描述和管理slab中的对象,包含slab链表,根据NUMA架构进行内存分配。slab描述符中,s_mem和freelist分别指向第一个对象和空闲对象链表。空闲对象链表由数组组成,根据活跃对象动态调整。
本地CPU空闲链表作为kmem_cache的一部分,记录对象的释放,便于内存回收。通过slabtop命令,可以查看系统的slab分配情况,包括内存使用、cache数量、slabs数量以及object大小分布。此外,/proc/meminfo和/proc/slabinfo提供了更详细的内存使用信息。
深入理解slab分配器的更多内容,可以参考相关文章如《Slab Allocator (kernel.org)》、《The Slab Allocator in the Linux kernel (hammertux.github.io)》以及《linux内存源码分析 - SLAB分配器概述》等。
linux查看cpu占用率的方法:
top
top是最常用的查看系统资源使用情况的工具,包括CPU、内存等等资源。这里主要关注CPU资源。
1.1 /proc/loadavg
load average取自/proc/loadavg。
9. 9. 8. 3/
前三个数字是1、5、分钟内进程队列中平均进程数,包括正在运行的进程+准备好等待运行的进程。
第四个数字分子表示正在运行的进程数,分母是进程总数。
最后一个数字是最近运行的进程ID号。
其中top取的是/proc/loadavg的前三个数。
1.2 top使用
打开top,可以指定更新的周期。
输入H,打开隐藏的线程;输入1,可以显示单核CPU使用情况。
top -H -b -d 1 -n > top.txt,每个1秒统计一次,共次,显示线程细节,并保存到top.txt中。
top采样来源你还依赖于/proc/stat和/proc//stat两个,这两个的详细介绍参考:/proc/stat和/proc//stat。
其中CPU信息对应的含义如下:
us是user的意思,统计nice小于等于0的用户空间进程,也即优先级为~。 ni是nice的意思,统计nice大于0的用户空间进程,也即优先级为~。 sys是system的意思,统计内核态运行时间,不包括中断。 id是idle的意思,几系统处于空闲态。 wa是iowait的意思,统计io等待时间。 hi是hardware interrupt,统计硬件中断时间。 si是software interrupt,统计软中断时间。 最后的st是steal的意思。
perf
通过sudo perf top -s comm,可以查看当前系统运行进程占比。
这里不像top一样区分idle、system、user,这里的占比是各个进程在总运行时间里面占比。
通过sudo perf record记录采样信息,然后通过sudo perf report -s comm。
sar、ksar
sar是System Activity Report的意思,可以用于实时观察当前系统活动,也可以生成历史记录的报告。
要使用sar需要安装sudo apt install sysstat,然后对sysstat进行配置。
sar用于记录统计信息,ksar用于将记录的信息图形化输出。
ksar下载地址在: github.com/vlsi/ksar/re...
sudo gedit /etc/default/sysstat--------------------------------将 ENABLED=“false“ 改为ENABLED=“true“。 sudo gedit /etc/cron.d/sysstat--------------------------------修改sar的周期等配置。 sudo /etc/init.d/sysstat restart--------------------------------重启sar服务 /var/log/sysstat/--------------------------------------------------sar log存放目录
使用sar记录开机到目前的统计信息到文件sar.txt。
LC_ALL=C sar -A > sar.txt
PS:这里直接使用sar -A,在ksar中无法正常显示。
如下执行java -jar ksar.jar,然后Data->Load from text file...选择保存的sar.txt文件。
得到如下的图表。
还可以通过sar记录一段时间的信息,指定采样周期和采样次数。
这些命令前加上LC_ALL=C之后保存到文件中,都可以在ksar中图形化显示。
collectl、colplot
collectl是一款非常优秀并且有着丰富的命令行功能的实用程序,你可以用它来采集描述当前系统状态的性能数据。
不同于大多数其它的系统监控工具,collectl 并非仅局限于有限的系统度量,相反,它可以收集许多不同类型系统资源的相关信息,如 cpu 、disk、memory 、network 、sockets 、 tcp 、inodes 、infiniband 、 lustre 、memory、nfs、processes、quadrics、slabs和buddyinfo等。
同时collectl还可以替代常用工具,比如top、vmstat、ps、iotop等。
安装collectl:
sudo apt-get install collectl
collectl的使用很简单,默认collectl显示cpu、磁盘、网络信息。
collectl还可以显示更多的子系统信息,如果选项存在对应的大写选项,大写选项表示更细节的设备统计信息。
b – buddy info (内存碎片) c – 所有CPU的合一统计信息;C - 单个CPU的统计信息。 d – 整个文件系统Disk合一统计信息;C - 单个磁盘的统计信息。 f – NFS V3 Data i – Inode and File System j – 显示每个CPU的Interrupts触发情况;J - 显示每个中断详细触发情况。 l – Lustre m – 显示整个系统Memory使用情况;M - 按node显示内存使用情况。 n – 显示整个系统的Networks使用情况;N - 分网卡显示网络使用情况。 s – Sockets t – TCP x – Interconnect y – 对系统所有Slabs (系统对象缓存)使用统计信息;Y - 每个slab使用的详细信息。
collectl --all显示所有子系统的统计信息,包括cpu、终端、内存、磁盘、网络、TCP、socket、文件系统、NFS。
collectl --top可以代替top命令:
collectl --vmstat可以代替vmstat命令:
collectl -c1 -sZ -i:1可以代替ps命令。
collectl和一些处理分析数据工具(比如colmux、colgui、colplot)结合能提供可视化图形。
colplot是collectl工具集的一部分,其将collectl收集的数据在浏览器中图形化展示。
colplot的介绍 在此,相关源码可以再 collectl-utils下载。
解压下载的colplot之后,sudo ./INSTALL安装colplot。
安装之后重启apache服务:
suod systemctl reload apache2 sudo systemctl restart apache2
在浏览器中输入 .0.0.1/colplot/,即可使用colplot。
通过Change Dir选择存放经过collectl -P保存的数据,然后设置Plot细节、显示那些子系统、plot大小等等。
最后Generate Plot查看结果。
Linux内核源码解析---万字解析从设计模式推演per-cpu实现原理
引子
在如今的大型服务器中,NUMA架构扮演着关键角色。它允许系统拥有多个物理CPU,不同NUMA节点之间通过QPI通信。虽然硬件连接细节在此不作深入讨论,但需明白每个CPU优先访问本节点内存,当本地内存不足时,可向其他节点申请。从传统的SMP架构转向NUMA架构,主要是为了解决随着CPU数量增多而带来的总线压力问题。
分配物理内存时,numa_node_id() 方法用于查询当前CPU所在的NUMA节点。频繁的内存申请操作促使Linux内核采用per-cpu实现,将CPU访问的变量复制到每个CPU中,以减少缓存行竞争和False Sharing,类似于Java中的Thread Local。
分配物理页
尽管我们不必关注底层实现,buddy system负责分配物理页,关键在于使用了numa_node_id方法。接下来,我们将深入探索整个Linux内核的per-cpu体系。
numa_node_id源码分析获取数据
在topology.h中,我们发现使用了raw_cpu_read函数,传入了numa_node参数。接下来,我们来了解numa_node的定义。
在topology.h中定义了numa_node。我们继续跟踪DECLARE_PER_CPU_SECTION的定义,最终揭示numa_node是一个共享全局变量,类型为int,存储在.data..percpu段中。
在percpu-defs.h中,numa_node被放置在ELF文件的.data..percpu段中,这些段在运行阶段即为段。接下来,我们返回raw_cpu_read方法。
在percpu-defs.h中,我们继续跟进__pcpu_size_call_return方法,此方法根据per-cpu变量的大小生成回调函数。对于numa_node的int类型,最终拼接得到的是raw_cpu_read_4方法。
在percpu.h中,调用了一般的read方法。在percpu.h中,获取numa_node的绝对地址,并通过raw_cpu_ptr方法。
在percpu-defs.h中,我们略过验证指针的环节,追踪arch_raw_cpu_ptr方法。接下来,我们来看x架构的实现。
在percpu.h中,使用汇编获取this_cpu_off的地址,代表此CPU内存副本到".data..percpu"的偏移量。加上numa_node相对于原始内存副本的偏移量,最终通过解引用获得真正内存地址内的值。
对于其他架构,实现方式相似,通过获取自己CPU的偏移量,最终通过相对偏移得到pcp变量的地址。
放入数据
讨论Linux内核启动过程时,我们不得不关注per-cpu的值是如何被放入的。
在main.c中,我们以x实现为例进行分析。通过setup_percpu.c文件中的代码,我们将node值赋给每个CPU的numa_node地址处。具体计算方法通过early_cpu_to_node实现,此处不作展开。
在percpu-defs.h中,我们来看看如何获取每个CPU的numa_node地址,最终还是通过简单的偏移获取。需要注意如何获取每个CPU的副本偏移地址。
在percpu.h中,我们发现一个关键数组__per_cpu_offset,其中保存了每个CPU副本的偏移值,通过CPU的索引来查找。
接下来,我们来设计PER CPU模块。
设计一个全面的PER CPU架构,它支持UMA或NUMA架构。我们设计了一个包含NUMA节点的结构体,内部管理所有CPU。为每个CPU创建副本,其中存储所有per-cpu变量。静态数据在编译时放入原始数据段,动态数据在运行时生成。
最后,我们回到setup_per_cpu_areas方法的分析。在setup_percpu.c中,我们详细探讨了关键方法pcpu_embed_first_chunk。此方法管理group、unit、静态、保留、动态区域。
通过percpu.c中的关键变量__per_cpu_load和vmlinux.lds.S的链接脚本,我们了解了per-cpu加载时的地址符号。PERCPU_INPUT宏定义了静态原始数据的起始和结束符号。
接下来,我们关注如何分配per-cpu元数据信息pcpu_alloc_info。percpu.c中的方法执行后,元数据分配如下图所示。
接着,我们分析pcpu_alloc_alloc_info的方法,完成元数据分配。
在pcpu_setup_first_chunk方法中,我们看到分配的smap和dmap在后期将通过slab再次分配。
在main.c的mm_init中,我们关注重点区域,完成map数组的slab分配。
至此,我们探讨了Linux内核中per-cpu实现的原理,从设计到源码分析,全面展现了这一关键机制在现代服务器架构中的作用。