Android 中 app freezer 原理详解(二):S 版本

news2024/11/27 12:47:45

基于版本:Android S

0. 前言

在之前的两篇博文《Android 中app内存回收优化(一)》《Android 中app内存回收优化(二)》中详细剖析了 Android 中 app 内存优化的流程。这个机制的管理通过 CachedAppOptimizer 类管理,为什么叫这个名字,而不叫 AppCompact 等?在之前的两篇博文中也提到了,因为该类中还管理了一个重要功能:freezer,一个针对应用进程长期处于 Cached 状态的优化。

在之前博文《app freezer 原理 R 版本》中简单的剖析了app freeze / unfreeze 的流程,但从代码逻辑上来看,笔者觉得 R 版本上有些逻辑是存在问题的,好在S 版本中都修复了。

本文将以 R 版本的原理为基础,对 S 版本进行对比分析。

1. freezer 触发

R 版本中,S 版本的触发也是在 applyOomAdjLSP() 函数中调用 updateAppFreezeStateLSP() 来确认是否冻结进程:

frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java

   private void updateAppFreezeStateLSP(ProcessRecord app) {

        // 确认该功能是否使能,如果没有使能则返回
        if (!mCachedAppOptimizer.useFreezer()) {
            return;
        }

        // S 版本新加的,确认应用是否可以豁免
        if (app.mOptRecord.isFreezeExempt()) {
            return;
        }

        // S 版本中cached进程优化相关的属性,都放到了mOptRecord中管理
        final ProcessCachedOptimizerRecord opt = app.mOptRecord;

        // 如果进程处于frozen状态,但shouldNotFreeze变成true,需要解冻
        if (opt.isFrozen() && opt.shouldNotFreeze()) {
            mCachedAppOptimizer.unfreezeAppLSP(app);
            return;
        }

        // 确定adj,是进入freeze 还是 unfreeze 流程
        final ProcessStateRecord state = app.mState;
        if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && !opt.isFrozen()
                && !opt.shouldNotFreeze()) {
            mCachedAppOptimizer.freezeAppAsyncLSP(app);
        } else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
            mCachedAppOptimizer.unfreezeAppLSP(app);
        }
    }

该函数与 R 版本基本逻辑差不多,稍微有些差异:

  • 进程有了是否freeze 豁免的功能,当应用设置了 INSTALL_PACKAGES 的权限之后,该应用处于豁免状态,详细看 ProcessList.startProcessLocked() 函数;
  • S 版本中cached进程的一些状态,统一由ProcessRecord 中的 mOptRecord 维护;
  • 最后再unfreeze 调用时,不再判断app 是否处于 frozen,因为这部分逻辑判断会在 unfreeze 函数中判定,这让代码更简介,R 版本中有些多余;

2. CachedAppOptimizer.init()

对于CachedAppOptimizer 的构造调用,以及 init() 函数的触发流程,可以参考《Android 中app内存回收优化(二)》 一文第 1 节 和 第 2 节。

frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
 
    public void init() {
        ...
        DeviceConfig.addOnPropertiesChangedListener(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                ActivityThread.currentApplication().getMainExecutor(),
                mOnNativeBootFlagsChangedListener);
        mAm.mContext.getContentResolver().registerContentObserver(
                CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver);
        synchronized (mPhenotypeFlagLock) {
            ...
            updateUseFreezer();
            ...
        }
    }

相比较与 R 版本,这里多了两个功能:

  • 新加一个 freeze_debounce_timeout 属性发生变化的 listener,当该属性变化时,会调用 updateFreezerDebounceTimeout() 进行更新;
  • 新加了 cached_apps_freezer 属性值发生变化的 observer;

R 版本中,freeze timeout 是10min,使用的是常量。而在 S 版本中,将该值设计为可变的,用户可以通过 DeviceConfig 进行修改。当发生变化时,会调用 updateFreezerDebounceTimeout()进行更新。

另外,在 S 版本中,对 cached_apps_freezer 的值做了一个observer,及时控制 freezer 的使能。

