当前位置: 首页 > news >正文

Android Framework 常见解决方案(25-1)定制CPUSET解决方案-framework部分修改

1 原理说明

这个方案有如下基本需求:

  • 构建自定义CPUSET,/dev/cpuset中包含一个全新的cpuset分组。且可以通过set_cpuset_policy和set_sched_policy接口可以设置自定义CPUSET。
  • 开机启动后可以通过zygote判定来对特定的应用进程设置CPUSET,并一直保持,且保证自定义CPUSET不受其他CPUSET影响,持续独立。

原理上因为修改代码涉及部分较多,因此共分3个部分:

  1. framework修改:添加SCHED GROUP和THREAD GROUP(THREAD_GROUP_对应SP_)的支持,且支持开机启动后直接设置。applyOomAdjLSP的判定保持不变。
  2. system修改:添加SP_CUSTOM的支持及set_cpuset_policy和set_sched_policy接口等支持,同时修改task_profiles.json,添加SP_CUSTOM CPUSET的支持。
  3. init.rc修改及编译部分调整:对自定义cpuset节点进行操作,vndk部分编译需要重新调整方案以及不修改VNDK如何保证编译通过。

由于修改中涉及代码量过大,这里拆分成两节进行展示。本章节主要针对第1部分修改进行说明。下一篇文章 👇

Android Framework 常见解决方案(25-2)定制CPUSET解决方案-system修改及编译部分调整

主要对第2和第3部分修改进行说明。

2 修改方案-framework部分(Android S)

这里需要添加THREAD_GROUP_CUSTOM,与system中修改的SP_CUSTOM同步,数值需一致,需要在$AOSP/frameworks/base/core/java/android/os/Process.java文件中修改:

public class Process {private static final String LOG_TAG = "Process";/*** An invalid UID value.*/public static final int INVALID_UID = -1;//.../*** Thread group for RT app.* @hide**/public static final int THREAD_GROUP_RT_APP = 6;/*** Thread group for bound foreground services that should* have additional CPU restrictions during screen off* @hide**/public static final int THREAD_GROUP_RESTRICTED = 7;/*** Thread Group for CUSTOM* @hide**/public static final int THREAD_GROUP_CUSTOM = 8;//...
}

接下来需要添加SCHED_GROUP_CUSTOM相关配置。在$AOSP/frameworks/base/services/core/java/com/android/server/am/ProcessList.java文件中修改:

public final class ProcessList {//...// Activity manager's version of Process.THREAD_GROUP_BACKGROUNDstatic final int SCHED_GROUP_BACKGROUND = 0;// Activity manager's version of Process.THREAD_GROUP_RESTRICTEDstatic final int SCHED_GROUP_RESTRICTED = 1;// Activity manager's version of Process.THREAD_GROUP_DEFAULTstatic final int SCHED_GROUP_DEFAULT = 2;// Activity manager's version of Process.THREAD_GROUP_TOP_APPpublic static final int SCHED_GROUP_TOP_APP = 3;// Activity manager's version of Process.THREAD_GROUP_TOP_APP// Disambiguate between actual top app and processes bound to the top appstatic final int SCHED_GROUP_TOP_APP_BOUND = 4;
+    //add custom schedule group
+    static final int SCHED_GROUP_CUSTOM = 10;//...private static boolean writeProcessOomListToProto(ProtoOutputStream proto, long fieldId,ActivityManagerService service, List<ProcessRecord> origList,boolean inclDetails, String dumpPackage) {//...switch (state.getSetSchedGroup()) {case SCHED_GROUP_BACKGROUND:schedGroup = ProcessOomProto.SCHED_GROUP_BACKGROUND;break;case SCHED_GROUP_DEFAULT:schedGroup = ProcessOomProto.SCHED_GROUP_DEFAULT;break;case SCHED_GROUP_TOP_APP:schedGroup = ProcessOomProto.SCHED_GROUP_TOP_APP;break;case SCHED_GROUP_TOP_APP_BOUND:schedGroup = ProcessOomProto.SCHED_GROUP_TOP_APP_BOUND;
+                case SCHED_GROUP_CUSTOM:
+                    schedGroup = ProcessOomProto.SCHED_GROUP_CUSTOM;
+                    break;}//...}//...private static boolean dumpProcessOomList(PrintWriter pw,ActivityManagerService service, List<ProcessRecord> origList,String prefix, String normalLabel, String persistentLabel,boolean inclDetails, String dumpPackage) {//...    for (int i = list.size() - 1; i >= 0; i--) {//...switch (state.getSetSchedGroup()) {case SCHED_GROUP_BACKGROUND:schedGroup = 'b';break;case SCHED_GROUP_DEFAULT:schedGroup = 'F';break;case SCHED_GROUP_TOP_APP:schedGroup = 'T';break;case SCHED_GROUP_RESTRICTED:schedGroup = 'R';break;case SCHED_GROUP_TOP_APP_BOUND:schedGroup = 'B';break;
+                case SCHED_GROUP_CUSTOM:
+                    schedGroup = 'C';
+                    break;default:schedGroup = '?';break;}}

