浅析linux 系统进程冻结(freezing of task)

news2024/11/19 19:36:00

1 概述

进程冻结是当系统hibernate或者suspend时,对进程进行暂停挂起的一种机制,后面主要以hibernate为例进行介绍。那么为什么要在hibernate或者suspend时需要把进程冻结呢?主要是出于如下的原因:

  1. 防止文件系统被修改后无法恢复。假设没有进程冻结操作,那么在hibernate时,进程可能会在hibernation image镜像生成后依然修改文件系统,这就导致当系统从hibernate镜像resume时这部分的修改丢失,更严重的可能导致无法从hibernate image恢复文件系统数据。
  2. hibernation image生成需要足够的内存空间,为了保证内存回收后不被其他进程再申请走,因此需要先对进程进行冻结,然后在生成hibernation image。
  3. 防止进程在系统suspend或者hibernate之后继续访问已经休眠的设备
  4. 防止用户空间进程需要针对suspend或者hibernate状态做相应的处理,有了进程冻结之后,suspend和hibernate对于用户空间进程来说完全是透明的,不用特殊做处理。

2 相关变量和接口

有3个 per-task 的 flag 用于描述进程冻结状态:
PF_NOFREEZE:如果置位表示该进程不会被冻结,为0表示进程需要在suspend或者hibernate时被冻结
PF_FROZEN:表示进程已经处于冻结状态
PF_FREEZER_SKIP:附加备用状态

3个重要的全局变量:
system_freezing_cnt:大于 0 表示系统进入了冻结状态
pm_freezing: true 表示用户进程被冻结
pm_nosig_freezing: true 表示内核进程和 workqueue 被冻结

重要的函数API:

freeze_processes():
-冻结用户态进程,内部会调用try_to_freeze_tasks(true)。
 
freeze_kernel_threads():
-冻结内核线程,内核会调用try_to_freeze_tasks(false)(实际上是冻结所有进程,因为也会扫描用户态进程)
 
thaw_kernel_threads():
 -解冻内核线程
 
thaw_processes():
  -解冻所有进程(包括内核线程和用户态进程)

3 如何请求冻结一个进程 try_to_freeze_tasks()【核心】

freeze_processes 和 freeze_kernel_threads 最终都会调用到一个关键函数 try_to_freeze_tasks

