Android 中 cgroup抽象层详解

news2025/5/20 19:51:59

源码基于:Android R

0. 前言

在之前的博文《Android中app freezer原理》一文中,我们看到冻结器的enable、freeze、unfreeze 都是通过 cgroup 的机制进行处理。

本文将介绍下 Android 中 cgroup 的抽象层基本信息和使用方式。

1. cgroups 简介

cgroups (全称:control groups) 是 Linux 内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 CPU、memory 等资源实现精细化的控制。目前越来越活的轻量级容器 Docker 就使用了 cgroups 提供的资源限制能力来完成 CPU、memory 等部门的资源控制。

cgroups 为每种可以控制的资源定义了一个子系统,典型的子系统如下:

  • cpu:主要限制进程的 cpu 使用率;
  • cpuaat:可以统计 cgroups 中的进程的 cpu 使用报告;
  • cpuset:可以为 cgroups 中的进程分配单独的 cpu 节点或内存节点;
  • memory:可以限制进程的 memory 使用量;
  • blkio:可以限制进程的块设备 io;
  • devices:可以控制进程能够访问某些设备;
  • freezer:可以挂起或恢复 cgroups 中的进程;
  • net_cls:可以标记 cgroups 中进程的网络数据包,然后可以使用 tc (traffic control)模块对数据包进行控制;
  • ns:可以使不同的 cgroups 下面的进程使用不同的 namespace;

Android 使用 cgroups 来控制和考量如 CPU、memory 等系统资源的使用和分配情况,并支持 Linux 内核 cgroup v1cgroup v2 版本。

2. Android cgroup 抽象层简介

Android Q(10) 或更高版本通过 task profiles 使用 cgroup 抽象层,task profiles 可以用来描述应用于某个线程或进程的一个set 或 sets 的限制。系统依照 task profiles 的规定选择一个或多个适当的 cgroups。通过这种限制,可以对底层的 cgroup 功能集进行更改,而不会影响较高的软件层。

Android P(9) 和更低版本,可用的 cgroups 以及他们的挂载点、版本都会在 init.rc 中设定。虽然这些信息可以更改,但 Android framework 的设定 (基于init.rc) 是一组特定的 cgroups 存在于特定的位置,并具有特定版本和子group 层级。这限制了选择下一个 cgroup 版本使用的能力,也限制了更改 cgroup 层级去使用新功能的能力。

在 Android Q(10) 或更高版本,将 cgroups 和 task profiles 搭配使用:

  • cgroup 配置:开发人员在 cgroups.json 文件中 cgroups 描述 cgroups 配置,以此定义 cgroups 组以及他们的挂载点和 attibutes。所有的 cgroups 将在 early-init 阶段被挂载上。
  • task profiles:这些配置文件提供了一种抽象概念,将必需的功能与该功能的实现详情分离。Android framework 使用 SetTaskProfiles 和 SetProcessProfiles 接口(这些API 是 Android R 或更高版本独有),按照 task_profiles.json 文件中描述将 task profiles 应用到一个进程或一个线程。

3. cgroups.json

文件路径:system/core/libprocessgroup/profiles/cgroups.json

system/core/libprocessgroup/profiles/cgroups.json

{
  "Cgroups": [
    {
      "Controller": "blkio",
      "Path": "/dev/blkio",
      "Mode": "0755",
      "UID": "system",
      "GID": "system"
    },
    {
      "Controller": "cpu",
      "Path": "/dev/cpuctl",
      "Mode": "0755",
      "UID": "system",
      "GID": "system"
    },
    {
      "Controller": "cpuacct",
      "Path": "/acct",
      "Mode": "0555"
    },
    {
      "Controller": "cpuset",
      "Path": "/dev/cpuset",
      "Mode": "0755",
      "UID": "system",
      "GID": "system"
    },
    {
      "Controller": "memory",
      "Path": "/dev/memcg",
      "Mode": "0700",
      "UID": "root",
      "GID": "system"
    },
    {
      "Controller": "schedtune",
      "Path": "/dev/stune",
      "Mode": "0755",
      "UID": "system",
      "GID": "system"
    }
  ],
  "Cgroups2": {
    "Path": "/sys/fs/cgroup",
    "Mode": "0755",
    "UID": "system",
    "GID": "system",
    "Controllers": [
      {
        "Controller": "freezer",
        "Path": "freezer",
        "Mode": "0755",
        "UID": "system",
        "GID": "system"
      }
    ]
  }
}