2.1 updateUseFreezer()

    private void updateUseFreezer() {
        // 获取 settings中属性 cached_apps_freezer的值,同R 版本
        final String configOverride = Settings.Global.getString(mAm.mContext.getContentResolver(),
                Settings.Global.CACHED_APPS_FREEZER_ENABLED);

        if ("disabled".equals(configOverride)) {
            mUseFreezer = false;
        } else if ("enabled".equals(configOverride)
                || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                    KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
            mUseFreezer = isFreezerSupported();
            updateFreezerDebounceTimeout();
        } else {
            mUseFreezer = false;
        }

        final boolean useFreezer = mUseFreezer;
        // enableFreezer() would need the global ActivityManagerService lock, post it.
        mAm.mHandler.post(() -> {
            if (useFreezer) {
                Slog.d(TAG_AM, "Freezer enabled");
                enableFreezer(true);

                if (!mCachedAppOptimizerThread.isAlive()) {
                    mCachedAppOptimizerThread.start();
                }

                if (mFreezeHandler == null) {
                    mFreezeHandler = new FreezeHandler();
                }

                Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
                    mCompactionPriority);

            } else {
                Slog.d(TAG_AM, "Freezer disabled");
                enableFreezer(false);
            }
        });
    }

针对 R 版本也做了个优化,将 enableFreezer() 的处理进行异步处理,因为在 S 版本中做了一个很大的调整。详细查看下文第 4 节。

freezer 功能是否使能,用流程图说明比较清晰:

需要注意的是,在获取freezer 节点的时候,与R 版本有所不同:

  • R 版本是,直接指定节点 /sys/fs/cgroup/freezer/cgroup.freeze;
  • S 版本是,通过函数 getFreezerCheckPath() 向libprocessgroup 中查找 pid 所对应的节点;

 

3. cgroups 简介

这里不再补充,详细可以查看 R 版本《Android 中 cgroup抽象层详解》

这里需要注意的是,R 版本中的 freezer 中,通过 /sys/fs/cgroup/freezer/cgroup.freeze 来确定是否使能 freezer,通过 /sys/fs/cgroup/freezer/cgroup.procs 来进行 frozen / unfrozen 操作。

而在 S 版本中,frozen/unfrozen 的操作通过 /sys/fs/cgroup/uid_xxx/pid_xxx/cgroup.freeze 节点。

在 S 版本中 cgroups.json 有所不同:

  "Cgroups2": {
    "Path": "/sys/fs/cgroup",
    "Mode": "0755",
    "UID": "system",
    "GID": "system",
    "Controllers": [
      {
        "Controller": "freezer",
        "Path": ".",
        "Mode": "0755",
        "UID": "system",
        "GID": "system"
      }
    ]
  }

不再出现 freezer 目录,而是直接在 /sys/fs/cgroup 目录下创建 uid_xxx/pid_xxx 目录。

详细看下面第 5.2.1 节。

4. enableFreezer()

frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java

    public synchronized boolean enableFreezer(boolean enable) {
        if (!mUseFreezer) {
            return false;
        }

        if (enable) {
            mFreezerDisableCount--;

            if (mFreezerDisableCount > 0) {
                return true;
            } else if (mFreezerDisableCount < 0) {
                Slog.e(TAG_AM, "unbalanced call to enableFreezer, ignoring");
                mFreezerDisableCount = 0;
                return false;
            }
        } else {
            mFreezerDisableCount++;

            if (mFreezerDisableCount > 1) {
                return true;
            }
        }

        // Override is applied immediately, restore is delayed
        synchronized (mAm) {
            synchronized (mProcLock) {
                mFreezerOverride = !enable;
                Slog.d(TAG_AM, "freezer override set to " + mFreezerOverride);

                mAm.mProcessList.forEachLruProcessesLOSP(true, process -> {
                    if (process == null) {
                        return;
                    }

                    final ProcessCachedOptimizerRecord opt = process.mOptRecord;
                    if (enable && opt.hasFreezerOverride()) {
                        freezeAppAsyncLSP(process);
                        opt.setFreezerOverride(false);
                    }

                    if (!enable && opt.isFrozen()) {
                        unfreezeAppLSP(process);

                        // Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)
                        opt.setFreezerOverride(true);
                    }
                });
            }
        }

        return true;
    }

代码与 R 版本 不同,这里通过 mAm.mProcessList.forEachLruProcessesLOSP() 对每个LRU 中的进程进行确认。

进程中引入了 mOptRecord.mFreezerOverride 属性,用以标记对某个进程是否进行 disable freezer 操作。若该值为 true,则表示进程被 disable freezer 过。此时如果再次 enable,需对进程进行 freeze 请求。