static int try_to_freeze_tasks(bool user_only)
{
    struct task_struct *g, *p;
    unsigned long end_time;
    unsigned int todo;
    bool wq_busy = false;
    ktime_t start, end, elapsed;
    unsigned int elapsed_msecs;
    bool wakeup = false;
    int sleep_usecs = USEC_PER_MSEC;
#ifdef CONFIG_PM_SLEEP
    char suspend_abort[MAX_SUSPEND_ABORT_LEN];
#endif
 
    start = ktime_get_boottime();
 
    end_time = jiffies + msecs_to_jiffies(freeze_timeout_msecs);
 
    if (!user_only)  //根据传入的参数判断是否只冻结用户态进程
        freeze_workqueues_begin();  //如果为false,则冻结WQ_FREEZABLE类型的workqueue
 
    while (true) {
        todo = 0;
        read_lock(&tasklist_lock);
        for_each_process_thread(g, p) { //遍历系统中所有进程
            if (p == current || !freeze_task(p)) //对非本进程的进程尝试冻结,冻结成功执行continue下一个
                continue;
 
            if (!freezer_should_skip(p)) //运行到这里说明尝试冻结失败了,如果该进程不能跳过冻结,todo需要+1
                todo++;
        }
        read_unlock(&tasklist_lock);
 
        if (!user_only) { //尝试对内核workqueue进行冻结
            wq_busy = freeze_workqueues_busy();
            todo += wq_busy;
        }
 
        if (!todo || time_after(jiffies, end_time)) //判断冻结操作是否完成,完成了就break退出循环,如果超时也会break,当做冻结失败
            break;
 
        if (pm_wakeup_pending()) {
#ifdef CONFIG_PM_SLEEP
            pm_get_active_wakeup_sources(suspend_abort,
                MAX_SUSPEND_ABORT_LEN);
            log_suspend_abort_reason(suspend_abort);
#endif
            wakeup = true;
            break;
        }
 
         /*
          * We need to retry, but first give the freezing tasks some
          * time to enter the refrigerator.  Start with an initial
          * 1 ms sleep followed by exponential backoff until 8 ms.
          */
         usleep_range(sleep_usecs / 2, sleep_usecs); //等待一段时间后重新尝试冻结操作
         if (sleep_usecs < 8 * USEC_PER_MSEC)
             sleep_usecs *= 2;
    } //end of while
 
    end = ktime_get_boottime();
    elapsed = ktime_sub(end, start);
    elapsed_msecs = ktime_to_ms(elapsed);
 
    if (wakeup) { //是否是被打断
        pr_cont("\n");
        pr_err("Freezing of tasks aborted after %d.%03d seconds",
               elapsed_msecs / 1000, elapsed_msecs % 1000);
    } else if (todo) { //超时退出时会到这里,此时todo大于0,还有进程未冻结,冻结失败
        pr_cont("\n");
        pr_err("Freezing of tasks failed after %d.%03d seconds"
               " (%d tasks refusing to freeze, wq_busy=%d):\n",
               elapsed_msecs / 1000, elapsed_msecs % 1000,
               todo - wq_busy, wq_busy);
 
        if (wq_busy)
            show_workqueue_state();
 
        read_lock(&tasklist_lock);
        for_each_process_thread(g, p) {
            if (p != current && !freezer_should_skip(p)
                && freezing(p) && !frozen(p))
                sched_show_task(p);
        }
        read_unlock(&tasklist_lock);
    } else {  //冻结成功
        pr_cont("(elapsed %d.%03d seconds) ", elapsed_msecs / 1000,
            elapsed_msecs % 1000);
    }
 
    return todo ? -EBUSY : 0;
}
【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

3.1 发送冻结信号 freeze_task()

freeze_task 是用来对一个进程进行freeze操作的函数,冻结进程的行为实际上只能由被冻结进程本身进行处理,而此函数只是向被冻结进程发送一个信号。

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.
     */
    if (freezer_should_skip(p))  // ----------- step 1
        return false;
 
    spin_lock_irqsave(&freezer_lock, flags);
    if (!freezing(p) || frozen(p)) {  // --------step 2
        spin_unlock_irqrestore(&freezer_lock, flags);
        return false;
    }
 
    if (!(p->flags & PF_KTHREAD))
        fake_signal_wake_up(p);  // --------- step 3
    else
        wake_up_state(p, TASK_INTERRUPTIBLE); // -------- step 4
 
    spin_unlock_irqrestore(&freezer_lock, flags);
    return true;
}
  1. 使用freezer_should_skip函数来判断该进程是否属于PF_FREEZER_SKIP类型,是否需要跳过冻结操作
  2. 使用freezing来判断是否要对该进程进行冻结,其中会判断pm_freezing和pm_nosig_freezing全局变量来判断对于内核进程是否也要执行冻结操作,frozen函数用来判断该进程是否是已经处于冻结状态了,如果已经冻结自然不需要重复发送冻结信号。
  3. 运行到这里,说明经过前面的条件判断,确定需要对该进程发送冻结请求,PF_KTHREAD 用来区分是否是内核线程,如果不是内核线程,那么表示该进程为用户态进程,此时发送一个虚假的信号去唤醒该进程,借助于进程的信号处理机制来处理进程冻结操作
  4. 如果当前进程是内核线程,此时需要使用 TASK_INTERRUPTIBLE 唤醒该内核线程,在需要冻结的内核线程中,需要调用 try_to_freeze、wait_event_freezable、wait_event_freezable_timeout 来处理进程冻结。

4 用户态进程冻结 fake_signal_wake_up()