cgroup v1 和 cgroup v2 描述的规则不一样。

对于 cgroup v1,必须拥有:

  • Controller:指定 cgroups 子系统名称,之后 task profiles 中设定需要依赖该名称;
  • Path:指定挂载的路径,有了该路径 task profiles 下才可以指定文件名;
  • Mode:用于指定Path 目录下文件的执行 mode;
  • UID:指定 user ID,指定Path 目录下文件的owner;
  • GID:指定 group ID,指定Path 目录下文件的owner;

对于 cgroup v2,基本同 v1,Controllers 中定义了子 cgroup,这些都挂载在同一个目录下。子 cgroup 中的 Path 是相对于根 Path。例如这里 freezer 的 Path 设定了 freezer,就是在 根 Path /sys/fs/cgroup/ 目录下创建一个目录 freezer。

另外 cgroups.json 文件可能不止一个:

/system/core/libprocessgroup/profiles/cgroups.json   //默认文件
/system/core/libprocessgroup/profiles/cgroups_<API level>.json   //API级别的文件,R版本没有,S版本很多
/vendor/xxx/cgroups.json   //vendor自定义文件

这三种文件加载顺序是:默认 -> API 级别 -> vendor,于是就存在一个覆盖的流程,只要后面的文件中定义的 Controller 值与前面的相同,就会覆盖前者的定义。

4. task profiles

文件路径:system/core/libprocessgroup/profiles/task_profiles.json

{
  "Attributes": [
    {
      "Name": "MemSoftLimit",
      "Controller": "memory",
      "File": "memory.soft_limit_in_bytes"
    },
    {
      "Name": "MemSwappiness",
      "Controller": "memory",
      "File": "memory.swappiness"
    },
    {
      "Name": "FreezerState",
      "Controller": "freezer",
      "File": "cgroup.freeze"
    }
  ],

  "Profiles": [
    {
      "Name": "Frozen",
      "Actions": [
        {
          "Name": "JoinCgroup",
          "Params":
          {
            "Controller": "freezer",
            "Path": ""
          }
        }
      ]
    },
    {
      "Name": "TimerSlackHigh",
      "Actions": [
        {
          "Name": "SetTimerSlack",
          "Params":
          {
            "Slack": "40000000"
          }
        }
      ]
    },
    {
      "Name": "PerfBoost",
      "Actions": [
        {
          "Name": "SetClamps",
          "Params":
          {
            "Boost": "50%",
            "Clamp": "0"
          }
        }
      ]
    },
    {
      "Name": "HighMemoryUsage",
      "Actions": [
        {
          "Name": "SetAttribute",
          "Params":
          {
            "Name": "MemSoftLimit",
            "Value": "512MB"
          }
        },
        {
          "Name": "SetAttribute",
          "Params":
          {
            "Name": "MemSwappiness",
            "Value": "100"
          }
        }
      ]
    },
    {
      "Name": "FreezerEnabled",
      "Actions": [
        {
          "Name": "SetAttribute",
          "Params":
          {
            "Name": "FreezerState",
            "Value": "1"
          }
        }
      ]
    }
  ],

  "AggregateProfiles": [
    {
      "Name": "SCHED_SP_DEFAULT",
      "Profiles": [ "TimerSlackNormal" ]
    },
    {
      "Name": "SCHED_SP_BACKGROUND",
      "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
    },
    {
      "Name": "SCHED_SP_FOREGROUND",
      "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
    },
    {
      "Name": "SCHED_SP_TOP_APP",
      "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
    },
    ...
  ]
}

整个文件配置由一个大括号包含,总体由三部分组成:

  • Attributes
  • Profiles
  • AggregateProfiles

