Linux freezer机制

一、概述

系统进入suspended或进程被加入到cgroup冻结或解冻分组,用户进程和部分内核线程被冻结后,会剥夺执行cpu资源,解冻或唤醒后恢复正常。

二、进程冻结与解冻原理

2.1 进程冻结

用户进程和内核线程冻结的基本流程:

内核态线程可直接唤醒执行冻结操作,用户进程则需要在其返回到用户态时才能执行冻结操作。这是因为若其在内核态执行时被冻结,若其正好持有一些锁,则可能会导致死锁。为此,内核通过了一个比较巧妙的方式实现了冻结流程。它首先为该进程设置了一个信号pending标志TIF_SIGPENDING,但并不向该进程发送实际的信号,然后通过ipi唤醒该进程执行。由于ipi会进行进程内核的中断处理流程,当其处理完成后,会调用ret_to_user函数返回用户态,而该函数会调用信号处理函数检查是否有pending的中断需要处理,由于先前已经设置了信号的pending标志,因此会执行信号处理流程。此时,会发现进程冻结相关的全局变量已设置,故进程将执行冻结流程。

2.1.1 冻结用户进程

int freeze_processes(void)
{
        int error;

        error = __usermodehelper_disable(UMH_FREEZING);
        if (error)
                return error;

        /* Make sure this task doesn't get frozen */
        current->flags |= PF_SUSPEND_TASK;

        if (!pm_freezing)
                atomic_inc(&system_freezing_cnt);

        pm_wakeup_clear(0);
        pr_info("%s:Freezing user space processes ... ", STR_KERNEL_LOG_ENTER);
        pm_freezing = true;
        // true表示用户进程
        error = try_to_freeze_tasks(true);
        if (!error) {
                __usermodehelper_set_disable_depth(UMH_DISABLED);
                pr_cont("done.");
        }
        pr_cont("\n");
        BUG_ON(in_atomic());

        /*
         * Now that the whole userspace is frozen we need to disable
         * the OOM killer to disallow any further interference with
         * killable tasks. There is no guarantee oom victims will
         * ever reach a point they go away we have to wait with a timeout.
         */
        if (!error && !oom_killer_disable(msecs_to_jiffies(freeze_timeout_msecs)))
                error = -EBUSY;

        if (error)
                thaw_processes();
        return error;
}

static int try_to_freeze_tasks(bool user_only) {
    if (!user_only)
                freeze_workqueues_begin();

        while (true) {
                todo = 0;
                read_lock(&tasklist_lock);
                for_each_process_thread(g, p) {
                        if (p == current || !freeze_task(p))
                                continue;
                         ......
         }
         ......                       
}

bool freeze_task(struct task_struct *p)
{
        unsigned long flags;

        /*
         * This check can race with freezer_do_not_count, but worst case that
         * will result in an extra wakeup being sent to the task.  It does not
         * race with freezer_count(), the barriers in freezer_count() and
         * freezer_should_skip() ensure that either freezer_count() sees
         * freezing == true in try_to_freeze() and freezes, or
         * freezer_should_skip() sees !PF_FREEZE_SKIP and freezes the task
         * normally.
         */
         // 若进程设置了PF_FREEZER_SKIP,则不能冻结
        if (freezer_should_skip(p))
                return false;

        spin_lock_irqsave(&freezer_lock, flags);
        if (!freezing(p) || frozen(p)) {
                spin_unlock_irqrestore(&freezer_lock, flags);
                return false;
        }
        // 如果是用户进程,需要先发送伪信号,当进程返回用户空间时处理信号过程中被冻结,因为若其在内核态执行时被冻结,若其正好持有一些锁,则可能会导致死锁
        // 如果是内核线程,直接冻结,并将状态设置为TASK_INTERRUPTIBLE
        if (!(p->flags & PF_KTHREAD))
                fake_signal_wake_up(p);
        else
                wake_up_state(p, TASK_INTERRUPTIBLE);

        spin_unlock_irqrestore(&freezer_lock, flags);
        return true;
}
// kernel/kernel/signal.c

oid signal_wake_up_state(struct task_struct *t, unsigned int state)
{
        // 设置TIF_SIGPENDING信号,在get_signal函数中获取处理
        set_tsk_thread_flag(t, TIF_SIGPENDING);
        /*
         * TASK_WAKEKILL also means wake it up in the stopped/traced/killable
         * case. We don't check t->state here because there is a race with it
         * executing another processor and just now entering stopped state.
         * By using wake_up_state, we ensure the process will wake up and
         * handle its death signal.
         */
        if (!wake_up_state(t, state | TASK_INTERRUPTIBLE))
                kick_process(t);
}

bool get_signal(struct ksignal *ksig)
{
        struct sighand_struct *sighand = current->sighand;
        struct signal_struct *signal = current->signal;
        int signr;

        if (unlikely(uprobe_deny_signal()))
                return false;

        /*
         * Do this once, we can't return to user-mode if freezing() == T.
         * do_signal_stop() and ptrace_stop() do freezable_schedule() and
         * thus do not need another check after return.
         */
        try_to_freeze();
        ......
   }     
static inline bool try_to_freeze(void)
{
        if (!(current->flags & PF_NOFREEZE))
                debug_check_no_locks_held();
        return try_to_freeze_unsafe();
}

static inline bool try_to_freeze_unsafe(void)
{
        might_sleep();
        if (likely(!freezing(current)))
                return false;
        return __refrigerator(false);
}

2.1.2 冻结内核线程

int freeze_kernel_threads(void)
{
        int error;

        pr_info("%s:Freezing remaining freezable tasks ... ", STR_KERNEL_LOG_ENTER);

        pm_nosig_freezing = true;
        // 传入false表示内核线程,代码流程同2.1.1中try_to_freeze_tasks
        error = try_to_freeze_tasks(false);
        if (!error)
                pr_cont("done.");

        pr_cont("\n");
        BUG_ON(in_atomic());

        if (error)
                thaw_kernel_threads();
        return error;
}

最后会调用kthread_freezable_should_stop函数执行内线线程冻结:

bool kthread_freezable_should_stop(bool *was_frozen)
{
        …
        if (unlikely(freezing(current)))
                frozen = __refrigerator(true);

        if (was_frozen)
                *was_frozen = frozen;
        return kthread_should_stop();
}

2.1.3 小结

进程被冻结主要做了以下事情:

1)设置task状态为TASK_UNINTERRUPTIBLE,表示不能加入就绪队列被调度