当然,CachedAppOptimizer 类中也有这样的成员变量 mFreezerOverride,这个用以控制从外部调用的 freezeAppAsyncLSP()。如果该值为 true,则表示disable freezer了,外部如果有调用 freezeAppAsyncLSP(),则不需要去处理。

5. freezeAppAsyncLSP()

    void freezeAppAsyncLSP(ProcessRecord app) {
        final ProcessCachedOptimizerRecord opt = app.mOptRecord;
        if (opt.isPendingFreeze()) {
            // Skip redundant DO_FREEZE message
            return;
        }

        mFreezeHandler.sendMessageDelayed(
                mFreezeHandler.obtainMessage(
                    SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
                mFreezerDebounceTimeout);
        opt.setPendingFreeze(true);
    }

相比较 R 版本,这里做了一个保护,防止反复进行 freeze 请求。

另外,timeout 从 R 版本中的常量 FREEZE_TIMEOUT_MS 改成可变的 debounce timeout。

注意:冻结时异步操作,使用 CachedAppOptimizer类中定义的 ServiceThread 进行,而解冻是东部操作,没有通过 ServiceThread。

5.1 freeze 消息处理

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SET_FROZEN_PROCESS_MSG:
                    synchronized (mAm) {
                        freezeProcess((ProcessRecord) msg.obj);
                    }
                    break;
                case REPORT_UNFREEZE_MSG:
                    int pid = msg.arg1;
                    int frozenDuration = msg.arg2;
                    String processName = (String) msg.obj;

                    reportUnfreeze(pid, frozenDuration, processName);
                    break;
                default:
                    return;
            }
        }

对于 freezer 一共有两个消息,REPORT_UNFREEZE_MSG 这个消息是在 unfreeze 之后进行记录的。

本文重点来看下 freeze 的消息处理,这里看到最终调用的是 freezeProcess() 函数,详细查看下一小节

5.2 freezeProcess()

frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java

       private void freezeProcess(final ProcessRecord proc) {
            int pid = proc.getPid(); // Unlocked intentionally
            final String name = proc.processName;
            final long unfrozenDuration;
            final boolean frozen;
            final ProcessCachedOptimizerRecord opt = proc.mOptRecord;

            opt.setPendingFreeze(false);

            try {
                // 确认进程是否存在任意的文件锁,避免不必要的 free/unfreeze操作
                //   这是为了防止冻结进程持有文件锁,从而引起死锁
                //   冻结成功之后,还会再次确认文件锁,如果有锁,则立即解冻
                if (mProcLocksReader.hasFileLocks(pid)) {
                    if (DEBUG_FREEZER) {
                        Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");
                    }
                    return;
                }
            } catch (Exception e) {
                Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid
                        + "): " + e);
                return;
            }

            synchronized (mProcLock) {
                pid = proc.getPid();

                // 如果进程没有变成Cached或者不能freeze,则退出此次freeze操作
                if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ
                        || opt.shouldNotFreeze()) {
                    return;
                }

                // 如果freezer 处于disable状态,返回,并告知该进程
                if (mFreezerOverride) {
                    opt.setFreezerOverride(true);
                    return;
                }


                // 已经处于frozen,或者不是一个应用进程,则退出此次 freeze操作
                //   pid为0,有可能进程还没有launch完成,或者进程被kill了
                if (pid == 0 || opt.isFrozen()) {
                    // Already frozen or not a real process, either one being
                    // launched or one being killed
                    return;
                }

                Slog.d(TAG_AM, "freezing " + pid + " " + name);

                // 在S版本中,将这部分功能提前,也是正确的行为
                // 冻结 binder
                //    1.如果freezer是使能,将同步发送所有的pending 交互给指定的pid;
                //    2.该函数调用后,所有的binder 请求,都会被block,并返回error给发送请求的进程;
                try {
                    if (freezeBinder(pid, true) != 0) {
                        rescheduleFreeze(proc, "outstanding txns");
                        return;
                    }
                } catch (RuntimeException e) { //如果调用失败,则直接kill该进程
                    Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
                    mFreezeHandler.post(() -> {
                        synchronized (mAm) {
                            proc.killLocked("Unable to freeze binder interface",
                                    ApplicationExitInfo.REASON_OTHER,
                                    ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
                        }
                    });
                }

                long unfreezeTime = opt.getFreezeUnfreezeTime();

                //核心函数 setProcessFrozen(),同步冻结进程
                try {
                    Process.setProcessFrozen(pid, proc.uid, true);

                    opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
                    opt.setFrozen(true);
                } catch (Exception e) {
                    Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
                }

                unfrozenDuration = opt.getFreezeUnfreezeTime() - unfreezeTime;
                frozen = opt.isFrozen();
            }

            if (!frozen) {
                return;
            }

            Slog.d(TAG_AM, "froze " + pid + " " + name);

            // 将此次 freeze 记录到 event log 中
            EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);

            // See above for why we're not taking mPhenotypeFlagLock here
            if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
                FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
                        FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
                        pid,
                        name,
                        unfrozenDuration);
            }

            // 确认在冻结的时候,是否收到了 TXNS_PENDING_WHILE_FROZEN的binder请求,
            //    如果有有收到请求,则重新freeze 该进程(unfreeze + freeze)
            try {
                // post-check to prevent races
                int freezeInfo = getBinderFreezeInfo(pid);

                if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {
                    synchronized (mProcLock) {
                        rescheduleFreeze(proc, "new pending txns");
                    }
                    return;
                }
            } catch (RuntimeException e) {
                Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
                mFreezeHandler.post(() -> {
                    synchronized (mAm) {
                        proc.killLocked("Unable to freeze binder interface",
                                ApplicationExitInfo.REASON_OTHER,
                                ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
                    }
                });
            }

            try {
                // 再次check文件锁,如果该冻结进程持有文件锁,立即unfreeze
                if (mProcLocksReader.hasFileLocks(pid)) {
                    if (DEBUG_FREEZER) {
                        Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
                    }
                    unfreezeAppLSP(proc);
                }
            } catch (Exception e) {
                Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
                unfreezeAppLSP(proc);
            }
        }

