Linux kernel OOM机制详解

目录

一、什么是OOM机制

二、OOM工作原理

2.1 如何选择被kill的目标进程

2.2 kill进程

三、OOM相关节点

四、OOM、kswapd、lmkd三者区别

一、什么是OOM机制

OOM机制(Out of Memory Killer)是Linux内核中的一种内存管理机制,用于在系统物理内存耗尽时选择并杀死一个或多个进程,以释放内存并防止系统崩溃。当系统无法满足新的物理内存分配请求,并且所有其他内存回收机制(如内存规整、页帧回收等)都失败时,OOM Killer会被触发。

二、OOM工作原理

基本原理:遍历系统中所有进程,根据每个进程的oom_adj、RSS,swap file、及页表占用的内存情况计算得到points,选择points最高的目标进程,杀死该目标进程,释放系统内存。

// kernel/mm/page_alloc.c

static inline struct page *
__alloc_pages_may_oom(gfp_t gfp_mask, unsigned int order,
        const struct alloc_context *ac, unsigned long *did_some_progress)
{
    struct oom_control oc = {
            .zonelist = ac->zonelist,
            .nodemask = ac->nodemask,
            .memcg = NULL,
            .gfp_mask = gfp_mask,
            .order = order,
    };
    ...
    out_of_memory(&oc);
    ...
}

// kernel/mm/oom_kill.c
bool out_of_memory(struct oom_control *oc)
{
    ...
    // 选择一个得分最高的进程,详见2.1
    select_bad_process(oc);
    // 杀进程,详见2.2
    oom_kill_process(oc, "Out of memory");
    ...
}

2.1 如何选择被kill的目标进程

遍历所有进程,逐个计算当前进程的oom_score,与上一个进程进行比较, 如果小的会话就会goto next 返回,如果大的话就会更新oc->chosen 的task 和 chosen_points, 也就是目前最高的oom_score,循环进行。


static void select_bad_process(struct oom_control *oc)
{
        // 1. 初始化为最小的Long值,这个值是用来比较所有的oom_score值,
        // 最后谁的值最大就选中哪个进程
        oc->chosen_points = LONG_MIN;

        if (is_memcg_oom(oc))
                mem_cgroup_scan_tasks(oc->memcg, oom_evaluate_task, oc);
        else {
                struct task_struct *p;

                rcu_read_lock();
                // 2. 遍历系统中所有的进程,找到oom_score得分最高的进程
                for_each_process(p)
                        if (oom_evaluate_task(p, oc))
                                break;
                rcu_read_unlock();
        }
}

static int oom_evaluate_task(struct task_struct *task, void *arg)
{
        struct oom_control *oc = arg;
        long points;
        // 1. 系统中init进程和内核进程不能kill
        if (oom_unkillable_task(task))
                goto next;

       ...
       
        // 2.计算进程的oom_score,记做points
        points = oom_badness(task, oc->totalpages);
        // 若当前的进程points为LONG_MIN(最小得分)或小于之前进程的points,直接返回
        if (points == LONG_MIN || points < oc->chosen_points)
                goto next;

        // 否则,更新chosen_points和task
select:
        if (oc->chosen)
                put_task_struct(oc->chosen);
        get_task_struct(task);
        oc->chosen = task;
        oc->chosen_points = points;   // 更新当前最新的points
next:
        return 0;

}

/* return true if the task is not adequate as candidate victim task. */
static bool oom_unkillable_task(struct task_struct *p)
{
        // 系统init进程不能kill
        if (is_global_init(p))
                return true;
        // 内核进程不能kill
        if (p->flags & PF_KTHREAD)
                return true;
        return false;
}

long oom_badness(struct task_struct *p, unsigned long totalpages)
{
        long points;
        long adj;

        // 1. 获取task的oom_score_adj
        adj = (long)p->signal->oom_score_adj;

        ...

         // 2. 根据RSS,swap file或swap device上占用的内存情况以及页表占用的内存情况计算得到points
         // MM_SWAPENTS--- 进程的内存页面被交换到磁盘上的个数
        points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) +
                mm_pgtables_bytes(p->mm) / PAGE_SIZE;
                
        ...
        // 3. 计算综合得分
        adj *= totalpages / 1000;
        points += adj;

        return points;
}

2.2 kill进程

杀死points最高的目标进程,并将与其共享内存描述符的其他进程也被杀死,以释放内存。

static void oom_kill_process(struct oom_control *oc, const char *message)
{
        struct task_struct *victim = oc->chosen;
        ...
        __oom_kill_process(victim, message);

        ...
}