2)设置task的flag为PF_FROZEN,表示进程已被冻结

3)调用schedule函数,将task从cpu上调度出来,不让其执行cpu,将寄存器堆栈信息保存到thread_info->cpu_context中

4)进程被解冻时,重新被调度,退出for循环,继续往下执行,重新设置task的状态为TASK_RUNNING

bool __refrigerator(bool check_kthr_stop)
{
        /* Hmm, should we be allowed to suspend when there are realtime
           processes around? */
        bool was_frozen = false;
        long save = current->state;

        pr_debug("%s entered refrigerator\n", current->comm);

        for (;;) {
                // 设置当前task状态为TASK_UNINTERRUPTIBLE
                set_current_state(TASK_UNINTERRUPTIBLE);

                spin_lock_irq(&freezer_lock);
                // 设置当前task的flag为PF_FROZEN,表示已冻结
                current->flags |= PF_FROZEN;
                if (!freezing(current) ||
                    (check_kthr_stop && kthread_should_stop()))
                        current->flags &= ~PF_FROZEN;
                trace_android_rvh_refrigerator(pm_nosig_freezing);
                spin_unlock_irq(&freezer_lock);

                if (!(current->flags & PF_FROZEN))
                        break;
                was_frozen = true;
                // 将task从cpu上调度出来,不让其执行cpu,执行schedule函数,会将寄存器堆栈信息保存到thread_info->cpu_context中
                // task的上下文保存后,停留在该处,下次被唤醒时,重新被调度,退出for循环,往下执行
                schedule();
        }
        pr_debug("%s left refrigerator\n", current->comm);

        /*
         * Restore saved task state before returning.  The mb'd version
         * needs to be used; otherwise, it might silently break
         * synchronization which depends on ordered task state change.
         */
         // 被唤醒时,重新设置task的状态
        set_current_state(save);

        return was_frozen;
}
EXPORT_SYMBOL(__refrigerator);

2.2 进程解冻或唤醒

进程解冻会调用调度模块进行进程唤醒,状态设置为runnable或running.

void __thaw_task(struct task_struct *p)
{
        unsigned long flags;
        const struct cpumask *mask = task_cpu_possible_mask(p);

        spin_lock_irqsave(&freezer_lock, flags);
        /*
         * Wake up frozen tasks. On asymmetric systems where tasks cannot
         * run on all CPUs, ttwu() may have deferred a wakeup generated
         * before thaw_secondary_cpus() had completed so we generate
         * additional wakeups here for tasks in the PF_FREEZER_SKIP state.
         */
        if (frozen(p) || (frozen_or_skipped(p) && mask != cpu_possible_mask))
                // 调用调度模块唤醒进程
                wake_up_process(p);
        spin_unlock_irqrestore(&freezer_lock, flags);
}