添加SCHED_GROUP_CUSTOM,与ProcessList.java中同步。在$AOSP/frameworks/base/core/proto/android/server/activitymanagerservice.proto文件中修改:

//...
message ProcessOomProto {//...enum SchedGroup {SCHED_GROUP_UNKNOWN = -1;SCHED_GROUP_BACKGROUND = 0;SCHED_GROUP_DEFAULT = 1;SCHED_GROUP_TOP_APP = 2;SCHED_GROUP_TOP_APP_BOUND = 3;
+        SCHED_GROUP_CUSTOM = 10;}

 为适配之前的适配SP_CUSTOM,同时契合之前1中修改的task_profile.json文件中的配置,在AOSP/frameworks/base/core/jni/android_util_Process.cpp文件中修改:

//修改android_os_Process_setProcessGroup,适配SP_CUSTOM
void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
{ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);//...while ((de = readdir(d))) {//...// grp != SP_BACKGROUND. Only change the cpuset cgroup for low priority thread, so it could// preserve it sched policy profile setting.if (t_pri >= ANDROID_PRIORITY_BACKGROUND) {switch (grp) {case SP_SYSTEM:taskprofile = "ServiceCapacityLow";break;case SP_RESTRICTED:taskprofile = "ServiceCapacityRestricted";break;case SP_FOREGROUND:case SP_AUDIO_APP:case SP_AUDIO_SYS:taskprofile = "ProcessCapacityHigh";break;case SP_TOP_APP:taskprofile = "ProcessCapacityMax";break;
+                case SP_CUSTOM:
+                    taskprofile = "CustomPerformance";
+                    break;default:taskprofile = "ProcessCapacityNormal";break;}if (!SetTaskProfiles(t_pid, {taskprofile}, true)) {signalExceptionForGroupError(env, errno ? errno : EPERM, t_pid);break;}// Change the cpuset policy profile for non-low priority thread according to the grp} else {if (!SetTaskProfiles(t_pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true)) {signalExceptionForGroupError(env, errno ? errno : EPERM, t_pid);break;}}}closedir(d);
}//修改get_cpuset_cores_for_policy,适配SP_CUSTOM
static void get_cpuset_cores_for_policy(SchedPolicy policy, cpu_set_t *cpu_set)
{FILE *file;std::string filename;CPU_ZERO(cpu_set);switch (policy) {case SP_BACKGROUND:if (!CgroupGetAttributePath("LowCapacityCPUs", &filename)) {return;}break;case SP_FOREGROUND:if (!CgroupGetAttributePath("HighCapacityCPUs", &filename)) {return;}break;case SP_AUDIO_APP:case SP_AUDIO_SYS:if (!CgroupGetAttributePath("AudioAppCapacityCPUs", &filename)) {return;}if (access(filename.c_str(), F_OK) != 0) {if (!CgroupGetAttributePath("HighCapacityCPUs", &filename)) {return;}}break;case SP_RT_APP:if (!CgroupGetAttributePath("HighCapacityCPUs", &filename)) {return;}break;case SP_TOP_APP:if (!CgroupGetAttributePath("MaxCapacityCPUs", &filename)) {return;}break;
+        case SP_CUSTOM:
+            if (!CgroupGetAttributePath("CustomCPUs", &filename)) {
+                return;
+            }
+            break;default:return;}file = fopen(filename.c_str(), "re");if (file != NULL) {// Parse cpus stringchar *line = NULL;size_t len = 0;ssize_t num_read = getline(&line, &len, file);fclose (file);if (num_read > 0) {parse_cpuset_cpus(line, cpu_set);} else {ALOGE("Failed to read %s", filename.c_str());}free(line);}return;
}

在这里使用com.ags.test应用demo进行测试后验证。根据自己需要调整即可。主要保证该应用始终在SCHED_GROUP_CUSTOM中,不切换到其它schedule group中,同时建立SCHED_GROUP_CUSTOM和THREAD_GROUP_CUSTOM之间的联系。在$AOSP/frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java文件中修改:

//...
+import static android.os.Process.THREAD_GROUP_CUSTOM;
//...
public class OomAdjuster {//...private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,long nowElapsed) {boolean success = true;final ProcessStateRecord state = app.mState;if (state.getCurRawAdj() != state.getSetRawAdj()) {state.setSetRawAdj(state.getCurRawAdj());}//...int processGroup;switch (curSchedGroup) {case ProcessList.SCHED_GROUP_BACKGROUND:processGroup = THREAD_GROUP_BACKGROUND;break;case ProcessList.SCHED_GROUP_TOP_APP:case ProcessList.SCHED_GROUP_TOP_APP_BOUND:processGroup = THREAD_GROUP_TOP_APP;break;case ProcessList.SCHED_GROUP_RESTRICTED:processGroup = THREAD_GROUP_RESTRICTED;break;
+                    case ProcessList.SCHED_GROUP_CUSTOM:
+                        processGroup = THREAD_GROUP_CUSTOM;
+                        break;default:processGroup = THREAD_GROUP_DEFAULT;break;}//...}//...private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval,boolean computeClients) {//...
+        if(app.processName.equals("com.ags.test")){
+            schedGroup = ProcessList.SCHED_GROUP_CUSTOM;
+        }// Do final modification to adj.  Everything we do between here and applying// the final setAdj must be done in this function, because we will also use// it when computing the final cached adj later.  Note that we don't need to// worry about this for max adj above, since max adj will always be used to// keep it out of the cached vaues.state.setCurAdj(psr.modifyRawOomAdj(adj));state.setCurCapability(capability);state.setCurrentSchedulingGroup(schedGroup);state.setCurProcState(procState);state.setCurRawProcState(procState);state.updateLastInvisibleTime(hasVisibleActivities);state.setHasForegroundActivities(foregroundActivities);state.setCompletedAdjSeq(mAdjSeq);// if curAdj or curProcState improved, then this process was promotedreturn state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState|| state.getCurCapability() != prevCapability;}//... 
}