static void __oom_kill_process(struct task_struct *victim, const char *message)
{
        struct task_struct *p;
        struct mm_struct *mm;
        bool can_oom_reap = true;

        p = find_lock_task_mm(victim);
        if (!p) {
                pr_info("%s: OOM victim %d (%s) is already exiting. Skip killing the task\n",
                        message, task_pid_nr(victim), victim->comm);
                put_task_struct(victim);
                return;
        } else if (victim != p) {
                get_task_struct(p);
                put_task_struct(victim);
                victim = p;
        }

        /* Get a reference to safely compare mm after task_unlock(victim) */
        // 1. 获取内存描述符引用
        mm = victim->mm;
        mmgrab(mm);

        /* Raise event before sending signal: task reaper must see this */
        // 2.记录OOM事件
        count_vm_event(OOM_KILL);
        memcg_memory_event_mm(mm, MEMCG_OOM_KILL);

        /*
         * We should send SIGKILL before granting access to memory reserves
         * in order to prevent the OOM victim from depleting the memory
         * reserves from the user space under its control.
         */
         // 3.向目标进程发送SIGKILL的信号
        do_send_sig_info(SIGKILL, SEND_SIG_PRIV, victim, PIDTYPE_TGID);
        mark_oom_victim(victim);
        pr_err("%s: Killed process %d (%s) total-vm:%lukB, anon-rss:%lukB, file-rss:%lukB, shmem-rss:%lukB, UID:%u pgtables:%lukB oom_score_adj:%hd\n",
                message, task_pid_nr(victim), victim->comm, K(mm->total_vm),
                K(get_mm_counter(mm, MM_ANONPAGES)),
                K(get_mm_counter(mm, MM_FILEPAGES)),
                K(get_mm_counter(mm, MM_SHMEMPAGES)),
                from_kuid(&init_user_ns, task_uid(victim)),
                mm_pgtables_bytes(mm) >> 10, victim->signal->oom_score_adj);
        task_unlock(victim);


        // 4.杀死共享内存描述符的其它进程
        for_each_process(p) {
                if (!process_shares_mm(p, mm))
                        continue;
                if (same_thread_group(p, victim))
                        continue;
                if (is_global_init(p)) {
                        can_oom_reap = false;
                        set_bit(MMF_OOM_SKIP, &mm->flags);
                        pr_info("oom killer %d (%s) has mm pinned by %d (%s)\n",
                                        task_pid_nr(victim), victim->comm,
                                        task_pid_nr(p), p->comm);
                        continue;
                }
                /*
                 * No kthread_use_mm() user needs to read from the userspace so
                 * we are ok to reap it.
                 */
                if (unlikely(p->flags & PF_KTHREAD))
                        continue;
                do_send_sig_info(SIGKILL, SEND_SIG_PRIV, p, PIDTYPE_TGID);
        }
        rcu_read_unlock();

        if (can_oom_reap)
                queue_oom_reaper(victim);
    
        // 5.释放内存描述符和进程引用
        mmdrop(mm);
        put_task_struct(victim);
}

共享内存描述符的常见场景:

  • 线程组共享

在Linux内核中,同一个线程组(thread group)中的所有线程共享同一个 mm_struct。线程组是由同一个进程创建的所有线程组成的集合,它们共享进程的资源,包括虚拟地址空间。

  • 进程间共享

通过 fork()系统调用创建的子进程会共享父进程的 mm_struct,直到子进程执行 exec()系列系统调用之前。执行 exec()后,子进程会加载新的程序,并创建一个新的 mm_struct。

  • 内存映射共享

通过内存映射(memory-mapped files)或共享内存(shared memory)区域,不同的进程可以共享相同的内存区域。这些共享的内存区域会在它们的 mm_struct中有所体现。

三、OOM相关节点

在Linux内核中,与OOM(Out of Memory)相关的'/proc'节点主要用于配置和监控OOM Killer的行为。以下是一些常见的与OOM相关的/proc节点:

  • /proc/sys/vm/overcommit_memory

作用:控制内存分配策略

值:

0:启发式过度提交(默认值),允许适度过度提交内存

1:总是过度提交,不检查内存是否足够

2:不允许过度提交,根据overcommit_ratio或overcommit_kB进行检查

  • /proc/sys/vm/overcommit_ratio

作用:当overcommit_memory设置为2时,控制物理内存的百分比,用于计算允许的内存分配

值:一个整数,表示物理内存的百分比

  • /proc/sys/vm/oom_kill_allocating_task

作用:控制OOM Killer是否杀死正在申请内存的进程

值:

0:不杀死正在申请内存的进程(默认值)

1:杀死正在申请内存的进程

  • /proc/sys/vm/panic_on_oom

作用:控制OOM发生时的行为

值:

0:调用OOM Killer(默认值)

1:系统panic(崩溃)

2:在某些情况下调用OOM Killer,在其他情况下系统panic

  • /proc/sys/vm/oom_dump_tasks

作用:控制是否在OOM发生时打印任务信息

值:

0:不打印任务信息

1:打印任务信息(默认值)

  • /proc/<pid>/oom_score

作用:显示进程的OOM分数,分数越高,越有可能被OOM Killer杀死

值:一个整数,表示进程的OOM分数

  • /proc/<pid>/oom_score_adj

作用:调整进程的OOM分数,范围从-1000(不会被杀死)到1000(优先被杀死)

值:一个整数,表示调整的OOM分数

  • /proc/<pid>/oom_adj

作用:旧版本的OOM分数调整节点,已被oom_score_adj替代

值:一个整数,范围从-17到15

通过这些/proc节点,可以配置和监控OOM Killer的行为,以确保系统在内存紧张时的稳定性和性能。如,可以通过调整oom_score_adj来保护重要进程不被OOM Killer杀死,或者通过设置panic_on_oom来控制系统在OOM发生时的行为。

四、OOM、kswapd、lmkd三者区别

所属空间

触发条件

实现原理

OOM

Kernel space

当系统无法满足新的物理内存分配请求,并且所有其他内存回收机制(如内存规整、页帧回收等)都失败

遍历系统中所有进程,根据oom_adj、RSS,swap file、及页表占用的内存情况计算得到points,选择points最高的进程,杀死该进程,释放系统内存。

kswapd

Kernel space

当系统的空闲内存低于一定阈值时,kswapd会被唤醒,开始回收内存页面

kswapd是Linux内核中的一个守护进程,负责内存回收,通过页面置换算法(如LRU)将不常用的页面交换到磁盘(swap)或回收页面缓存,以释放内存。

lmkd

User space

psi内存压力达到设置的压力等级

Android lmkd采用epoll方式监听linux内核psi内存压力等级的触发事件,并根据psi内存压力等级及进程优先级(oom_adj)来选择目标进程并查杀,缓解系统内存的压力。

另一篇文章讲解lmkd:Android lmkd机制详解-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值