[linux内存管理] 第000篇 Linux内存管理系列开篇 9月前查看 评论
[linux内存管理] 第000篇 Linux内存管理系列开篇

本文为Linux内存管理系列文章的摘要总结。文章首先介绍了源码遵循的约定,包括芯片架构、内存架构、内核版本等信息。接着概述了内存管理的一些预备知识,包括内存与内存管理机制、页表查询过程、内存分布等。文章详细分析了物理内存初始化流程,包括启动代码分析、start_kernel全局简述、fixmap映射、memblock子系统等。此外,文章还介绍了各种内存分配器(如buddy分配器、percpu分配器、CMA分配器等)和内存管理相关概念(如缺页异常处理、memcg、PSI等)。最后,列出了文章中涉及的专业术语及其解释。

[linux内存管理] 第029篇 谁把folio的函数定义“藏”起来了? 2周前查看 3 条
[linux内存管理] 第029篇 谁把folio的函数定义“藏”起来了?

这篇文章探讨了Linux内核中一种特殊的函数定义方式,作者在阅读代码时遇到了无法找到某些函数定义的问题,如`folio_test_active()`。通过深入研究,作者发现这些函数是通过宏定义如`PAGE_TYPE_OPS()`和`FOLIO_FLAG()`在`/include/linux/page-flags.h`中定义的。文章详细解释了这些宏如何工作,以及如何通过它们生成函数定义。总结部分指出,这种探索过程虽然艰辛,但对于喜欢阅读代码的人来说,发现和理解这些机制是非常有趣且有益的。

[linux内存管理] 第028篇 do_anonymous_page只处理私有映射吗? 1月前查看 1 条
[linux内存管理] 第028篇 do_anonymous_page只处理私有映射吗?

笔者水平较低,如有错误欢迎各位看官老爷指正,Thanks♪(・ω・)ノ 笔者忽然高产了起来,原因是因为笔者总算是搞明白了一个近几天一直困扰自己的问题,为什么网上一些帖子说do_anonymous_page只用来处理私有匿名映射呢? 看到这个函数,下意识就会觉得其实它是用来处理所有匿名映射的才对啊。

[linux内存管理] 第027篇 Linux ARM64 虚拟地址布局 2月前查看 1 条
[linux内存管理] 第027篇 Linux ARM64 虚拟地址布局

本文讨论了ARM64架构下Linux内核的虚拟地址布局。文章指出,尽管ARM64地址宽度为64位,但最大支持的物理地址为48位,即256T。虚拟地址宽度可以选择36位、39位、42位、47位、48位或52位,以39位为例,用户空间和内核空间大小均为512G。文章还详细介绍了内核虚拟地址空间分布,包括线性映射区域、模块区域、内核镜像区域、VMEMMAP区域、PCI I/O区域、固定映射区域等。最后,文章解释了线性空间下移的原因,并展示了虚拟地址空间总体分布图。

[linux内存管理] 第026篇 从内核源码看 slab 内存池的创建初始化流程 2月前查看 评论
[linux内存管理] 第026篇 从内核源码看 slab 内存池的创建初始化流程

本文详细介绍了Linux内核中slab内存池的创建过程,从源码层面解释了slab cache的架构设计和实现。文章首先介绍了slab cache的创建接口函数kmem_cache_create,并解释了其参数与slab cache结构体属性的对应关系。接着,文章深入分析了slab cache创建的详细流程,包括获取锁、参数校验、查找可复用的slab cache、计算slab对象的内存布局、初始化slab cache的重要属性、创建本地cpu缓存和NUMA节点缓存等步骤。此外,文章还介绍了slab allocator体系的初始化过程,解释了如何解决先有鸡还是先有蛋的问题,并详细说明了slab对象的内存布局和计算slab所需物理内存页个数的逻辑。最后,文章总结了slab cache的创建流程和架构,并展望了后续对slab内存池内存分配的深入探讨。

[linux内存管理] 第025篇 细节拉满,80 张图带你一步一步推演 slab 内存池的设计与实现 2月前查看 评论
[linux内存管理] 第025篇 细节拉满,80 张图带你一步一步推演 slab 内存池的设计与实现

本文介绍了 Linux 内核中的 slab 内存池,用于高效地分配和释放小内存块。作者首先回顾了 Linux 内存分配的宏观流程,然后引出 slab 内存池的产生背景和优势。slab 内存池将频繁使用的小内存块池化,避免了频繁的内存分配和释放带来的性能开销。文章详细介绍了 slab 对象池的内存布局,包括对齐、red zone、freepointer 和状态信息等。接着,文章分析了 slab 的总体架构设计,包括 kmem_cache、kmem_cache_cpu 和 kmem_cache_node 等数据结构。最后,文章详细介绍了 slab 内存分配和释放的原理,包括从本地 cpu 缓存、partial 列表、NUMA 节点缓存和伙伴系统中分配和释放内存的场景。

[linux内存管理] 第024篇 slab内存分配器概述 2月前查看 评论
[linux内存管理] 第024篇 slab内存分配器概述