线程入队操作并标记线程p为runnable状态,线程标记为TASK_RUNNING,并执行唤醒抢占操作。

int wake_up_process(struct task_struct *p)
{
        WARN_ON(task_is_stopped_or_traced(p));
        return try_to_wake_up(p, TASK_NORMAL, 0);
}

static int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
        unsigned long flags;
        int cpu, success = 0;

        /*
         * If we are going to wake up a thread waiting for CONDITION we
         * need to ensure that CONDITION=1 done by the caller can not be
         * reordered with p->state check below. This pairs with mb() in
         * set_current_state() the waiting thread does.
         */
        smp_mb__before_spinlock();
        raw_spin_lock_irqsave(&p->pi_lock, flags);
        if (!(p->state & state))
                goto out;

        success = 1; /* we're going to change ->state */
        cpu = task_cpu(p);

    /* 使用内存屏障保证p->on_rq的数值是最新的。如果线程已经在运行队列rq里面了,即进程已经处于
    runnable/running状态。ttwu_remote目的是由于线程 p已经在运行队列rq里面了,并且没有完全
    取消调度,再次唤醒的话,需要将线程的状态翻转:将状态设置为TASK_RUNNING,这样
    线程就一直在运行队列里面了。这种情况则直接退出后续流程,并对调度状态/数据进行统计 */
        if (p->on_rq && ttwu_remote(p, wake_flags))
                goto stat;

#ifdef CONFIG_SMP
    /* 等待在其他cpu上的线程调度完成 */
        while (p->on_cpu)
                cpu_relax();
        /*
         * Pairs with the smp_wmb() in finish_lock_switch().
         */
        smp_rmb();

        p->sched_contributes_to_load = !!task_contributes_to_load(p);
        p->state = TASK_WAKING;
        
    /* 根据进程的所属的调度类调用相应的回调函数 */
        if (p->sched_class->task_waking)
                p->sched_class->task_waking(p);
        /* 根据线程p相关参数和系统状态,为线程p选择合适的cpu */
        cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);
    /* 如果选择的cpu与线程p当前所在的cpu不相同,则将线程的wake_flags设置为需要迁移,然后将线程p迁移到cpu上 */
        if (task_cpu(p) != cpu) {
                wake_flags |= WF_MIGRATED;
                set_task_cpu(p, cpu);
        }
#endif /* CONFIG_SMP */
        /* 线程p入队操作并标记线程p为runnable状态,同时唤醒抢占 */
        ttwu_queue(p, cpu);
stat:
    /* 与调度相关的统计 */
        ttwu_stat(p, cpu, wake_flags);
out:
        raw_spin_unlock_irqrestore(&p->pi_lock, flags);

        return success;
}

static void ttwu_queue(struct task_struct *p, int cpu)
{
        struct rq *rq = cpu_rq(cpu);

#if defined(CONFIG_SMP)
        if (sched_feat(TTWU_QUEUE) && !cpus_share_cache(smp_processor_id(), cpu)) {
                sched_clock_cpu(cpu); /* sync clocks x-cpu */
                ttwu_queue_remote(p, cpu);
                return;
        }
#endif

        raw_spin_lock(&rq->lock);
        ttwu_do_activate(rq, p, 0);
        raw_spin_unlock(&rq->lock);
}

static void ttwu_do_activate(struct rq *rq, struct task_struct *p, int wake_flags)
{
#ifdef CONFIG_SMP
        if (p->sched_contributes_to_load)
                rq->nr_uninterruptible--;
#endif
        //将线程p加入运行队列rq中
        ttwu_activate(rq, p, ENQUEUE_WAKEUP | ENQUEUE_WAKING);
    //将任务标记为可运行的,并执行唤醒抢占。
        ttwu_do_wakeup(rq, p, wake_flags);
}

static void ttwu_activate(struct rq *rq, struct task_struct *p, int en_flags)
{
        activate_task(rq, p, en_flags);
        p->on_rq = TASK_ON_RQ_QUEUED;

        /* if a worker is waking up, notify workqueue */
        if (p->flags & PF_WQ_WORKER)
                wq_worker_waking_up(p, cpu_of(rq));
}

static void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
{
        update_rq_clock(rq);
        sched_info_queued(rq, p);
        p->sched_class->enqueue_task(rq, p, flags);
}

void activate_task(struct rq *rq, struct task_struct *p, int flags)
{
        if (task_contributes_to_load(p))
                rq->nr_uninterruptible--;

        enqueue_task(rq, p, flags);
}