前面介绍到当对一个用户态的进程进行冻结请求时,会发送一个虚假的信号 fake_signal_wake_up 来唤醒用户态进程处理信号。那么这个流程是怎样的呢?

 static void fake_signal_wake_up(struct task_struct *p)
{
    unsigned long flags;
 
    if (lock_task_sighand(p, &flags)) {
        signal_wake_up(p, 0);
        unlock_task_sighand(p, &flags);
    }
}

4.1 signal_wake_up() / signal_wake_up_state()

static inline void signal_wake_up(struct task_struct *t, bool resume)
{
	signal_wake_up_state(t, resume ? TASK_WAKEKILL : 0);
}
 
void signal_wake_up_state(struct task_struct *t, unsigned int state)
{
	set_tsk_thread_flag(t, TIF_SIGPENDING); //设置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)) //设置进程为TASK_INTERRUPTIBLE状态,并唤醒进程
		kick_process(t); //让进程陷入内核态处理信号
}

signal_wake_up_state 里面主要做了两件事情:

  1. 线程设置TIF_SIGPENDING表示信号来了,这就说明其实信号的处理和进程的调度是采取这样一种类似的机制:当发现一个进程应该被调度的时候,并不直接把它赶下来,而是设置一个标识位TIF_NEED_RESCHED表示等待调度,然后等待系统调用结束或者中断处理结束,从内核态返回用户态的时候,调用schedule函数进行调度。信号也是类似的,当信号来的时候并不直接处理这个信号,而是设置一个标识位TIF_SIGPENDING,来表示已经有信号等待处理,同样等待系统调用结束,或者中断处理结束,从内核态返回用户态的时候,再进行信号的处理。
  2. 试图唤醒这个进程或者线程。wake_up_state会调用try_to_wake_up方法,就是将这个进程或者线程设置为 TASK_RUNNING 状态准备被调度,然后放在运行队列中,这个时候随着时钟不断的滴答,迟早会被调用。如果 wake_up_state 返回 0,说明进程或者线程已经是 TASK_RUNNING 状态了,如果它在另外一个 CPU 上运行,则调用 kick_process 发送一个处理器间中断,强制那个进程或者线程重新调度,重新调度完毕后,会返回用户态运行,这是一个时机,会检查 TIF_SIGPENDING 标识位。

4.1.1 发送 cpu 中断 kick_process()

/***
 * kick_process - kick a running thread to enter/exit the kernel
 * @p: the to-be-kicked thread
 * Cause a process which is running on another CPU to enter
 * kernel-mode, without any delay. (to get signals handled.)
*/
void kick_process(struct task_struct *p)
{
    int cpu;
 
    preempt_disable();
    cpu = task_cpu(p); //找到该进程要被运行的cpu
    if ((cpu != smp_processor_id()) && task_curr(p))
        smp_send_reschedule(cpu);// 发送CPU间的IPI中断请求
    preempt_enable();
}
 
void smp_send_reschedule(int cpu)
{
    BUG_ON(cpu_is_offline(cpu));
    smp_cross_call_common(cpumask_of(cpu), IPI_RESCHEDULE);
}

这里需要注意为什么最后要发送一个IPI中断给到需要运行该进程的CPU,因为需要冻结用户态进程,因为该中断会使得用户进程陷入到内核态,处理中断,中断处理完成后都会调用ret_to_user返回用户态继续运行,而在ret_to_user中,也就会返回用户态前会判断是否有pending signal要被处理,这时就可以处理进程冻结了。

->ret_to_user
-->do_notify_resume
--->do_signal
---->get_signal
------>try_to_freeze //执行线程具体眠动作

5 内核线程冻结

对于一个需要睡眠的内核线程,一般的处理流程如下:

set_freezable();
do {
    hub_events();
    wait_event_freezable(khubd_wait,
            !list_empty(&hub_event_list) ||
            kthread_should_stop());
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
 

et_freezable 会清除本进程的 PF_NOFREEZE 标志,也就意味着该内核进程可以被冻结。或者:

set_freezable();
while (!kthread_should_stop()) {
    try_to_freeze();
......
}

while循环在每次运行到 try_to_freeze 时都会检测调用 freezing 函数检测一下本进程是否可以被冻结,如果可以就直接进行冻结操作。

5.1 执行休眠动作 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);//冻结存储
}