另外,task_profiles.json 文件也不止一个:

system/core/libprocessgroup/profiles/task_profiles.json     //默认
system/core/libprocessgroup/profiles/task_profiles_<API level>.json  //API级别的文件,R版本没有,S有很多
vendor/xxx/task_profiles.json   //vendor配置

加载、覆盖的顺序同cgroups.json,按照 Name 来匹配,只要两个文件中定义同名项,后者就会覆盖前者的定义。 

4.1 Attributes 段

Attributes 中 cgroups 中特定的文件。

Attributes 是task profiles 文件定义中的引用。在 task profiles 之外,只有当 framework 请求直接访问这些文件,且无法使用 task profiles 抽象访问时。其他情况下,使用 task profiles,它可以更好地分离所需行为及其实现详情。

Attributes 中每一项包含:

  • Name: 该 Attribute 的名称,profiles 中引用时使用该Name 值;
  • Controller:引用 cgroups.json 文件中的一个 cgroup controller,引用 cgroup 的Controller 值;
  • File:在 cgroup Controller 所在的目录下的一个特殊文件;

如上面:

  "Attributes": [
    {
      "Name": "FreezerState",
      "Controller": "freezer",
      "File": "cgroup.freeze"
    }
  ],

用的是 Controller 为 freezer 的 cgroup,从上面第 3 节中得知,它采用 cgroups v2 的格式,cgroup Path 为 /sys/fs/cgroup/freezer/,这里定义的 attribute 指定的是该目录下 cgroup.freeze 文件。

 

在代码中,通过 ProfileAttribute 类来管理每个 Attribute:

system/core/libprocessgroup/task_profiles.h

class ProfileAttribute {
  public:
    ProfileAttribute(const CgroupController& controller, const std::string& file_name)
        : controller_(controller), file_name_(file_name) {}

    const CgroupController* controller() const { return &controller_; }
    const std::string& file_name() const { return file_name_; }
    void Reset(const CgroupController& controller, const std::string& file_name);

    bool GetPathForTask(int tid, std::string* path) const;

  private:
    CgroupController controller_;
    std::string file_name_;
};

4.2 Profiles 段

每一项定义包含:

  • Name:指定 profile name;
  • Actions:罗列该 profile 被应用时,需要执行的 actions 集合,每个 action 包含:
    • Name:需要执行的 action 类别;
    • Params:该 action 所需的参数的集合;

下面来看下Actions 中 Name 可选的类别及其Params 配置:

ActionParameterDescription
SetTimerSlackSlack定时器可宽延的时间,单位为 ns
SetAttributeName引用Attributes 中的某一个属性的名称
Value要写入到attribute指定文件中的数据
WriteFileFilePath文件路径
Value要写入到文件的值
JoinCgroupController文件 cgroups.json 中的cgroup名称
Pathcgroup 层次结构中的额子组路径

 

4.2.1 SetTimerSlack

SetTimerSlack 只有一个参数 Slack,这个参数对应 /proc/PID/timerslack_ns 节点。TimerSlack 是Linux 系统为了降低系统功耗、避免 timer 时间参差不齐、过于频繁的唤醒 cpu,而设置的一种对齐策略。这个值关系到进程的定时器,如 select、epoll_wait、sleep 等API 的唤醒时间。

在Linux 4.6+ 版本,都是支持 /proc/PID/timerslack_ns 节点。

具体参考:https://cloud.tencent.com/developer/article/1836285

 

在代码中,通过 SetTimerSlackAction 类来管理该 profile:

system/core/libprocessgroup/task_profiles.cpp

bool SetTimerSlackAction::ExecuteForTask(int tid) const {
    static bool sys_supports_timerslack = IsTimerSlackSupported(tid);

    if (sys_supports_timerslack) {
        auto file = StringPrintf("/proc/%d/timerslack_ns", tid);
        if (!WriteStringToFile(std::to_string(slack_), file)) {
            if (errno == ENOENT) {
                // This happens when process is already dead
                return true;
            }
            PLOG(ERROR) << "set_timerslack_ns write failed";
        }
    }

    // TODO: Remove when /proc/<tid>/timerslack_ns interface is backported.
    if (tid == 0 || tid == GetThreadId()) {
        if (prctl(PR_SET_TIMERSLACK, slack_) == -1) {
            PLOG(ERROR) << "set_timerslack_ns prctl failed";
        }
    }

    return true;
}