逻辑与R 版本有很大的不同:

  • 在冻结进程之前,调用 freezeBinder(),用以冻结binder 通信;
  • 在冻结进程之后,调用 getBinderFreezeInfo(),确认是否在冻结的时候有 TXNS_PENDING_WHILE_FROZEN 的binder 请求,如果有该请求,则重新freeze 该进程(unfreeze + freeze);

弄个流程图理解冻结过程:

 

 5.2.1 setProcessFrozen()

frameworks/base/core/java/android/os/Process.java
 
    public static final native void setProcessFrozen(int pid, int uid, boolean frozen);
frameworks/base/core/jni/android_util_Process.cpp

void android_os_Process_setProcessFrozen(
        JNIEnv *env, jobject clazz, jint pid, jint uid, jboolean freeze)
{
    bool success = true;

    if (freeze) {
        success = SetProcessProfiles(uid, pid, {"Frozen"});
    } else {
        success = SetProcessProfiles(uid, pid, {"Unfrozen"});
    }

    if (!success) {
        signalExceptionForGroupError(env, EINVAL, pid);
    }
}

此处的调用在博文《cgroup抽象层》中已经分析过,通过接口 SetProcessProfiles() 精细是 SetAttributeAction 类型的profile,最终调用 ExecuteForProcess():

system/core/libprocesscgroup/task_profiles.cpp

bool SetAttributeAction::ExecuteForProcess(uid_t, pid_t pid) const {
    return ExecuteForTask(pid);
}

bool SetAttributeAction::ExecuteForTask(int tid) const {
    std::string path;

    if (!attribute_->GetPathForTask(tid, &path)) {
        LOG(ERROR) << "Failed to find cgroup for tid " << tid;
        return false;
    }

    if (!WriteStringToFile(value_, path)) {
        PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
        return false;
    }

    return true;
}

通过代码需要确定 attribute_->GetPathForTask():

system/core/libprocessgroup/task_profiles.cpp

bool ProfileAttribute::GetPathForTask(int tid, std::string* path) const {
    std::string subgroup;
    if (!controller()->GetTaskGroup(tid, &subgroup)) {
        return false;
    }

    if (path == nullptr) {
        return true;
    }

    if (subgroup.empty()) {
        *path = StringPrintf("%s/%s", controller()->path(), file_name_.c_str());
    } else {
        *path = StringPrintf("%s/%s/%s", controller()->path(), subgroup.c_str(),
                             file_name_.c_str());
    }
    return true;
}

有两部分,一个是通过 controller 获取subgroup,二是进行最终path 的拼接。

下面来看下 congtroller 指向的 GetTaskGroup()

system/core/libprocessgroup/cgroup_map.cpp