修改SpecializeCommon,主要是在创建进程时直接设置cpuset,在$AOSP/frameworks/base/core/jni/android_util_Process.cpp文件中修改:

static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,jobjectArray rlimits, jlong permitted_capabilities,jlong effective_capabilities, jint mount_external,jstring managed_se_info, jstring managed_nice_name,bool is_system_server, bool is_child_zygote,jstring managed_instruction_set, jstring managed_app_data_dir,bool is_top_app, jobjectArray pkg_data_info_list,jobjectArray allowlisted_data_info_list, bool mount_data_dirs,bool mount_storage_dirs) {const char* process_name = is_system_server ? "system_server" : "zygote";auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);//...if (setresgid(gid, gid, gid) == -1) {fail_fn(CREATE_ERROR("setresgid(%d) failed: %s", gid, strerror(errno)));}// Must be called when the new process still has CAP_SYS_ADMIN, in this case,// before changing uid from 0, which clears capabilities.  The other// alternative is to call prctl(PR_SET_NO_NEW_PRIVS, 1) afterward, but that// breaks SELinux domain transition (see b/71859146).  As the result,// privileged syscalls used below still need to be accessible in app process.SetUpSeccompFilter(uid, is_child_zygote);// Must be called before losing the permission to set scheduler policy.SetSchedulerPolicy(fail_fn, is_top_app);+    if (nice_name.has_value()) {
+        if(strncmp(nice_name.value().c_str(),"com.ags.test",sizeof("com.ags.test"))==0){
+#if 1 //set_cpuset_policy方案
+            int ret = set_cpuset_policy(0, SP_CUSTOM);
+            if (ret != 0) {
+                ALOGE("set_cpuset_policy call default failure,ret:%d,(%s)", ret,nice_name.value().c_str());
+            }else{
+                ALOGD("set_cpuset_policy call default success,ret==0,(%s)", nice_name.value().c_str());
+            }
+#else //set_sched_policy兼容方案
+            int ret = set_sched_policy(0, SP_CUSTOM);
+            if (ret != 0) {
+                ALOGE("set_sched_policy call default failure,ret:%d,(%s)", ret,nice_name.value().c_str());
+            }
+           else{
+               ALOGD("set_sched_policy call default success,ret==0,(%s)", nice_name.value().c_str());
+            }
+#endif
+        }
+    }//...}