如果我们想要创建一个需要冻结的内核线程,就需要遵守上面的要求来实现它,否则会导致系统 freeze 失败从而无法休眠。

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

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

相关文章

springboot集成security(认证)

目录1. 依赖2. 自定义登录逻辑1. 数据库查询2. security认证1. loadUserByUsername2. PasswordEncorder&#xff08;不加密&#xff09;3. MD5加密数据库密码4. PasswordEncorder&#xff08;加密&#xff09;5. BCryptPasswordEncoder6. 认证流程&#xff08;图&#xff09;3.…

【POJ No. 3764】 最长xor 路径 The xor-longest Path

【POJ No. 3764】 最长xor 路径 The xor-longest Path 北大OJ 题目地址 【题意】 在边权树中&#xff0c;路径p的xor长度被定义为路径p上边权的 &#xff0c;⊕是xor运算符&#xff0c;表示异或。若一个路径有最大的xor长度&#xff0c;则该路径是xor最长的路径。给定n 个节点…

如何正确使用Airtest报告插件?报告小tips上线

1. 前言 在使用Airtest做自动化测试时&#xff0c;默认生成的报告&#xff0c;其实是airtest的专属报告。 它对于poco语句&#xff08;控件测试场景&#xff09;、airtest-selenium语句&#xff08;web测试场景&#xff09;的支持不够完善&#xff0c;因此我们需要用 插件的形…

Shell脚本学习指南(六)——输入/输出、文件与命令执行

文章目录前言标准输入、标准输出与标准错误输出使用read读取行关于重定向额外的重定向运算符文件描述符处理printf的完整介绍波浪号展开与通配符波浪号展开使用通配符命令替换为head命令使用sed创建邮件列表简易数学&#xff1a;expr引用执行顺序与evaleval语句subShell与代码块…

CAS:210236-90-1,Fluorescein Tyramide,荧光素酪胺绿色荧光试剂

一&#xff1a;产品描述 1、名称 英文&#xff1a;Fluorescein Tyramide 中文&#xff1a;荧光素酪胺 2、CAS编号&#xff1a;210236-90-1 3、分子式&#xff1a;C29H21NO7 4、分子量&#xff1a;495.49 6、储存&#xff1a; -20℃可长期保存&#xff0c;注意避光并置于…

个人微信号API接口,微信机器人

自定义的微信机器人&#xff0c;需求是可以自己批量添加好友、批量打标签等进行好友管理&#xff0c;社群管理需要自动聊天&#xff0c;自动回复&#xff0c;发朋友圈&#xff0c;转发语音&#xff0c;以及定时群发等&#xff0c;还可以提取聊天内容&#xff0c;进行数据汇总&a…

美妆商场系统/在线购物系统/美妆销售系统

摘 要 本毕业设计的内容是设计并且实现一个基于JSP技术的美妆商场系统。它是在Windows下&#xff0c;以MYSQL为数据库开发平台&#xff0c;Tomcat网络信息服务作为应用服务器。美妆商场系统的功能已基本实现&#xff0c;主要包括个人中心、用户管理、商品中心管理、商品类型管…

故障分析 | MySQL 使用 load data 导入数据错误的一个场景

作者&#xff1a;刘晨 网名 bisal &#xff0c;具有十年以上的应用运维工作经验&#xff0c;目前主要从事数据库应用研发能力提升和技术管理相关的工作&#xff0c;Oracle ACE &#xff0c;腾讯云TVP&#xff0c;拥有 Oracle OCM & OCP 、EXIN DevOps Master 、SCJP 等国际…

HashSet、HashMap、LinkedHashMap、HashTable、ConcurrentHashMap源码阅读笔记

目录一、HashSet二、HashMap三、LinkedHashMap四、HashTable五、ConcurrentHashMap一、HashSet 首先&#xff0c;让我们先从最简单的开始&#xff0c;总的来说&#xff0c;hashSet可以说是建立在hashMap上的变种应用。 通过阅读hashSet的源码我们可以得出以下结论&#xff1a;…

