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_INTERRUPTIBLEif (!(p->flags & PF_KTHREAD))fake_signal_wake_up(p);elsewake_up_state(p, TASK_INTERRUPTIBLE);spin_unlock_irqrestore(&freezer_lock, flags);return true;
}
// kernel/kernel/signal.coid 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_taskserror = 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 realtimeprocesses around? */bool was_frozen = false;long save = current->state;pr_debug("%s entered refrigerator\n", current->comm);for (;;) {// 设置当前task状态为TASK_UNINTERRUPTIBLEset_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;}
#endifraw_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_SMPif (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_RUNNINGp->state = TASK_RUNNING;
#ifdef CONFIG_SMPif (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.mzph.cn/news/697512.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024开年,手机厂商革了自己的命

文|刘俊宏 编|王一粟 2024开年,AI终端的号角已经由手机行业吹响。 OPPO春节期间就没闲着,首席产品官刘作虎在大年三十就迫不及待地宣布,OPPO正式进入AI手机时代。随后在开年后就紧急召开了AI战略发布会,…

GaussDB SQL调优:建立合适的索引

背景 GaussDB是华为公司倾力打造的自研企业级分布式关系型数据库,该产品具备企业级复杂事务混合负载能力,同时支持优异的分布式事务,同城跨AZ部署,数据0丢失,支持1000扩展能力,PB级海量存储等企业级数据库…

昨天Google发布了最新的开源模型Gemma,今天我来体验一下

前言 看看以前写的文章,业余搞人工智能还是很早之前的事情了,之前为了高工资,一直想从事人工智能相关的工作都没有实现。现在终于可以安静地系统地学习一下了。也是一边学习一边写博客记录吧。 昨天Google发布了最新的开源模型Gemma&#xf…

电商数据采集的几个标准

面对体量巨大的电商数据,很多品牌会选择对自己有用的数据进行分析,比如在控价过程中,需要对商品的价格数据进行监测,或者是需要做数据分析时,则需要采集到商品的价格、销量、评价量、标题、店铺名等信息,数…

Unity中.Net与Mono的关系

什么是.NET .NET是一个开发框架,它遵循并采用CIL(Common Intermediate Language)和CLR(Common Language Runtime)两种约定, CIL标准为一种编译标准:将不同编程语言(C#, JS, VB等)使用各自的编译器,按照统…

JavaScript 原始值和引用值在变量复制时的异同

相比于其他语言,JavaScript 中的变量可谓独树一帜。正如 ECMA-262 所规定的,JavaScript 变量是松散类型的,而且变量不过就是特定时间点一个特定值的名称而已。由于没有规则定义变量必须包含什么数据类型,变量的值和数据类型在脚本…

【Python笔记-设计模式】原型模式

一、说明 原型模式是一种创建型设计模式, 用于创建重复的对象,同时又能保证性能。 使一个原型实例指定了要创建的对象的种类,并且通过拷贝这个原型来创建新的对象。 (一) 解决问题 主要解决了对象的创建与复制过程中的性能问题。主要针对…

redhawk:使用ipf文件反标instance power

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 往期文章链接: Redhawk:Input Data Preparation 使用ptpx和redhawk报告功耗时差别总是很大,如果需要反标top/block的功耗值可以在gsr文件中使用BLOCK_POWER_FOR_SCALING的命令

Verilog刷题笔记35

题目: Create a 1-bit wide, 256-to-1 multiplexer. The 256 inputs are all packed into a single 256-bit input vector. sel0 should select in[0], sel1 selects bits in[1], sel2 selects bits in[2], etc. 解法: module top_module( input [255:…

Spring Cloud Alibaba-05-Gateway网关-02-断言(Predicate)使用

Lison <dreamlison163.com>, v1.0.0, 2023.10.20 Spring Cloud Alibaba-05-Gateway网关-02-断言(Predicate)使用 文章目录 Spring Cloud Alibaba-05-Gateway网关-02-断言(Predicate)使用通过时间匹配通过 Cookie 匹配通过 Header 匹配通过 Host 匹配通过请求方式匹配通…

C# CAD2016 cass10宗地Xdata数据写入

一、 查看cass10写入信息 C# Cad2016二次开发获取XData信息&#xff08;二&#xff09; 一共有81条数据 XData value: QHDM XData value: 121321 XData value: SOUTH XData value: 300000 XData value: 141121JC10720 XData value: 权利人 XData value: 0702 XData value: YB…

java面试题之mybatis篇

什么是ORM&#xff1f; ORM&#xff08;Object/Relational Mapping&#xff09;即对象关系映射&#xff0c;是一种数据持久化技术。它在对象模型和关系型数据库直接建立起对应关系&#xff0c;并且提供一种机制&#xff0c;通过JavaBean对象去操作数据库表的数据。 MyBatis通过…

MATLAB练习题:randperm函数的练习题

​讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili MATLAB中有一个非常有用的函数&#xff1a;randperm函数&…

华为算法题 go语言或者ptython

1 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返…

如何进行高性能架构的设计

一、前端优化 减少请求次数页面静态化边缘计算 增加缓存控制&#xff1a;请求头 减少图像请求次数&#xff1a;多张图片变成 一张。 减少脚本的请求次数&#xff1a;css和js压缩&#xff0c;将多个文件压缩成一个文件。 二、页面静态化 三、边缘计算 后端优化 从三个方面进…

adb-monkey命令

目录 adb shell monkey -p/-v 包名 次数 1、指定一个包 2、指定多个包 3、不指定包 Event percentages&#xff08;事件百分比&#xff09; 常见参数 --throttle 延迟时间 单位毫秒 --pct-touch 设定触屏事件生成的百分比 --pct-motion 设定滑动事件生成…

Redis高性能原理

redis大家都知道拥有很高的性能&#xff0c;每秒可以支持上万个请求&#xff0c;这里探讨下它高性能的原理。单线程架构和io多路复用技术。 一&#xff0c;单线程架构 单线程架构指的是命令执行核心线程是单线程的&#xff0c;数据持久化、同步、异步删除是其他线程在跑的。re…

亿道丨三防平板丨加固平板丨三防加固平板丨改善资产管理

库存资产管理中最重要的部分之一是准确性&#xff1b;过时的库存管理技术会增加运输过程中人为错误、物品丢失或纸张损坏的风险。如今随着三防平板电脑的广泛使用&#xff0c;库存管理也迎来了好帮手&#xff0c;通过使用三防平板电脑能够确保库存管理、数据存储和记录保存的准…

React18源码: React调度中的3种优先级类型和Lane的位运算

优先级类型 React内部对于优先级的管理&#xff0c;贯穿运作流程的4个阶段&#xff08;从输入到输出&#xff09;&#xff0c;根据其功能的不同&#xff0c;可以分为3种类型&#xff1a; 1 &#xff09;fiber优先级(LanePriority) 位于 react-reconciler包&#xff0c;也就是L…

【操作系统】磁盘存储空间的管理

实验5 磁盘存储空间的管理 一、实验目的 磁盘是用户存放程序和数据的存储设备&#xff0c;磁盘管理的主要目的是充分有效地利用磁盘空间。本实验模拟实现磁盘空间的分配与回收&#xff0c;使学生对磁盘空间的管理有一个较深入的理解。 二、实验内容 实验任务&#xff1a;用位…