4.2.2 SetAttribute

SetAttribute则跟 task_profiles.json 中的Attributes挂钩起来,对应了SetAttributeAction。

SetAttribute 有两个参数,Name 指的就是之前定义的 Attribute 的名称,Value 则是往 Attribute 对应的cgroup 的子节点写入的值。

在代码中,通过 SetAttributeAction 类来管理 SetAttribute 的profile:

system/core/libprocessgroup/task_profiles.cpp

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,类型为 ProfileAttribute。

代码中可以获知,先根据 Attribute 中的path,再将 value 写入文件节点中。

 

4.2.3 JoinCgroup

JoinCgroup 只有两个参数Controller 和 Path,Controller 指的是 cgroups 的 subsystem,Path 则是指该 subsystem 下的路径,也就是子 cgroup。通过该配置,将设置成这个profile 的进程或线程加入到该 subsystem 的子 cgroup中,受这个cgroup 的资源限制。

在代码中通过 SetCgroupAction 类来管理这个profile。

例如上面的:

{
  "Attributes": [
    ...
  ],

  "Profiles": [
    {
      "Name": "Frozen",
      "Actions": [
        {
          "Name": "JoinCgroup",
          "Params":
          {
            "Controller": "freezer",
            "Path": ""
          }
        }
      ]
    }
  ],

  "AggregateProfiles": [
    ...
  ]
}

这里配置的 profile 名字为 Frozen,利用的是Cgroup Controller 为 freezer,Path 为空。

也就是说该 profile 需要使用 /sys/fs/cgroup/freezer/ 目录下的某个子 cgroup 文件。具体看系统调用。通过查找,系统在 CachedAppOptimizer 类中会调用 Process.setProcessFrozen(),进而调用到 jni android_util_Process_setProcessFrozen() 接口:

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);
    }
}

当进程进行 freeze 或 unfreeze 的时候,会调用 SetProcessProfiles(),精细是 SetCgroupAction 类型的profile,最终调用 ExecuteForProcess():

system/core/libprocessgroup/task_profiles.cpp

bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
    std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid);
    unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(procs_path.c_str(), O_WRONLY | O_CLOEXEC)));
    if (tmp_fd < 0) {
        PLOG(WARNING) << "Failed to open " << procs_path;
        return false;
    }
    if (!AddTidToCgroup(pid, tmp_fd)) {
        LOG(ERROR) << "Failed to add task into cgroup";
        return false;
    }

    return true;
}

通过函数,先通过 Controller 的 GetProcsFilePath() 接口获取该profile 需要修改的path,参数为该 profile 配置的 Path:

system/core/libprocessgroup/cgroup_map.cpp

std::string CgroupController::GetProcsFilePath(const std::string& rel_path, uid_t uid,
                                               pid_t pid) const {
    std::string proc_path(path());
    proc_path.append("/").append(rel_path);
    proc_path = regex_replace(proc_path, std::regex("<uid>"), std::to_string(uid));
    proc_path = regex_replace(proc_path, std::regex("<pid>"), std::to_string(pid));

    return proc_path.append(CGROUP_PROCS_FILE);
}

 最终写的文件就是 CGROUP_PROCS_FILE,也就是 cgroup.procs 文件。

4.3 AggregateProfiles 段

在 Android 12 或更高版本中,task_profiles.json 文件中还包含了 AggregateProfiles 段。

这里定义一个或多个 profile 的别名,由一下内容组成:

  • Name:指定aggregate profile 的名称;
  • Profiles:该 aggregate profile 包含的 profil 名称集合;

当一个 aggregate profile 被应用时,里面包含的所有的 profile 都会被自动的应用。