bool CgroupController::GetTaskGroup(int tid, std::string* group) const {
    std::string file_name = StringPrintf("/proc/%d/cgroup", tid);
    std::string content;
    if (!android::base::ReadFileToString(file_name, &content)) {
        PLOG(ERROR) << "Failed to read " << file_name;
        return false;
    }

    // if group is null and tid exists return early because
    // user is not interested in cgroup membership
    if (group == nullptr) {
        return true;
    }

    std::string cg_tag;

    if (version() == 2) {
        cg_tag = "0::";
    } else {
        cg_tag = StringPrintf(":%s:", name());
    }
    size_t start_pos = content.find(cg_tag);
    if (start_pos == std::string::npos) {
        return false;
    }

    start_pos += cg_tag.length() + 1;  // skip '/'
    size_t end_pos = content.find('\n', start_pos);
    if (end_pos == std::string::npos) {
        *group = content.substr(start_pos, std::string::npos);
    } else {
        *group = content.substr(start_pos, end_pos - start_pos);
    }

    return true;
}

读取 /proc/PID/cgroup 中信息,对于 cgroup2,读取是 0:: 所在信息,在 S 版本中指定的是uid和pid 拼接,例如:

130|shift:/sys/fs/cgroup # cat /proc/2119/cgroup
4:memory:/
3:cpuset:/restricted
2:cpu:/foreground
1:blkio:/foreground
0::/uid_10083/pid_2119

最终写的是 /sys/fs/cgroup/uid_xxx/pid_xxx/cgroup.freeze 文件。

某个进程被冻结的旅程图:

6. unfreezeAppLSP()

frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java

   void unfreezeAppLSP(ProcessRecord app) {
        final int pid = app.getPid();
        final ProcessCachedOptimizerRecord opt = app.mOptRecord;

        // 进程已经处于peding freeze中,移除冻结消息
        if (opt.isPendingFreeze()) {
            // Remove pending DO_FREEZE message
            mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
            opt.setPendingFreeze(false);
        }

        opt.setFreezerOverride(false);

        // 如果进程还没有冻结,则无需做解冻处理
        if (!opt.isFrozen()) {
            return;
        }

        // 冻住的进程可以接收异步binder请求,但是不会处理,只是放入binder buffer, 过多的请求会导致buffer耗尽;
        // 这里需要确认下该进程在解冻之前,进程是否在冰冻期间收到同步的binder 请求,有则kill该进程
        boolean processKilled = false;

        try {
            int freezeInfo = getBinderFreezeInfo(pid);

            if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
                Slog.d(TAG_AM, "pid " + pid + " " + app.processName
                        + " received sync transactions while frozen, killing");
                app.killLocked("Sync transaction while in frozen state",
                        ApplicationExitInfo.REASON_OTHER,
                        ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION, true);
                processKilled = true;
            }

            if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) {
                Slog.d(TAG_AM, "pid " + pid + " " + app.processName
                        + " received async transactions while frozen");
            }
        } catch (Exception e) {
            Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + pid + " "
                    + app.processName + ". Killing it. Exception: " + e);
            app.killLocked("Unable to query binder frozen stats",
                    ApplicationExitInfo.REASON_OTHER,
                    ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
            processKilled = true;
        }

        //进程被kill 了,无需 unfreeze
        if (processKilled) {
            return;
        }

        // app.freezeUnfreezeTime记录的是上次free、unfreeze的时间
        long freezeTime = opt.getFreezeUnfreezeTime();

        try {
            freezeBinder(pid, false);
        } catch (RuntimeException e) {
            Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
                    + ". Killing it");
            app.killLocked("Unable to unfreeze",
                    ApplicationExitInfo.REASON_OTHER,
                    ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
            return;
        }

        try {
            Process.setProcessFrozen(pid, app.uid, false);

            opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
            opt.setFrozen(false);
        } catch (Exception e) {
            Slog.e(TAG_AM, "Unable to unfreeze " + pid + " " + app.processName
                    + ". This might cause inconsistency or UI hangs.");
        }

        if (!opt.isFrozen()) {
            Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName);

            mFreezeHandler.sendMessage(
                    mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
                        pid,
                        (int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
                        app.processName));
        }
    }

逻辑比较清晰,核心处理函数是 setProcessFrozen(),详细的流程在上面第 5.2.1 节中已经分析过。

7. kernel 处理

kernel/cgroup/cgroup.c

static struct cftype cgroup_base_files[] = {
    ...

