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

news/2024/5/17 21:36:58

基于版本: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.javaprivate 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.javapublic 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() 的过程异步处理。

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

 

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/freezer/cgroup.freeze 节点。详细看下面第 5.2.1 节。

4. enableFreezer()

frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.javapublic 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 delayedsynchronized (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 messagereturn;}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.javaprivate void freezeProcess(final ProcessRecord proc) {int pid = proc.getPid(); // Unlocked intentionallyfinal 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 killedreturn;}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 hereif (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 racesint 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文件锁,如果该冻结进程持有文件锁,立即unfreezeif (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.javapublic static final native void setProcessFrozen(int pid, int uid, boolean frozen);
frameworks/base/core/jni/android_util_Process.cppvoid 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.cppbool 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;
}

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

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

6. unfreezeAppLSP()

frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.javavoid unfreezeAppLSP(ProcessRecord app) {final int pid = app.getPid();final ProcessCachedOptimizerRecord opt = app.mOptRecord;// 进程已经处于peding freeze中,移除冻结消息if (opt.isPendingFreeze()) {// Remove pending DO_FREEZE messagemFreezeHandler.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 了,无需 unfreezeif (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 处理

 

 
 


http://www.mrgr.cn/p/11186568

相关文章

k8s一站式使用笔记

前言 个人感觉比较磨心态&#xff0c;要坐住&#xff0c;因为细节太多&#xff0c;建议&#xff1a;一遍看个大概&#xff0c;二遍回来细品&#xff0c;不要当成任务&#xff0c;把握零碎时间 一、k8s安装 1、配置准备 硬件要求 内存&#xff1a;2GB或更多RAMCPU: 2核CPU或更…

【RabbitMQ】Linux系统服务器安装RabbitMQ

一、下载 首先应该下载erlang&#xff0c;rabbitmq运行需要有erland环境。 官网地址&#xff1a;https://www.erlang.org/downloads 下载rabbitmq 官网环境&#xff1a;https://www.rabbitmq.com/download.html 注意&#xff1a;el7对应centos7&#xff0c;el8对应centos8…

centos下安装ftp-读取目录列表失败-

1.下载安装ftp服务器端和客户端 #1.安装yum -y install vsftpdyum -y install ftp #2.修改配置文件vim /etc/vsftpd.conflocal_enablesYESwrite_enableYESanonymous_enableYESanon_mkdir_write_enableYES //允许匿名用户在FTP上创建目录anon_upload_enableYES //允许匿名用户…

数值线性代数: 共轭梯度法

本文总结线性方程组求解的相关算法&#xff0c;特别是共轭梯度法的原理及流程。 零、预修 0.1 LU分解 设&#xff0c;若对于&#xff0c;均有&#xff0c;则存在下三角矩阵和上三角矩阵&#xff0c;使得。 设&#xff0c;若对于&#xff0c;均有&#xff0c;则存在唯一的下三…

kotlin 编写一个简单的天气预报app(四)

编写界面来显示返回的数据 用户友好性&#xff1a;通过界面设计和用户体验优化&#xff0c;可以使天气信息更易读、易理解和易操作。有效的界面设计可以提高用户满意度并提供更好的交互体验。 增加城市名字的TextView <TextViewandroid:id"id/textViewCityName"…

matlab使用教程(5)—矩阵定义和基本运算

本博客介绍如何在 MATLAB 中创建矩阵和执行基本矩阵计算。 MATLAB 环境使用矩阵来表示包含以二维网格排列的实数或复数的变量。更广泛而言&#xff0c;数组为向量、矩阵或更高维度的数值网格。MATLAB 中的所有数组都是矩形&#xff0c;在这种意义上沿任何维度的分量向量的长度…

【英杰送书第三期】Spring 解决依赖版本不一致报错 | 文末送书

Yan-英杰的主 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 问题描述 报错信息如下 报错描述 解决方法 总结 【粉丝福利】 【文末送书】 目录&#xff1a; 本书特色&#xff1a; 问题描述 报错信息如下 Description:An attempt…

三、前端高德地图、测量两个点之前的距离

点击测距工具可以开启测量&#xff0c;再次点击关闭测量&#xff0c;清除地图上的点、连线、文字 再次点击测量工具的时候清除。 首先 上面的功能条河下面的地图我搞成了两个组件&#xff0c;他们作为兄弟组件存在&#xff0c;所以简单用js写了个事件监听触发的对象&#xff…

视频怎么加水印?这几种加水印方法非常简单

给视频加水印是一种保护知识产权的方法。水印是一种数字标记&#xff0c;可以包括作者的名称、品牌标识或其他信息&#xff0c;以便识别和追踪视频的来源。通过给视频加水印&#xff0c;能够有效地防止视频被盗用或未经授权的使用&#xff0c;让我们的知识产权得到更好的保护。…

fpga开发——蜂鸣器

蜂鸣器的原理 有源蜂鸣器和无源蜂鸣器 无源蜂鸣器利用电磁感应现象&#xff0c;为音圈接入交变电流后形成的电磁铁与永磁铁相吸或相斥而推动振膜发声&#xff0c;接入直流电只能持续推动振膜而无法产生声音&#xff0c;只能在接通或断开时产生声音。无源蜂鸣器的工作原理与扬声…

Ueditor 百度强大富文本Springboot 项目集成使用(包含上传文件和上传图片的功能使用)简单易懂,举一反三

Ueditor 百度强大富文本Springboot 项目集成使用 首先如果大家的富文本中不考虑图片或者附件的情况下&#xff0c;只考虑纯文本且排版的情况下我们可以直接让前端的vue来继承UEditor就可以啦。但是要让前端将那几个上传图片和附件的哪些功能给阉割掉&#xff01; 然后就是说如…

Cisco 路由器配置管理

大多数网络中断的最常见原因是错误的配置更改。对网络设备配置的每一次更改都伴随着造成网络中断、安全问题甚至性能下降的风险。计划外更改使网络容易受到意外中断的影响。 Network Configuration Manager 网络更改和配置管理 &#xff08;NCCM&#xff09;解决方案&#xff…

14个最强大的建筑设计AI工具

在整个行业中&#xff0c;建筑师在他们的创造性追求中正在拥抱一个新的合作伙伴&#xff1a;AI。 一旦受到重复和单调的困扰&#xff0c;建筑工人发现自己正处于数字革命的风口浪尖&#xff0c;其中比特和字节掌握着自动化和曾经难以想象的可能性的关键。 推荐&#xff1a;用 …

【Linux】网络基础之TCP协议

目录 &#x1f308;前言&#x1f338;1、基本概念&#x1f33a;2、TCP协议报文结构&#x1f368;2.1、源端口号和目的端口号&#x1f369;2.2、4位首部长度&#x1f36a;2.3、32位序号和确认序号&#xff08;重点&#xff09;&#x1f36b;2.4、16位窗口大小&#x1f36c;2.5、…

golang单元测试及mock总结

文章目录 一、前言1、单测的定位2、vscode中生成单测 二、构造测试case的注意事项1、项目初始化2、构造空interface{}3、构造结构体的time.Time类型4、构造json格式的test case 三、运行单测文件1、整体运行单测文件2、运行单个单测文件报错&#xff08;1&#xff09;command-l…

IO流(2)-缓冲流

1. 缓冲流的简单介绍 我们上贴说到了 FileInputStream&#xff0c;FileOutputStream&#xff0c;FileReader&#xff0c;FileWriter。 其实这四个流&#xff0c;我们通常把它叫做原始流&#xff0c;它们是比较偏底层的&#xff1b;而今天我们要说的四个缓冲流&#xff0c;如…

微服务 云原生:搭建 K8S 集群

为节约时间和成本&#xff0c;仅供学习使用&#xff0c;直接在两台虚拟机上模拟 K8S 集群搭建 踩坑之旅 系统环境&#xff1a;CentOS-7-x86_64-Minimal-2009 镜像&#xff0c;为方便起见&#xff0c;直接在 root 账户下操作&#xff0c;现实情况最好不要这样做。 基础准备 关…

vins调试的注意事项

1、摄像头的内参和畸变矫正系数 这个系数不对&#xff0c;没法做&#xff0c;因为下一步没法做对。这个会导致系统无法初始化。 2、对畸变的像素点&#xff0c;求得归一化坐标的方法 理解不同矫正模型的原理&#xff0c;确保矫正对了&#xff0c;得到z1平面的去畸变点。 3、摄…

map,set的封装(基于改造红黑树)

目录 引言 1.迭代器 2.map的[]重载 3.KeyOfValue模板参数 4.整体代码展示 //改造后的红黑树代码 #include <iostream> using namespace std;enum Colour {RED 0,BLACK, };//为了实现map与set封装使用同一个模板红黑树&#xff0c;前者的value是pair&#xff0c;后者…