如上面:

  "AggregateProfiles": [
    {
      "Name": "SCHED_SP_FOREGROUND",
      "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
    },
    ...
  ]

当应用 SCHED_SP_FOREGROUND 这个 aggregate profile时,里面包含的所有的 profiles (High

Performance、HighIoPriority、TimerSlackNormal) 都会被应用。

另外,如果没有递归,aggregate profiles 中可以包含单独的profiles 或其他 aggregate profiles。

5. cgroups 初始化

在 init 启动的第二阶段会调用:

system/core/init/init.cpp

int SecondStageMain(int argc, char** argv) {
    ...
    am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
    ...
}
system/core/init/init.cpp

static Result<void> SetupCgroupsAction(const BuiltinArguments&) {
    // Have to create <CGROUPS_RC_DIR> using make_dir function
    // for appropriate sepolicy to be set for it
    make_dir(android::base::Dirname(CGROUPS_RC_PATH), 0711);
    if (!CgroupSetup()) {
        return ErrnoError() << "Failed to setup cgroups";
    }

    return {};
}

创建一个 CGROUPS_RC_PATH 文件:/dev/cgroup_info/cgroup.rc

之后将 cgroups.json 文件的信息写入到 cgroup.rc 文件中,以供 task_profiles 读取controller 信息。

 

6. Task profiles

通过代码,我们其实可以清晰看到,TaskProfiles 类在构造的时候开始解析 task_profile.json:

syste/core/libprocessgroup/task_profiles.cpp

TaskProfiles::TaskProfiles() {
    // load system task profiles
    if (!Load(CgroupMap::GetInstance(), TASK_PROFILE_DB_FILE)) {
        LOG(ERROR) << "Loading " << TASK_PROFILE_DB_FILE << " for [" << getpid() << "] failed";
    }

    // load vendor task profiles if the file exists
    if (!access(TASK_PROFILE_DB_VENDOR_FILE, F_OK) &&
        !Load(CgroupMap::GetInstance(), TASK_PROFILE_DB_VENDOR_FILE)) {
        LOG(ERROR) << "Loading " << TASK_PROFILE_DB_VENDOR_FILE << " for [" << getpid()
                   << "] failed";
    }
}

主要通过 Load() 去解析两个文件:

  • TASK_PROFILE_DB_FILE (/etc/task_profiles.json)
  • TASK_PROFILE_DB_VENDOR_FILE (/vendor/etc/task_profiles.json)

在Load() 中会分别解析 task_profiles.json 文件中的 Attributes、Profiles、AggregateProfiles 段 。这里暂不过多剖析。我们在 task profiles 解析完成之后,系统通过 SetProcessProfiles() 或 SetTaskProfiles() 来达到应用 profile 的目的。

6.1 SetProcessProfiles()

system/core/libprocessgroup/processgroup.cpp

bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
    return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles);
}

这是一个全局的函数,通过 TaskProfiles 的单例调用 task profiles 下的SetProcessProfiles():

system/core/libprocessgroup/task_profiles.cpp

bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid,
                                      const std::vector<std::string>& profiles) {
    for (const auto& name : profiles) {
        TaskProfile* profile = GetProfile(name);
        if (profile != nullptr) {
            if (!profile->ExecuteForProcess(uid, pid)) {
                PLOG(WARNING) << "Failed to apply " << name << " process profile";
            }
        } else {
            PLOG(WARNING) << "Failed to find " << name << "process profile";
        }
    }
    return true;
}

进一步通过 profiles 的name,确定精细的 profile,进而调用 ExecuteForProcess() 函数,如上面的第 4.2.3 节,最终精细的就是 SetCgroupAction 这个 profile。

流程大致如下:

6.2 SetTaskProfiles()

system/core/libprocessgroup/processgroup.cpp

bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache) {
    return TaskProfiles::GetInstance().SetTaskProfiles(tid, profiles, use_fd_cache);
}

具体的流程同 SetProcessProfiles() 函数,最终调用的是 profile action 的ExecuteForTask() 函数。

 

 