	{
		.name = "cgroup.freeze",
		.flags = CFTYPE_NOT_ON_ROOT,
		.seq_show = cgroup_freeze_show,
		.write = cgroup_freeze_write,
	},

    ...
};

/sys/fs/cgroup/uid_xxx/pid_xxx/cgroup.freeze 文件的写入触发回调函数 cgroup_freeze_write() 函数,通过当前的 kernfs 找到对应的 cgroup,将这个 cgroup 下所有进程以及子进程都 freeze。

详细的 cgroup_freeze_write() 函数,后续将补充剖析。

 
 

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

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

相关文章

HTTP协议 和 HTTPS协议的区别(4点) HTTPS如何使用SSL/TLS协议加密过程 CA证书干啥的

&#xff08;一&#xff09;HTTP协议 和 HTTPS协议的区别&#xff08;4点&#xff09;&#xff1a; 1. HTTP协议的端口号是80&#xff0c; HTTPS协议的端口号是443 2. HTTP协议使用的URL是以 http:// 开头&#xff0c;HTTPS协议使用的URL是以https://开头 3. HTTP协议和HTTP…

Qt 中操作xml文件和JSON字符串

文章目录 1、概述1.1、xml介绍1.2、json介绍 2、xml文件增删改查2.1、写xml文件内容2.2、读xml文件内容2.3、删除xml文件内容2.4、修改xml文件内容 3、构建JSON字符串3.1、JSON字符串排版4、剪切板操作 1、概述 1.1、xml介绍 XML 指可扩展标记语言&#xff08;EXtensible Mark…

“效能指标”,该由谁来定义?| 谈效风生

第5期&#xff1a;效能指标&#xff0c;该由谁来定义&#xff1f; 回顾上期《「自动化」聊起来简单&#xff0c;做起来难》我们聊了聊如何打造「自动化」的事&#xff0c;这也是真正实现研发效能提升的必要条件。从单点自动化提升效率&#xff0c;到全工具链自动化&#xff0c;…

常微分方程建模R包ecode(二)——绘制相速矢量场

本节中我们考虑一个更为复杂的常微分方程模型&#xff0c; d X C d t ν ( X A Y A ) − β ⋅ X C ⋅ ( Y C Y A ) − ( μ g ) ⋅ X C , ( 1 ) d Y C d t β ⋅ X C ⋅ ( Y C Y A ) − ( μ g ρ ) ⋅ Y C , ( 2 ) d X A d t g ⋅ X C − β ⋅ X A ⋅ ( Y C Y A …

2023最新版本~十分钟零基础搭建EMQX服务器

购买服务器 已知服务器大厂商 1 阿里云 点击直接访问 2 华为云点击直接访问 3 腾讯云 点击直接访问 还是比较推荐大公司 不会跑路 这里我购买的是一年的华为云服务器(新用户 64一年) 镜像推荐乌班图18 登陆服务器&#xff08;需要重置密码&#xff01;&#xff01;&…

Ansible自动化运维工具 —— Playbook 剧本

playbooks 本身由以下各部分组成 &#xff08;1&#xff09;Tasks&#xff1a;任务&#xff0c;即通过 task 调用 ansible 的模板将多个操作组织在一个 playbook 中运行 &#xff08;2&#xff09;Variables&#xff1a;变量 &#xff08;3&#xff09;Templates&#xff1a;模…

ubuntu16.04忘记密码了怎么办,亲测有效

由于装载Ubuntu系统的电脑&#xff08;非虚拟机&#xff09;好久没有用&#xff0c;忘记了密码&#xff0c;只能进行密码重置&#xff0c;亲测有效&#xff1a; 1.首先ubuntu系统开机&#xff0c;期间按着shift键不放&#xff0c;选择高级选项。 2.enter键进入如下界面&#x…

python-occ入门指北

0、系统环境: Win10 在Windows环境中玩PythonOCC比较简单的方式是使用anaconda的cmd prompt或者powershell的prompt&#xff0c;这里我用的是cmd。PowerShell也有很多粉丝&#xff0c;但是个人真的觉得这个东西挺鸡肋的。 另外在Win10或Win11上另一个玩法是使用WSL2&#xff…

【漏洞挖掘】Xray+rad自动化批量漏洞挖掘

