Linux内核内存不足的时候处理机制
1、Linux内核在内存不足时采用多级处理机制,主要包括以下步骤:
在内存不足的时候,页回收负责回收物理页,对于没有后备存储设备支持的匿名页,把数据换出到交换区,然后释放物理页;对于有后备存储设备支持的文件页,把数据写回存储设备,然后释放物理页。如果页回收失败,使用最后一招内存耗尽杀手,选择把进程杀掉。
1. 内存回收(Reclaim)
缓存回收:优先回收文件系统缓存(如页缓存)。这些缓存内存是可回收的,因为它们主要用于提升性能而非核心功能。
直接回收(Direct Reclaim):当缓存回收后仍不足时,当前进程会直接回收内存(如释放未使用的页帧)。此过程会阻塞进程,直到回收成功。
后台回收(Kswapd):通过kswapd守护进程异步回收内存。它会根据LRU算法选择最近未使用的页帧进行回收。
2. 内存规整(Compaction)
当内存碎片化严重时,内核会尝试将零散的内存页合并成连续块。这有助于提高大页分配的成功率。
3. OOM机制(Out of Memory)
触发条件:当上述回收和规整仍无法满足内存需求时,内核会触发OOM机制。
OOM Killer:选择并杀死占用内存最多的进程(通过oom_badness()算法评分)。可配置参数如:
/proc/sys/vm/oom_kill_allocating_task:是否允许杀死触发OOM的进程。
/proc/sys/vm/oom_dump_tasks:是否打印所有进程的内存使用信息。
进程保护:可通过/proc/<pid>/oom_score_adj调整进程的OOM评分(值越大越容易被杀)。
关键参数
/proc/sys/vm/min_free_kbytes:设置内存水位阈值(低于此值触发回收)。
/proc/sys/vm/panic_on_oom:是否在OOM时强制重启系统(默认关闭)。
总结
Linux内核通过多级回收机制(缓存回收 → 直接回收 → 后台回收)和规整策略应对内存不足,OOM机制作为最后手段杀死进程。
OOM核心函数:out_of_memory()处理内存不足时的OOM机制
进程选择:select_bad_process()根据LRU算法选择OOM进程
评分计算:badness()计算进程的OOM评分(内存使用量+用户调整值)
进程杀死:oom_kill_process()发送SIGKILL信号杀死进程并记录日志
内核日志:通过printk输出OOM事件信息
用户调整:oom_score_adj参数允许用户调整进程的OOM优先级
#include <linux/mm.h>
#include <linux/oom.h>
void out_of_memory(struct zonelist *zonelist, gfp_t gfp_mask, int order)
{
struct task_struct *p;
int points = 0;
// 1. 选择OOM进程
p = select_bad_process(gfp_mask, order);
if (!p)
return;
// 2. 计算OOM评分
points = badness(p, zonelist, gfp_mask, order);
if (points <= 0)
return;
// 3. 杀死进程
oom_kill_process(p, gfp_mask, order, points, zonelist);
}
int badness(struct task_struct *p, struct zonelist *zonelist, gfp_t gfp_mask, int order)
{
// 计算进程的OOM评分
int points = 0;
points += p->mm->total_vm; // 内存使用量
points -= p->oom_score_adj; // 用户调整值
return points;
}
void oom_kill_process(struct task_struct *p, gfp_t gfp_mask, int order, int points, struct zonelist *zonelist)
{
// 杀死进程并记录日志
printk(KERN_ERR "OOM killer: Killed process %d (%s) score %d\n", p->pid, p->comm, points);
force_sig(SIGKILL, p);
}
select_bad_process 函数实现解析
select_bad_process 是 Linux OOM 机制的核心函数,用于选择“最坏”进程(即占用内存最多的进程)进行杀死。以下是其实现逻辑的详细解析:
1. 函数签名
struct task_struct *select_bad_process(unsigned int *ppoints, unsigned long totalpages, const nodemask_t *nodemask, bool force_kill);
参数:
ppoints:输出参数,存储选择进程的 OOM 评分。
totalpages:系统总内存页数。
nodemask:节点掩码(NUMA 系统使用)。
force_kill:是否强制杀死进程。
2. 核心逻辑
struct task_struct *select_bad_process(unsigned int *ppoints, unsigned long totalpages, const nodemask_t *nodemask, bool force_kill)
{
struct task_struct *g, *p;
struct task_struct *chosen = NULL;
unsigned long chosen_points = 0;
// 遍历所有进程和线程
for_each_process_thread(g, p) {
unsigned int points;
// 跳过不可杀进程(如 init 进程、内核线程等)
if (oom_unkillable_task(p, NULL, nodemask))
continue;
// 计算进程的 OOM 评分
points = oom_badness(p, NULL, nodemask, totalpages);
if (!points || points < chosen_points)
continue;
// 更新选择的进程和评分
chosen = p;
chosen_points = points;
}
// 返回选择的进程
*ppoints = chosen_points;
return chosen;
}
3. 评分计算流程
跳过不可杀进程:如 init 进程、内核线程、OOM_DISABLE 进程等。
调用 oom_badness:计算进程的 OOM 评分(基于内存使用量和用户调整值)。
选择最高评分进程:遍历所有进程,保留评分最高的进程作为目标。
4. 评分计算公式
unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg, const nodemask_t *nodemask, unsigned long totalpages)
{
long points;
long adj;
// 跳过不可杀进程
if (oom_unkillable_task(p, memcg, nodemask))
return 0;
// 获取用户调整值(oom_score_adj)
adj = (long)p->signal->oom_score_adj;
if (adj == OOM_SCORE_ADJ_MIN || test_bit(MMF_OOM_SKIP, &p->mm->flags) || in_vfork(p))
return 0;
// 计算基础评分(物理内存 + 交换区 + 页表)
points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) + mm_pgtables_bytes(p->mm) / PAGE_SIZE;
// 调整评分(基于系统总内存)
adj *= totalpages / 1000;
points += adj;
return points > 0 ? points : 1;
}
基础评分:基于进程的 RSS(常驻内存)+ 交换区页数 + 页表大小。
用户调整:通过 oom_score_adj 参数调整评分(值越小优先级越高)。
5. 关键特性
实时性:遍历所有进程线程,实时选择评分最高进程。
公平性:结合内存使用量和用户调整值,避免误杀关键进程。
可扩展性:支持 NUMA 系统(通过 nodemask 参数)。
总结:select_bad_process 通过遍历所有进程,结合内存使用量和用户调整值,实时选择占用内存最多的进程进行杀死,确保系统内存安全。