至此,关于Android 中 cgroup 的抽象层大致讲述完了,代码逻辑很清晰,主要的内核代码在后期会详细剖析。这里总结下:

  • 通过 cgroups.json 配置 cgroup 的所有子系统,Controller 的名称会在后面 Attributes 或 Profiles 中使用到。另外,这样的 cgroups.json 文件可能还有很多,有加载顺序,也就有了覆盖;
  • 通过 task_profiles.json 配置所有活动,利用之前的 cgroups.json 中定义的子系统,进一步定义Attributes、Profiles 及 AggregateProfiles。同样的,也有加载顺序,也就有了覆盖;
  • cgroups.json 的解析是在 init 的第二阶段完成;
  • 系统会创建一个 TaskProfiles 的单例,管理所有的 profile,而 profile 中也维护着对应的 actions;
  • 通过接口 SetProcessProfiles() 来应用特定的 profile 到进程上;
  • 通过接口 SetTaskProfiles() 来应用特定的 profile 到线程上;

 

 

 

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

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

相关文章

Linux常用命令——dump命令

在线Linux命令查询工具 dump 用于备份ext2或者ext3文件系统 补充说明 dump命令用于备份ext2或者ext3文件系统。可将目录或整个文件系统备份至指定的设备&#xff0c;或备份成一个大文件。 语法 dump(选项)(参数)选项 -0123456789&#xff1a;备份的层级&#xff1b; -b&…

layui框架学习(32:表单)

Layui中的分页模块laypage支持在指定的容器中显示分页信息&#xff0c;主要是指总页数、当前页面、上一页/下一页等信息&#xff0c;可以配合其它组件实现分页显示数据或者分页显示内容&#xff0c;事实上&#xff0c;Layui中的动态数据表格等模块都附带有显示分页功能。laypag…

hive之文件格式与压缩

hive文件格式&#xff1a; 概述&#xff1a; 为Hive表中的数据选择一个合适的文件格式&#xff0c;对提高查询性能的提高是十分有益的。Hive表数据的存储格式&#xff0c;可以选择text file、orc、parquet、sequence file等。 文本文件&#xff1a; 文本文件就是txt文件&…

【算法 -- LeetCode】(025) K 个一组翻转链表

1、题目 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点…

企业服务器数据库中了360后缀勒索病毒怎么解决加密的方式有哪些

随着网络安全问题日益突出&#xff0c;企业服务器数据库成为黑客攻击的重要目标之一。近日&#xff0c;我们收到很多企业的求助&#xff0c;企业的服务器数据库遭到了360后缀的勒索病毒攻击&#xff0c;导致企业内部的许多重要数据被加密无法读取使用&#xff0c;严重影响了企业…

05网络模型练习题

新版MATLAB中&#xff0c;图论工具箱的函数进行了更换 MATLAB中文参考文档&#xff1a;https://ww2.mathworks.cn/help/matlab/index.html 遍历、最短路径和循环 bfsearch 广度优先图搜索 dfsearch 深度优先图搜索 shortestpath 两个单一节点之间的最短路径 shortestpathtree …

Flask 笔记

Flask 笔记 一、Flask介绍 1、学习Flask框架的原因 2020 Python 开发者调查结果显示Flask和Django是Python Web开发使用的最主要的两个框架。 2、Flask介绍 ​ Flask诞生于2010年&#xff0c;是Armin ronacher用Python 语言基于Werkzeug工具箱编写的轻量级Web开发框架。 ​…

企业数字化转型要转什么?怎么转?_光点科技

随着科技的飞速发展和互联网的普及&#xff0c;数字化转型已成为现代企业持续发展和提高竞争力的必经之路。数字化转型是指企业在信息技术的驱动下&#xff0c;将传统的业务模式、流程和文化进行全面优化和改造&#xff0c;以适应数字化时代的新要求。 一、企业数字化转型的重要…

二极管常见参数

写在前面&#xff1a; 本文章旨在总结备份、方便以后查询&#xff0c;由于是个人总结&#xff0c;如有不对&#xff0c;欢迎指正&#xff1b;另外&#xff0c;内容大部分来自网络、书籍、和各类手册&#xff0c;如若侵权请告知&#xff0c;马上删帖致歉。 目录 肖特基二极管PN…