//将任务标记为可运行的,并执行唤醒抢占操作
static void ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
{
        check_preempt_curr(rq, p, wake_flags);
        trace_sched_wakeup(p, true);
        
    //将线程p的状态设置为TASK_RUNNING
        p->state = TASK_RUNNING;
#ifdef CONFIG_SMP
        if (p->sched_class->task_woken)
                p->sched_class->task_woken(rq, p);

        if (rq->idle_stamp) {
                u64 delta = rq_clock(rq) - rq->idle_stamp;
                u64 max = 2*rq->max_idle_balance_cost;

                update_avg(&rq->avg_idle, delta);

                if (rq->avg_idle > max)
                        rq->avg_idle = max;

                rq->idle_stamp = 0;
        }
#endif
}


/*在增加nr_running之前调用enqueue_task()函数。在这里,将更新公平调度统计数据,然后将线程
    p的调度实体放入rbtree红黑树中*/
static void enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
        struct cfs_rq *cfs_rq;
        struct sched_entity *se = &p->se;

        for_each_sched_entity(se) {
                if (se->on_rq)
                        break;
                cfs_rq = cfs_rq_of(se);
                // 调度实体存入就绪队列
                enqueue_entity(cfs_rq, se, flags);

                /*
                 * end evaluation on encountering a throttled cfs_rq
                 *
                 * note: in the case of encountering a throttled cfs_rq we will
                 * post the final h_nr_running increment below.
                */
                if (cfs_rq_throttled(cfs_rq))
                        break;
                cfs_rq->h_nr_running++;

                flags = ENQUEUE_WAKEUP;
        }

        for_each_sched_entity(se) {
                cfs_rq = cfs_rq_of(se);
                cfs_rq->h_nr_running++;

                if (cfs_rq_throttled(cfs_rq))
                        break;
                // 更新cfs队列权重
                update_cfs_shares(cfs_rq);
                // 更新调度实体的平均负载
                update_entity_load_avg(se, 1);
        }

        if (!se) {
                update_rq_runnable_avg(rq, rq->nr_running);
                add_nr_running(rq, 1);
        }
        hrtick_update(rq);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/410366.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JSP实现数据传递与保存(二)

