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.pswp.cn/news/697512.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/697512.shtml
英文地址,請注明出處:http://en.pswp.cn/news/697512.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

設計模式-建造者模式(Builder Pattern)

一、建造者模式說明 建造者模式(Builder Pattern)是一種創建型設計模式,它的主要目的是將一個復雜對象的構建過程與其表示分離,使得同樣的構建過程可以創建不同的表示。 在建造者模式中,通常涉及以下幾個角色&#xf…

多業務場景下對于redis分布式鎖的一些思考

現在讓你寫一個Redis分布式鎖 大概率你會先寫一個框架 public Boolean setIfAbsent(String key, Object value,Long timeout) {try {return Boolean.TRUE.equals(objectRedisTemplate.opsForValue().setIfAbsent(key, value,timeout,TimeUnit.SECONDS));} catch (Exception e) …

2024開年,手機廠商革了自己的命

文|劉俊宏 編|王一粟 2024開年,AI終端的號角已經由手機行業吹響。 OPPO春節期間就沒閑著,首席產品官劉作虎在大年三十就迫不及待地宣布,OPPO正式進入AI手機時代。隨后在開年后就緊急召開了AI戰略發布會,…

【Antd】Form 表單獲取不到 Input 的值

文章目錄 今天遇到了一個奇怪的bug,Form表單中的Input組件的值,不能被Form獲取,導致輸入了內容,但是表單提交的時候值為undefined 報錯代碼 import { Button, Form, Input } from antd; import React from react;const App: Rea…

GaussDB SQL調優:建立合適的索引

背景 GaussDB是華為公司傾力打造的自研企業級分布式關系型數據庫,該產品具備企業級復雜事務混合負載能力,同時支持優異的分布式事務,同城跨AZ部署,數據0丟失,支持1000擴展能力,PB級海量存儲等企業級數據庫…

SQL中為什么不要使用1=1

最近看幾個老項目的SQL條件中使用了11,想想自己也曾經這樣寫過,略有感觸,特別拿出來說道說道。 編寫SQL語句就像炒菜,每一種調料的使用都可能會影響菜品的最終味道,每一個SQL條件的加入也可能會影響查詢的執行效率。那…

昨天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 變量是松散類型的,而且變量不過就是特定時間點一個特定值的名稱而已。由于沒有規則定義變量必須包含什么數據類型,變量的值和數據類型在腳本…

mysql.service is not a native service, redirecting to systemd-sysv-install

字面意思:mysql.service不是本機服務,正在重定向到systemd sysv安裝 在CentOS上使用Systemd管理MySQL服務的具體步驟如下: 1、創建MySQL服務單元文件: 首先,你需要創建一個Systemd服務單元文件,以便Syste…

【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…

2.居中方式總結

居中方式總結 經典真題 怎么讓一個 div 水平垂直居中 盒子居中 首先題目問到了如何進行居中&#xff0c;那么居中肯定分 2 個方向&#xff0c;一個是水平方向&#xff0c;一個是垂直方向。 水平方向居中 水平方向居中很簡單&#xff0c;有 2 種常見的方式&#xff1a; 設…

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;數組中同一個元素在答案里不能重復出現。 你可以按任意順序返…