1556_AURIX_TC275_复位系统控制单元

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 这是上一部分没有看完的CCU的核心寄存器的存储映射信息&#xff0c;只是一个汇总&#xff0c;没有需要着重处理的分析点。 复位控制单元涉及到的几个大功能&#xff1a;基本复位、外部复位…

订单服务-----遇到的问题及解决方案

订单服务的问题及解决方案 问题1&#xff1a;Feign远程调用时丢失请求头 出现这个Feign远程调用时丢失请求头的问题是因为Feign在远程调用的时候会创建一个新的请求&#xff0c;但是这个新的请求里啥都没有&#xff0c;没有cookie值&#xff0c;而这个cookie值里有成功登录后的…

投稿MDPI旗下期刊的一些心得和记录

投稿历程&#xff1a; 09.02 提交初稿 under review 09.05 分配助理编辑&#xff0c;论文送审 10.13 第一轮审稿完毕&#xff0c;大修&#xff0c;两个审稿人 Pending Major Revisions 10.16 语言问题需要润色&#xff0c;使用MDPI润色机构 10.19 重新提交修改稿 Resubmitt…

【Axure教程】中继器版穿梭表格

表格是我们系统中常用的组件&#xff0c;穿梭表格就是使用直观方式在两个表格中移动数据&#xff0c;实现数据的流动。今天作者就教大家如何在Axure里用中继器制作出表格穿梭的效果&#xff1a; 1、选中效果&#xff1a;鼠标点击表格中所在的行&#xff0c;可以选中该行数据 …

【Vue 快速入门系列】组件的基本使用

文章目录一、组件的概念二、非单文件组件三、单文件组件1.main.js2.App.vue3.school.vue4.student.vue5.index.html四、内容补充及原理剖析1.组件命名注意点2.组件嵌套内置关系3.重要的内置关系一、组件的概念 组件&#xff08;Component&#xff09;是 Vue.js 最强大的功能之…

Java日期与时间

时间与日期DateSimpleDateFormatCalendarJDK8新增日期类概述LocalDate、LocalTime、LocalDateTimeInstant时间戳DateTimeFormatterDuration/PeriodChronoUnitDate Date类概述 Date类的对象在Java中代表的是当前所在系统的此刻日期时间。 Date的构造器 Date的常用方法 Date类…

2022最新 MySQL事务面试题合集

小熊学Java网站&#xff1a;https://javaxiaobear.gitee.io/&#xff0c;每周持续更新干货&#xff0c;建议收藏&#xff01; 61、什么是数据库事务&#xff1f;事务的特性是什么&#xff1f; 事务&#xff1a; 是数据库操作的最小工作单元&#xff0c;是作为单个逻辑工作单元执…

微服务框架 SpringCloud微服务架构 25 黑马旅游案例 25.2 条件过滤

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构25 黑马旅游案例25.2 条件过滤25.2.1 直接开干25 黑马旅游案例 25.2 条件…

怎么把磁盘合并成一个?两个硬盘分区合并,如何硬盘分区合并

怎么把磁盘合并成一个&#xff1f;有时候电脑分区有点多&#xff0c;想要给硬盘的分区合并不知道如何操作&#xff0c;如何在不影响系统正常使用的情况下合并分区呢&#xff1f;本篇文章将详细解答这个问题。 之前有个客户提出这样一个问题&#xff0c;那就是在安装完windows10…

【CSS3】text-shadow/text-overflow,边框图片,透明度,小米轮播图子菜单另一种实现

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录text-shadowtext-overflow边框图片border-image-sourceborder-image-sliceborder-image-widthbor…

数据智仓功能介绍(一)

数据智仓英文名称为Smart Data Warehouse&#xff0c;可简写为SDW。数据智仓是JVS整体企业数字化解决方案的核心能力&#xff0c;与JVS的低代码开发套件平级。数据仓库的目的是构建面向分析的集成化数据环境&#xff0c;为企业提供基于数据的决策支持&#xff08;Decision Supp…