一、session对象 session机制是一种服务器端的机制,在服务器端保存信息用于存储与用户相关的会话信息 1.1 session与窗口的关系 每个session对象都与一个浏览器窗口对应,重新开启一个浏览器窗口,可以重新创建一个session对象(不…

IO进程线程复习:进程线程

1.进程的创建 #include<myhead.h>int main(int argc, const char *argv[]) {printf("hello world\n");//父进程执行的内容int num520;//在父进程中定义的变量pid_t pidfork();//创建子进程if(pid>0){while(1){printf("我是父进程&#xff0c;num%d\n&…

input输入框过滤非金额内容保留一个小数点和2位小数

这篇是输入框过滤非金额内容保留一个小数点和2位小数&#xff0c;金额的其他格式化可以看这篇文章常用的金额数字的格式化方法 js方法直接使用 该方式可以直接使用过滤内容&#xff0c;也可以到onInput或onblur等地方过滤&#xff0c;自行使用 /*** 非金额字符格式化处理* p…

有趣的CSS - 弹跳的圆

大家好&#xff0c;我是 Just&#xff0c;这里是「设计师工作日常」&#xff0c;今天分享的是用css写一个好玩的不停弹跳变形的圆。 《有趣的css》系列最新实例通过公众号「设计师工作日常」发布。 目录 整体效果核心代码html 代码css 部分代码 完整代码如下html 页面css 样式页…

解读2024生物发酵展览会-蓝帕控制阀门

参展企业介绍 感谢你正在或即将使用LAPAR系列产品&#xff0c;感谢你关注LAPAR&#xff01; LAPAR&#xff0c;流体控制领域的国际品牌之一&#xff0c;总部位于意大利米兰&#xff0c;成立多年以来&#xff0c;LAPAR凭借其完善的网络体系、优秀的产品质量、一体式的客户解决…

蓝桥杯-成绩分析

许久不敲代码&#xff0c;库名也忘了&#xff0c;精度设置还有求最大最小值都是常规题了。 #include <iostream> #include <iomanip> using namespace std; int main() { //一种不用开数组的方法 int n; cin>>n; int top0; int low100;//确定最大…

Uva 101: 木块问题(The Blocks Problem)

看着算法书看到了这一题&#xff0c;想着不能只看不做&#xff0c;就想着做了一下 算法书上的描述太抽象了&#xff0c;就网上找了其他的描述 当然去看英文描述是最准确的&#xff0c;算法书上说是哪一个oj网来着&#xff1f;我给忘了 STL还是很好用的 代码如下&#xff1a; …

【Vue3】学习watch监视:深入了解Vue3响应式系统的核心功能(下)

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

【Android12】Monkey压力测试源码执行流程分析

Monkey压力测试源码执行流程分析 Monkey是Android提供的用于应用程序自动化测试、压力测试的测试工具。 其源码路径(Android12)位于 /development/cmds/monkey/部署形式为Java Binary # development/cmds/monkey/Android.bp // Copyright 2008 The Android Open Source Proj…

Windows 安装Redis(图文详解)

一、Redis是什么数据库&#xff1f; Remote Dictionary Server(Redis) 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库&#xff0c;并提供多种语言的 API&#xff0c;是跨平台的非关系型数据库。 …

多线程和并发

线程 进程&#xff1a;在操作系统中运行的程序&#xff0c;一个进程可以包含多个线程 程序就是指令和数据的有序集合&#xff0c;静态概念 进程就是执行程序的一次执行过程&#xff0c;动态概念系统资源分配的单元 一个进程中包含多个线程&#xff0c;一个进程至少包含一个线…

图解KMP算法

目录 1.最长公共前后缀1.1前缀1.2后缀1.3最长公共前后缀 2、KMP算法过程2.1例子12.2例子22.3Python代码&#xff1a;2.4next数组的计算过程 1.最长公共前后缀 1.1前缀 前缀说的是一个字符串除了最后一个字符以外&#xff0c;所有的子串都算是前缀。 前缀字符串&#xff1a;A…

KubeSphere实战

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 知…

49.仿简道云公式函数实战-文本函数-Ip

1. Ip函数 获取当前用户的ip地址 注意是Ipv4的地址 2. 函数用法 IP() 3. 函数示例 获取当前用户的ip地址IP() 4. 代码实战 首先我们在function包下创建text包&#xff0c;在text包下创建IpFunction类&#xff0c;代码如下&#xff1a; package com.ql.util.express.sel…

11:日志分析系统ELK|Elasticsearch|kibana

日志分析系统ELK&#xff5c;Elasticsearch&#xff5c;kibana 日志分析系统ELKELK概述Elasticsearch安装Elasticsearch部署Elasticsearch集群Elasticsearch插件 熟悉Elasticsearch的API调用_cat API创建 tedu 索引使用 PUT 方式增加数据查询数据修改数据删除数据 KibanaKibana…

(挖坑) Python调用图工具

基本效果 输入 #!/usr/bin/env pythonThis example demonstrates a simple use of pycallgraph.from pycallgraph import PyCallGraph from pycallgraph.output import GraphvizOutputclass Banana:def eat(self):passclass Person:def __init__(self):self.no_bananas()def…

Xcode与Swift开发小记

引子 鉴于React Native目前版本在iOS上开发遇到诸多问题&#xff0c;本以为搞RN只需理会Javascript开发&#xff0c;没想到冒出CocoaPod的一堆编译问题。所以横下一条心&#xff0c;决定直接进攻iOS本身。不管你是用React Native&#xff0c;还是用Flutter&#xff0c;iOS下的…

算能RISC-V通用云开发空间编译pytorch @openKylin留档

终于可以体验下risc-v了&#xff01; 操作系统是openKylin&#xff0c;算能的云空间 尝试编译安装pytorch 首先安装git apt install git 然后下载pytorch和算能cpu的库&#xff1a; git clone https://github.com/sophgo/cpuinfo.git git clone https://github.com/pytorc…

java农产品商城商城计算机毕业设计包运行调试讲解

jsp mysql农业商城 特效&#xff1a;js产品轮播 功能&#xff1a; 前台&#xff1a; 1.绿色水果 图文列表 详情 2.新闻动态 文章标题列表 详情 3.有机蔬菜 图文列表 详情 4.有机谷物 图文列表 详情 5.有机大米 图文列表 详情 6.用户注册 登陆&#xff08;选择用户和管…

c++ 广度优先搜索(Breadth-First Search,BFS)

广度优先搜索&#xff08;Breadth-First Search&#xff0c;BFS&#xff09;是一种图遍历算法&#xff0c;通常用于搜索或遍历树和图等数据结构。其基本思想是先访问起始顶点&#xff0c;然后逐层遍历其相邻的顶点&#xff0c;直到找到目标顶点或遍历完所有顶点。 BFS通常使用…