填充操作中的数据结构 在内存管理中,填充操作涉及到多个核心数据结构,其中包括: 1. **struct kmem_cache**:代表一种“对象类型”的缓存池。内核启动时或动态创建时,会为常见的内核对象(如 `task_struct`、`fs_struct`、`inode`)各自建立一个 `kmem_cache`,用它来管理该类型对象的分配与回收。 2. **struct array_cache**:本地缓存池,每个CPU一个。当本地缓存池为空时,需要从共享缓存池或者slans_partial/slabs_free中,获取batchcount个对象到本地缓存池。 3. **slab_flags_t flags**:对象分配掩码,用于控制对象的分配行为。 4. **unsigned int num**:一个slab最多可以有多少个对象。 5. **unsigned int gfporder**:一个slab占用多个2的order次方物理页面。 6. **size_t colour**:一个slab分配器有多少个不同的高速缓存行,用于着色。 7. **unsigned int colour_off**:一个着色区长度,和L1高速缓存行大小相同。 8. **void (*ctor)(void *obj)**:构造函数,用于初始化新分配的对象。 9. **const char *name**:slab描述符名字。 10. **struct list_head list**:链表节点,用于把slab描述符添加到全局链表slab_caches中。 11. **int refcount**:本描述符引用计数。 12. **int object_size**:对象实际大小。 13. **int align**:对齐长度。 14. **unsigned long num_active**:当前正被使用(allocated but not freed)的对象总数。 15. **unsigned long num_allocations**:累计分配(kmem_cache_alloc)次数。 16. **unsigned long high_mark**:历史最高的 num_active 值(峰值并发使用量)。 17. **unsigned long grown**:slab “增长”次数:从 partial/slabs_empty 中取空 slab 或新建 slab 的总次数。 18. **unsigned long reaped**:slab “收割”次数:将完全空闲的 slab 返还给伙伴系统的总次数。 19. **unsigned long errors**:运行时遇到的错误次数(如分配失败、参数校验失败等)。 20. **unsigned long max_freeable**:在任意时刻,整 cache 中可一次性返还给伙伴系统的最大页数。 21. **unsigned long node_allocs**:NUMA 环境下,从本节点分配 slab 对象的次数。 22. **unsigned long node_frees**:NUMA 环境下,向本节点返还 slab 对象的次数。 23. **unsigned long node_overflow**:NUMA 本地缓存溢出的次数(本地 node cache 空间不足需借用或返还)。 24. **atomic_t allochit**:分配时直接命中 per-CPU cache 的次数(无需访问 partial/full)。 25. **atomic_t allocmiss**:分配时未命中 per-CPU cache,需要去 partial/full 或新建 slab 的次数。 26. **atomic_t freehit**:释放时直接回填 per-CPU cache 的次数。 27. **atomic_t freemiss**:释放时未能回填 per-CPU cache,需要 flush 回 partial/full 的次数。 28. **int obj_offset**:对象在 slab 内的偏移(bytes),仅在调试模式下有效。 29. **struct kasan_cache kasan_info**:KASAN相关的缓存信息。 30. **unsigned int *random_seq**:用于随机化 freelist 的序列。 31. **unsigned int useroffset**:用户对象偏移量。 这些数据结构共同构成了填充操作的核心,它们协同工作,实现了高效、低碎片的内存管理。

[linux内存管理] 第023篇 watermark详解 5月前查看 评论
[linux内存管理] 第023篇 watermark详解

本文探讨了 Linux 内存管理中的水位机制,特别是 `zoned page frame allocator` 如何使用水位来控制内存分配和回收。文章首先介绍了 `struct zone` 结构体和三种水位 `WMARK_MIN`、`WMARK_LOW` 和 `WMARK_HIGH` 的概念及其作用。随后,文章详细分析了水位的初始化过程,包括计算 `min_free_kbytes`、更新内存区水位、刷新内存区统计阈值和初始化低内存保留等步骤。接着,文章讨论了快速分配和慢速分配中的水位检测机制,以及 `kswapd` 和内存规整过程中的水位检测。最后,文章强调了调整内存水位的重要性,以及如何根据不同业务场景进行优化。

[linux内存管理] 第22篇 buddy内存管理之慢速分配 6月前查看 评论
[linux内存管理] 第22篇 buddy内存管理之慢速分配