至此,framework层相关修改就结束了。主要添加SCHED_GROUP_CUSTOM、THREAD_GROUP_CUSTOM(THREAD_GROUP_对应SP_)即以和对应SP_CUSTOM之间的联系建立,且支持开机启动后直接过滤包,进行CPUSET的设置。同时applyOomAdjLSP的判定保持不变,不受其他CPUSET的影响。

相关文章:

Android Framework 常见解决方案(25-1)定制CPUSET解决方案-framework部分修改

1 原理说明 这个方案有如下基本需求&#xff1a; 构建自定义CPUSET&#xff0c;/dev/cpuset中包含一个全新的cpuset分组。且可以通过set_cpuset_policy和set_sched_policy接口可以设置自定义CPUSET。开机启动后可以通过zygote判定来对特定的应用进程设置CPUSET&#xff0c;并…...

PyTorch 参数化深度解析:自定义、管理和优化模型参数

目录 torch.nn子模块parametrize parametrize.register_parametrization 主要特性和用途 使用场景 参数和关键字参数 注意事项 示例 parametrize.remove_parametrizations 功能和用途 参数 返回值 异常 使用示例 parametrize.cached 功能和用途 如何使用 示例…...

自承载 Self-Host ASP.NET Web API 1 (C#)

本教程介绍如何在控制台应用程序中托管 Web API。 ASP.NET Web API不需要 IIS。 可以在自己的主机进程中自托管 Web API。 创建控制台应用程序项目 启动 Visual Studio&#xff0c;然后从“开始”页中选择“新建项目”。 或者&#xff0c;从“ 文件 ”菜单中选择“ 新建 ”&a…...

Vue2-子传父和父传子的基本用法

在Vue 2中&#xff0c;可以使用props和$emit来实现子组件向父组件传值&#xff08;子传父&#xff09;和父组件向子组件传值&#xff08;父传子&#xff09;。 子传父&#xff08;子组件向父组件传值&#xff09;的基本用法如下&#xff1a; 在父组件中定义一个属性&#xff…...

使用numpy处理图片——镜像翻转和旋转

在《使用numpy处理图片——基础操作》一文中&#xff0c;我们介绍了如何使用numpy修改图片的透明度。本文我们将介绍镜像翻转和旋转。 镜像翻转 上下翻转 from PIL import Image import numpy as np img Image.open(example.png) data np.array(img)# axis0 is vertical, a…...

HTML5 article标签,<time>...</time>标签和pubdate属性的运用

1、<article>...</article>标签的运用 article标签代表文档、页面或应用程序中独立的、完整的、可以独自被外部引用的内容。它可以是一篇博客或报竟杂志中的文章、一篇论坛帖子、一段用户评论或一个独立的插件&#xff0c;或者其他任何独立的内容。把文章正文放在h…...

Amazing OpenAI API:把非 OpenAI 模型都按 OpenAI API 调用

分享一个有趣的小工具&#xff0c;10MB 身材的小工具&#xff0c;能够将各种不同的模型 API 转换为开箱即用的 OpenAI API 格式。 让许多依赖 OpenAI API 的软件能够借助开发者能够接触到的&#xff0c;非 OpenAI 的 API 私有部署和使用起来。 写在前面 这个小工具软件写于两…...

RK3568平台开发系列讲解(驱动篇)pinctrl 函数操作集结构体讲解

🚀返回专栏总目录 文章目录 一、pinctrl_ops二、pinmux_ops三、pinconf_ops沉淀、分享、成长,让自己和他人都能有所收获!😄 pinctrl_ops:提供有关属于引脚组的引脚的信息。pinmux_ops:选择连接到该引脚的功能。pinconf_ops:设置引脚属性(上拉,下拉,开漏,强度等)。…...

vue购物车案例,v-model 之 lazy、number、trim,与后端交互

购物车案例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><script src"./js/vue.js"></script> </head> <body> <div id"d1"&…...

云原生Kubernetes: Kubeadm部署K8S 1.29版本 单Master架构

目录 一、实验 1.环境 2.K8S master节点环境准备 3.K8S master节点安装kubelet、kubeadm、kubectl 3.K8S node节点环境准备与软件安装 4.K8S master节点部署服务 5.K8S node节点部署 6.K8S master节点查看集群 7.容器网络&#xff08;CNI&#xff09;部署 8.K8S 集群…...

C++协程操作

什么是C++协程 C++中的协程是一种用户态轻量级线程,它拥有自己的上下文和栈,并且协程的切换和调度由用户定义,不需要陷入内核。如同一个进程可以拥有多个线程,一个线程也可以拥有多个协程。协程的优点在于极高的执行效率,因为协程切换不需要陷入内核,而是由用户程序定义切…...

计算机配件杂谈-鼠标

目录 基础知识鼠标的发展鼠标的左右手鼠标的显示样式鼠标的移动和可见性移动可见性 现在的我们的生活工作都基本上离不开电脑了&#xff0c;不管是你平时玩玩游戏&#xff0c;上班工作等等&#xff1b; 今天将关于鼠标的一些小的技巧分享出来&#xff0c;共勉&#xff01; 基础…...

用Python来制作一个微信聊天机器人

1. 效果展示 通过本地搭建一个flask服务器来接收信息&#xff0c;这里我简单使用展示&#xff0c;就没有对接收的信息进行处理了。 信息接收展示 发送信息展示 这里就直接使用python发送一个post请求即可&#xff0c;可以发送文字或者图片 代码展示 接收信息 #!/usr/bin/e…...

2024年第九届机器学习技术国际会议(ICMLT 2024) 即将召开

2024年第九届机器学习技术国际会议&#xff08;ICMLT 2024&#xff09;将于2024年5月24-26日在挪威奥斯陆举行。ICMLT 2024旨在讨论机器学习技术领域的最新研究技术现状和前沿趋势&#xff0c;为来自世界各地的科学家、工程师、实业家、学者和其他专业人士提供一个互动和交流的…...

算法训练day9Leetcode232用栈实现队列225用队列实现栈

今天学习的文章和视频链接 https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 栈与队列理论基础 见我的博客 https://blog.csdn.net/qq_36372352/article/details/135470438?spm1001.2014.3001.5501 232用栈实现…...

linux驱动(四):platform

本文主要探讨x210驱动的平台设备类型(platform)以及misc设备。 驱动模型 设备驱动模型&#xff1a;总线(bus type)、设备(device)和驱动(driver) 总线&#xff1a;虚拟总线用于挂接驱动驱动和设备 总线、设备、驱动关系&#xff1a;/sys/bus下的子目录…...

Guava:Cache强大的本地缓存框架

Guava Cache是一款非常优秀的本地缓存框架。 一、 经典配置 Guava Cache 的数据结构跟 JDK1.7 的 ConcurrentHashMap 类似&#xff0c;提供了基于时间、容量、引用三种回收策略&#xff0c;以及自动加载、访问统计等功能。 基本的配置 Testpublic void testLoadingCache() th…...

#{}和${}的区别?

#{}是占位符&#xff0c;预编译处理&#xff1b;${}是拼接符&#xff0c;字符串替换&#xff0c;没有预编译处理。Mybatis在处理#{}时&#xff0c;#{}传入参数是以字符串传入&#xff0c;会将SQL中的#{}替换为?号&#xff0c;调用PreparedStatement的set方法来赋值。Mybatis在…...

string的模拟实现

string的模拟实现 msvc和g下的string内存比较成员变量构造函数与析构函数拷贝构造函数赋值拷贝c_str、size和capacity函数以及重载[]、clear、expand_capacity迭代器与遍历reservepush_back、append、insert字符串比较运算符erase<<流提取 >>流插入resizefindsubst…...

算法练习:查找二维数组中的目标值

题目&#xff1a; 编写一个高效的算法来搜索矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a;每行的元素从左到右升序排列。每列的元素从上到下升序排列。 实现&#xff1a; 1. main方法 public static void main(String[] args) {int[][] matrix {{1…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

23-Oracle 23 ai 区块链表(Blockchain Table)

小伙伴有没有在金融强合规的领域中遇见&#xff0c;必须要保持数据不可变&#xff0c;管理员都无法修改和留痕的要求。比如医疗的电子病历中&#xff0c;影像检查检验结果不可篡改行的&#xff0c;药品追溯过程中数据只可插入无法删除的特性需求&#xff1b;登录日志、修改日志…...

基于服务器使用 apt 安装、配置 Nginx

&#x1f9fe; 一、查看可安装的 Nginx 版本 首先&#xff0c;你可以运行以下命令查看可用版本&#xff1a; apt-cache madison nginx-core输出示例&#xff1a; nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...