CCLINK IE转MODBUS-TCP网关cclink与以太网的区别

你是否曾经遇到过需要同时处理CCLINK IE FIELD BASIC和MODBUS两种数据协议的情况&#xff1f;远创智控的YC-CCLKIE-TCP网关可以帮助你解决这个问题。 远创智控YC-CCLKIE-TCP网关可以分别从CCLINK IE FIELD BASIC一侧和MODBUS一侧读写数据&#xff0c;然后将数据存入各自的缓冲区…

SpringBoot整合可视化监控工具——SpringBoot Admin

目录 父项目 子模块——server 子模块——client 开放监控指标 性能 环境 日志配置 映射 问题一: 明明项目启动却还是Instance 是 office 问题二: 记springboot中yml文件最后一位是星号*的写法 在说软件监控之前&#xff0c;我们先来了解一下软件的发展史&#xff0c…

【字符流】案例:文件到集合

案例&#xff1a;文件到集合 1.需求&#xff1a; 把文本文件中的数据读取到集合&#xff0c;并遍历集合。要求&#xff1a;文件中的每一行数据是一个集合元素 2.思路 创建字符缓冲输入流对象创建ArrayList集合对象调用字符缓冲输入流对象的方法读数据把读取到的字符串数据存…

【Lingo软件求解案例一:MCM1988:B题两辆平板车的装货问题】

题目描述 有 七种规格的包装箱 要装到 两辆平板车 上 包装箱的宽和高是一样的 但是厚度&#xff08;t 厘米&#xff09;和重量&#xff08;w 公斤&#xff09;是不一样的 表格中给出每种包装箱的厚度、重量和数量 每辆平板车有10.2米长的地方可以用来装包装箱 像面包片一样 载…

【PostgreSQL内核学习(七)—— 查询规划(生成路径)】

查询规划——预处理 生成路径 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们尊重他人的知识产权和学术成果&#xff0c;力求遵循合理使用原则&#xff0c;并在适用的情况下注明引用来源。 本文主要参考了《PostgresSQL数据库内核分析》一书 生…

进阶C语言——动态内存管理

好久不见&#xff0c;今天我们学习一下C语言的动态内存管理&#xff0c;这是一个和指针一样重要的章节&#xff0c;所以大家一定要好好学这章。 1. 为什么存在动态内存分配 我们已经掌握的内存开辟方式有&#xff1a; int val 20;//在栈空间上开辟四个字节 char arr[10] {0};…

Obsidian同步到Notion

插件介绍 将Obsidian的内容同步到Notion需要使用一个第三方插件"Obsidian shared to Notion"EasyChris/obsidian-to-notion: Share obsidian markdown file to notion and generate notion share link 同步obsdian文件到notion&#xff0c;并生成notion分享链接&am…

数据仓库表设计理论

数据仓库表设计理论 数仓顾名思义是数据仓库&#xff0c;其数据来源大多来自于业务数据(例如:关系型数据库)&#xff0c;当设计数仓中表类型时(拉链表、增量表、全量表、流水表、切片表)时&#xff0c;应先观察业务数据的特点再设计数仓表结构 首先业务数据是会不断增长的-即…

flask介绍、快速使用、配置文件、路由系统

前言: Flask框架和Django框架的区别&#xff1a; Django框架&#xff1a; 大而全&#xff0c;内置的app的很多&#xff0c;第三方app也很多Flask框架&#xff1a; 小而精&#xff0c;没有过多的内置app&#xff0c;只能完成web框架的基本功能&#xff0c;很多功能都需要借助第三…

护城河理论

护城河理论 护城河理论|来自股神巴菲特&#xff0c;是指投资的企业在某一方面的核心竞争力。 模型介绍 在2000年的伯克希尔哈撒韦的年会上&#xff0c;巴菲特说&#xff1a;让我们来把护城河作为一个伟大企业的首要标准&#xff0c;保持它的宽度&#xff0c;保持它不被跨越。我…