文章目录 前言一、挖掘方法二、使用步骤工具安装使用方法开始挖掘 总结 前言 自动化漏洞挖掘是指利用计算机程序和工具来扫描、分析和检测应用程序、网络和系统中的安全漏洞的过程。这种方法可以帮助安全专家和研究人员更高效地发现和修复潜在的安全威胁&#xff0c;从而提高整…

企业级开发中协同开发与持续集成持续部署

文章目录 1 创建代码仓库2 使用git协同开发2.1 独立团队开发2.2 多团队开发git工作流 2 持续集成和持续部署2.1 创建docker镜像2.2 使用coding构建 1 创建代码仓库 每个项目有唯一的代码仓库&#xff0c;所以不是每个开发者都需要创建一个代码仓库&#xff0c;一般都是项目负责…

Node版本自由切换之nvm安装教程

1.nvm安装包下载&#xff0c;这里推荐1.1.7版本 https://github.com/coreybutler/nvm-windows/releases/download/1.1.7/nvm-setup.zip 2.解压后运行exe文件&#xff0c;一路默认就可以了&#xff0c;自定义的话&#xff0c;文件路径不要有中文&#xff1b; 3.安装之后使用命…

CloudCompare软件手册01

1. 介绍 1.1 历史 CloudCompare是一个3D点云(和三角形网格)编辑和处理软件。 最初&#xff0c;它被设计用于在密集的3D点云之间进行直接比较。它依赖于一种特定的八叉树结构&#xff0c;在执行这类任务时&#xff0c;这种结构能够提供出色的性能。此外&#xff0c;由于大多数…

基于python+Xception算法模型实现一个图像分类识别系统

一、目录 Xception介绍数据集处理模型训练模型评估项目扩展 二、Xception介绍 在计算机视觉领域&#xff0c;图像识别是一个非常重要的任务&#xff0c;其应用涵盖了人脸识别、物体检测、场景理解等众多领域。随着深度学习技术的发展&#xff0c;深度卷积神经网络&#xff0…

C++继承——多继承问题

目录 单继承&#xff1a; 多继承&#xff1a; 菱形继承&#xff1a;菱形继承是多继承的一种特殊情况。 三.菱形继承的两种解决方式区别&#xff1a; 3.1采用作用域解决的菱形继承&#xff1a; 检测器运行图&#xff1a; 反汇编运行图&#xff1a; 3.1菱形虚继承&…

webstorm配置less转译

Program中路径如果识别不到 项目文件\node_modules.bin\lessc

如何优雅的显示404页面

源码&#xff1a;mumangguo/404-notfound - 码云 - 开源中国https://gitee.com/mumangguo/404-notfound 1.孤独型404页面 2.酷炫效果404页面 3.太空404页面 4.404寻亲页面&#xff08;公益&#xff09; 每一次刷新都是一个公益捐赠活动&#xff01; 以上就是笔者要分享的4个4…

【H5移动端】常用的移动端方案合集-键盘呼起、全面屏适配、图片大小显示、300ms点击延迟、首屏优化(不定期补充~)

文章目录 前言键盘呼起问题靠近底部的输入项被键盘遮挡底部按钮被顶上去 全面屏适配图片大小显示问题解决300ms延迟首屏优化 前言 这篇文章总结了我在工作中做H5遇到的一些问题&#xff0c;包括我是怎么解决的。可能不是当下的最优解&#xff0c;但是能保证解决问题。 单位适…

【hive】Install hive using mysql as hive metadata service

文章目录 一. Requirements二. Installing Hive from a Stable Release三. Running Hive四. Running Hive CLI五.Running HiveServer2 and Beeline1. 下载安装mysql2. 下载mysql驱动3. 配置hive-site.xml4. 初始化元数据库5. 通过beeline进行连接 一. Requirements Users are s…

简易博客系统自动化测试

&#x1f349;作者 Autumn60 &#x1f3c4;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f472;微语 &#xff1a;只有镜子和钱包&#xff0c;可以告诉你生活中&#xff0c;大部分的为什么和凭什么 文章目录 目录 文章目录 概述: 测试方法&am…

自动化测试和手动测试相比,哪个更具优势?

在软件测试行业中&#xff0c;争议最大的话题是“更好的是手动测试还是自动化测试”。尽管自动化测试最常谈论流行语&#xff0c;并且正在慢慢主导测试领域&#xff0c;手动测试的必要性不可忽视。 在本文中&#xff0c;将探讨手动测试和自动化测试之间的更深差异。 什么是手动…