**本文分析了 Linux 内核中慢速内存分配路径 `__alloc_pages_slowpath`,该路径在快速分配失败时被触发。慢速分配尝试通过多种手段获取内存,包括内存回收、内存压缩和唤醒 kswapd 线程等**。 **主要步骤如下**: 1. **判断是否允许直接回收内存**:根据 GFP 标志判断是否可以进行直接内存回收。 2. **判断是否为高成本请求**:根据请求的 order 和 migratetype 判断是否为高成本请求。 3. **尝试直接内存压缩**:在高成本请求或无法访问预留内存的情况下,尝试进行直接内存压缩。 4. **唤醒 kswapd 线程**:唤醒 kswapd 线程进行内存回收。 5. **尝试再次分配内存**:根据新的分配标志和 zonelist 尝试再次分配内存。 6. **直接内存回收**:如果允许直接回收内存,则尝试进行直接内存回收。 7. **再次尝试分配内存**:在内存回收后再次尝试分配内存。 8. **尝试内存压缩**:如果直接内存回收失败,则尝试进行内存压缩。 9. **处理 CPU 集合更新**:检查 CPU 集合是否更新,并进行相应的处理。 10. **启动 OOM 杀手**:如果所有尝试都失败,则启动 OOM 杀手进程。 11. **重试分配**:如果 OOM 杀手进程有所进展,则重试分配。 **慢速分配路径的关键在于通过各种手段增加空闲内存,以便能够成功分配请求的内存**。 **总结来说,慢速分配路径是 Linux 内核中保证内存分配可靠性的重要机制,它通过多种手段应对内存不足的情况,确保系统能够正常运行**。

[linux内存管理] 第21篇 buddy内存管理之快速分配 6月前查看 评论
[linux内存管理] 第21篇 buddy内存管理之快速分配

### 4. 慢速分配 当快速分配无法满足分配请求时,会进入慢速分配流程。 ```c static struct page *__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order, struct alloc_context *ac) { gfp_t alloc_flags = ALLOC_WMARK_LOW | ALLOC_CPUSET; unsigned int alloc_flags_idx = ALLOC_INDEX(gfp_mask); struct zone *zone; nodemask_t *alloc_nodemask; int alloc_nid; struct pglist_data *pgdat; struct page *page = NULL; unsigned long mark; /* The fallback path may need to allocate memory on another node */ alloc_nid = numa_node_id(); /* * On the first allocation attempt, we try to allocate from the * preferred zone only. On the second attempt, we try to allocate * from the whole zonelist. */ if (ac->preferred_zoneref->zone != NULL) zone = ac->preferred_zoneref->zone; else zone = NULL; /* * The first allocation attempt can ignore memory policy. For the * second attempt, we need to honor the memory policy. */ if (ac->nodemask == &cpuset_current_mems_allowed) { alloc_nodemask = NULL; alloc_flags &= ~ALLOC_CPUSET; } else { alloc_nodemask = ac->nodemask; } pgdat = zone->zone_pgdat; alloc_flags |= pgdat->alloc_flags; /* If we are re-filling the per-cpu pageset, then honor the * zone reclaim mode. */ if (order == 0 && ac->pcp) { alloc_flags |= pgdat->alloc_flags; if (!node_reclaim_enabled()) alloc_flags &= ~ALLOC_CPUSET; } /* * We have already checked if the zone is online and the node is * online. If the zone is not online, we cannot use it for * allocation. If the node is not online, then it is possible * that the zone is online but the node is not. If we cannot use * the zone, then we must use a different node. */ if (!zone->node->online) { zone = NULL; alloc_nid = numa_next_node(alloc_nid, alloc_nodemask); if (alloc_nid == NUMA_NO_NODE) { if (alloc_flags_idx == ALLOC_INDEX(GFP_KERNEL)) { return NULL; } alloc_flags_idx = ALLOC_INDEX(GFP_KERNEL); alloc_flags &= ~ALLOC_CPUSET; alloc_flags &= ~ALLOC_WMARK_LOW; alloc_flags |= ALLOC_WMARK_MIN; goto retry; } pgdat = NODE_DATA(alloc_nid); alloc_flags |= pgdat->alloc_flags; } /* We are going to sleep. If this allocation is for a page cache * page, then we want to go to sleep only if the dirty pages are * within the limit. */ if (!ac->spread_dirty_pages && !node_dirty_ok(pgdat)) { if (alloc_flags_idx == ALLOC_INDEX(GFP_KERNEL)) { return NULL; } alloc_flags_idx = ALLOC_INDEX(GFP_KERNEL); alloc_flags &= ~ALLOC_CPUSET; alloc_flags &= ~ALLOC_WMARK_LOW; alloc_flags |= ALLOC_WMARK_MIN; goto retry; } /* * The zone has enough free pages. We can allocate a new page. */ if (zone_page_state(zone, NR_FREE_PAGES) >= zone_page_state(zone, NR_ZONE_FREE_MIN) + (1 << order)) { alloc_flags &= ~ALLOC_CPUSET; } retry: alloc_flags |= gfp_to_alloc_flags(gfp_mask, alloc_flags_idx); /* If the zone is not online, we need to allocate from another zone. */ if (!zone) { if (!alloc_nodemask) { zone = zone_reclaim(zone, gfp_mask, order); if (zone) goto retry; } else { zone = zone_reclaim_nodemask(pgdat, gfp_mask, order); if (zone) goto retry; } } /* * If we are here, it means that we have found a zone that has enough * free pages and we can allocate a new page. */ if (zone_page_state(zone, NR_FREE_PAGES) >= zone_page_state(zone, NR_ZONE_FREE_MIN) + (1 << order)) { page = get_page_from_freelist(gfp_mask, order, alloc