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

Android thermal (4)_cooling device(上)

前言

前面介绍过thermal子系统中部分重要结构,本次来到cooling device。在 Linux thermal framework 中,cooling device(冷却设备)是一个 可控的散热/限功耗单元。

它不一定是物理的“风扇”,也可以是 降低 CPU 频率、降低 GPU 频率、关掉 NPU 模块 等。对 thermal framework 来说,它就是一个“温控手段”的抽象。换句话说:sensor 负责测温 → thermal zone 定义温度策略 → cooling device 执行降温动作。

1 cooling device介绍

1.1 cooling device 的作用

(1)连接 温度 和 动作
当 thermal zone 检测温度超过某个 trip point(阈值),它会调用 cooling device 的接口。比如:

  • 温度 > 80℃ → CPU 频率降低一个档位(cpufreq cooling device)
  • 温度 > 90℃ → GPU 降频(devfreq cooling device)
  • 温度 > 95℃ → 打开风扇(fan cooling device)
  • 温度 > 100℃ → 强制关机(system shutdown cooling device)

(2)提供 可编程降温手段

  • 通过 thermal_cooling_device_ops,内核提供通用接口(get/set state, state2power, power2state)。
  • 各个厂商/驱动只需要实现自己的 cooling device,比如 Rockchip TSADC 驱动里 CPU 降频、高通TSENS 里的 GPU 降频,就可以被统一纳入 thermal framework 管理

1.2 cooling device 的分类(常见)

Linux 里已经有一些通用的 cooling device 驱动:

  • CPUFreq cooling device:通过 cpufreq 降低 CPU 频率 / 电压。
  • Devfreq cooling device:控制 GPU / NPU / DSP 的运行频率。
  • Fan cooling device:控制风扇转速(0 档 / 1 档 / 2 档 …)。
  • Power actor cooling device:直接限制模块的功率消耗(Watt)。
  • System shutdown cooling device:温度过高时,触发系统关机或重启。

1.3 cooling device 的工作机制

整体流程可以这样理解:

  • 传感器采样
    thermal sensor(如 Rockchip TSADC / Qualcomm TSENS)测得温度。
  • thermal zone 判断
    thermal zone 定义了 trip points(阈值)。比如 trip0 = 70℃(轻度),trip1 = 90℃(严重)。
  • governor 策略决策
    governor(如 step_wise, power_allocator)根据温度,决定需要激活哪个 cooling device、到什么 level。
  • 调用 cooling device ops
    thermal core 调用 set_cur_state() 或 power2state() 来实际执行操作。
  • 执行降温动作
    风扇加速、CPU/GPU 降频、触发关机 …

1.4 结构关系

  • Thermal Sensor:负责实时采集温度(如 TSADC/TSENS)。
  • Thermal Zone:热管理核心,维护 trip point、governor 策略。
  • Cooling Device:执行降温动作(CPU 降频、风扇、关机等)
    在这里插入图片描述

thermal sensor ↔ thermal zone ↔ cooling device 在 Linux Thermal Framework 中的关系用时序/交互图:
在这里插入图片描述

2 结构体&dtsi

2.1 thermal_cooling_device

这个结构体表示 一个冷却设备 实例(device object)。

struct thermal_cooling_device {int id;                          // cooling device 的全局唯一 IDchar *type;                      // cooling device 类型(字符串),例如 "processor", "gpu", "fan"unsigned long max_state;         // 冷却设备的最大状态值 (0 ~ max_state)struct device device;            // 对应的内核 device 结构,挂到设备模型struct device_node *np;          // 设备树中的节点指针(如果有的话)void *devdata;                   // 私有数据,驱动注册时传入void *stats;                     // 统计数据(比如 cooling device 的使用情况)const struct thermal_cooling_device_ops *ops; // 操作函数表 (回调接口)bool updated;                    // cooling device 是否需要更新struct mutex lock;               // 保护 thermal_instances 链表struct list_head thermal_instances; // 挂载在此 cooling device 上的 thermal_instance 列表struct list_head node;           // cooling device 全局链表节点(thermal_cdev_list)ANDROID_KABI_RESERVE(1);         // Android 内核兼容性保留字段
};

2.2 thermal_cooling_device_ops

这是 冷却设备驱动 的一组回调函数(ops),用于告诉 thermal framework 如何操作该 cooling device。

struct thermal_cooling_device_ops {int (*get_max_state) (struct thermal_cooling_device *, unsigned long *);int (*get_cur_state) (struct thermal_cooling_device *, unsigned long *);int (*set_cur_state) (struct thermal_cooling_device *, unsigned long);int (*get_requested_power)(struct thermal_cooling_device *, u32 *);int (*state2power)(struct thermal_cooling_device *, unsigned long, u32 *);int (*power2state)(struct thermal_cooling_device *, u32, unsigned long *);
};

详细说明如下:

  • get_max_state(struct thermal_cooling_device *, unsigned long *)
    返回该冷却设备的 最大冷却状态(多少个等级)。比如风扇有 0–3 档,那最大 state = 3

  • get_cur_state(struct thermal_cooling_device *, unsigned long *)
    获取当前冷却设备的状态。比如风扇目前在 2 档,GPU 降频到一半。

  • set_cur_state(struct thermal_cooling_device *, unsigned long)
    设置冷却设备的状态。这是 thermal framework 调用 cooling device 实际动作的入口。比如当温度超过 trip point,就调用这个函数把风扇调到高档,或者降低 CPU 频率。

  • get_requested_power(…)
    可以获取当前冷却设备能够消耗/限制的功率。适用于 power actor 模式(动态功率限制)。

  • state2power(…)
    将冷却设备的 “state” 映射到实际功率限制。例如 CPU 降频到 state=2,功耗从 10W → 6W。

  • power2state(…)
    功率到状态的映射。Thermal governor 可以直接基于功率约束来决定需要的冷却 state。

【重要】简单总结
* xxx_state 系列是 状态 控制接口(风扇转速、CPU 降频级别)。
* xxx_power 系列是 功率 控制接口(高端芯片常见,支持智能功率管理)。

3 关键函数

继续thermal zone之后的调用,thermal zone在注册过程,调用 thermal_of_for_each_cooling_maps、thermal_zone_bind_cooling_device等函数,关联到绑定 thermal zone 与cooling device。

2.1 thermal_of_for_each_cooling_maps

thermal_of_for_each_cooling_maps 遍历一个 thermal_zone_device 的 所有 cooling-maps 节点,并对每个 cooling map 调用 action 函数(比如 bind/unbind)。

static int thermal_of_for_each_cooling_maps(struct thermal_zone_device *tz,struct thermal_cooling_device *cdev,int (*action)(struct device_node *, int, int,struct thermal_zone_device *, struct thermal_cooling_device *))
{struct device_node *tz_np, *cm_np, *child;int ret = 0;// 根据 thermal zone 的名字找到对应的 Device Tree 节点。tz_np = thermal_of_zone_get_by_name(tz);if (IS_ERR(tz_np)) {pr_err("Failed to get node tz by name\n");return PTR_ERR(tz_np);}// 找 thermal zone 节点下的 cooling-maps 子节点cm_np = of_get_child_by_name(tz_np, "cooling-maps");if (!cm_np)goto out;// 遍历 cooling-maps 下的每个子节点,for_each_child_of_node是一个循环遍历的宏定义for_each_child_of_node(cm_np, child) {// 对每一个 map 节点,调用 thermal_of_for_each_cooling_deviceret = thermal_of_for_each_cooling_device(tz_np, child, tz, cdev, action);if (ret) {of_node_put(child);break;}}// 最后释放引用计数:of_node_put(cm_np); of_node_put(tz_np);of_node_put(cm_np);
out:of_node_put(tz_np);return ret;
}

3.2 thermal_of_for_each_cooling_device

thermal_of_for_each_cooling_device 在**一个 map_np(某个 cooling-map 节点)**中,找到它引用的 trip 点和 cooling-device 列表,然后调用 action。
action 是一个指向目标函数的指针,通过action封装。在之前的调用关系,action等通过bind 或 unbind操作,具体需要看调用位置。

static int thermal_of_for_each_cooling_device(struct device_node *tz_np, struct device_node *map_np,struct thermal_zone_device *tz, struct thermal_cooling_device *cdev,int (*action)(struct device_node *, int, int,struct thermal_zone_device *, struct thermal_cooling_device *))
{struct device_node *tr_np;int count, i, trip_id;// 从 map_np 读取 trip 属性(即 thermal trip 节点的 phandle)。tr_np = of_parse_phandle(map_np, "trip", 0);if (!tr_np)return -ENODEV;// 根据 trip 节点,找到对应的 trip id。trip_id = of_find_trip_id(tz_np, tr_np);if (trip_id < 0)return trip_id;// 统计 cooling-device 属性里有多少个 cooling device 被引用count = of_count_phandle_with_args(map_np, "cooling-device", "#cooling-cells");// 如果 ≤0,说明 DT 有问题(至少要有一个 cooling device)。if (count <= 0) {pr_err("Add a cooling_device property with at least one device\n");return -ENOENT;}/** At this point, we don't want to bail out when there is an* error, we will try to bind/unbind as many as possible* cooling devices*/// 遍历每个 cooling devicefor (i = 0; i < count; i++)// 对 map_np 里的每一个 cooling device 调用 action(即 bind 或 unbind)。action(map_np, i, trip_id, tz, cdev);return 0;
}

例如,当 thermal_of_unbind(tz, cdev) 调用时:

  • 它会传入 __thermal_of_unbind 作为 action。
  • thermal_of_for_each_cooling_maps 遍历 tz 下所有 cooling-maps
  • thermal_of_for_each_cooling_device 遍历 cooling-maps 里的每个 cooling-device。
  • 对于每个 cooling-device,调用 __thermal_of_unbind(…),去检查它是否是当前要 unbind 的 cdev,如果是,则调用 thermal_zone_unbind_cooling_device。

函数 (thermal_of_for_each_cooling_maps + thermal_of_for_each_cooling_device) 就是 thermal 框架里的 迭代器模式,专门遍历 DT 里定义的 cooling-maps,并对每个 (trip, cooling-device) 绑定关系执行 action(bind/unbind)。也就是说:

  • thermal_of_for_each_cooling_maps = 遍历 所有 map 节点
  • thermal_of_for_each_cooling_device = 遍历 map 节点里的所有 cooling device
  • action = 具体操作(bind/unbind)

3.3 thermal_of_zone_get_by_name

thermal_of_zone_get_by_name 的作用就是根据 thermal_zone_device 的名字(tz->type)找到对应的 device tree 节点。

static struct device_node *thermal_of_zone_get_by_name(struct thermal_zone_device *tz)
{struct device_node *np, *tz_np;// 1. 找到 dts 里的 "thermal-zones" 根节点np = of_find_node_by_name(NULL, "thermal-zones");if (!np)return ERR_PTR(-ENODEV);// 2. 在 thermal-zones 下面找到名字为 tz->type 的子节点tz_np = of_get_child_by_name(np, tz->type);// 3. 释放对 np 的引用of_node_put(np);// 4. 没找到子节点,就返回错误if (!tz_np)return ERR_PTR(-ENODEV);return tz_np;
}

逻辑解读

(1)找到根节点 “thermal-zones”

  • DTS 中通常会有一个类似这样的定义:thermal-zones {…}
  • of_find_node_by_name(NULL, “thermal-zones”) 就是找到这个根节点。

(2)找到具体的 thermal zone

  • 每个 thermal_zone_device 都有一个 type 字符串,例如 “cpu-thermal”、“gpu-thermal”。
  • of_get_child_by_name(np, tz->type) 就是去找对应的 zone 节点。

(3)引用计数管理

  • of_node_put(np) 用来释放 thermal-zones 根节点的引用(因为后续我们只需要具体的子节点)。

(4)错误处理

  • 如果找不到对应的 zone,就返回 ERR_PTR(-ENODEV)。

(5)返回正确的节点

  • 返回的 tz_np 后续会被用来解析 cooling-maps、trips 等属性。

3.4 thermal_zone_bind_cooling_device

thermal_zone_bind_cooling_device() 用来把 一个 cooling device(降温手段) 绑定到 一个 thermal zone(热区) 的 某个 trip point(温度阈值) 上。绑定完成后,governor 在该 trip 被触发时就能对这个 cooling device 下达调节指令(例如升降风扇档位、CPU/GPU 降频)。

参数解释:
thermal_zone_device *tz:要绑定的 thermal zone
int trip_index:thermal zone 的 trip point 索引(比如第 0 个高温报警点,第 1 个临界点等)
thermal_cooling_device *cdev:要绑定的 cooling device
long upper:冷却设备能用的最高 state(即最多开多大风扇,或最多降多少频)
long lower:冷却设备能用的最低 state(即最小冷却级别)
int weight:当一个 trip 绑定了多个 cooling device 时,用来衡量不同设备的重要性或分配比例

int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,int trip_index,struct thermal_cooling_device *cdev,unsigned long upper, unsigned long lower,unsigned int weight)
{struct thermal_instance *dev;          // 绑定关系的实例对象:把 tz + cdev + trip 这三者串起来struct thermal_instance *pos;          // 遍历时的临时指针(instance)struct thermal_zone_device *pos1;      // 用来在全局 tz 链表中查找 tzstruct thermal_cooling_device *pos2;   // 用来在全局 cdev 链表中查找 cdevconst struct thermal_trip *trip;       // 指向 tz 中的第 trip_index 个 trip pointbool upper_no_limit;                   // 标记 upper 是否是“无上限”int result;                            // 通用错误码/返回值if (trip_index >= tz->num_trips || trip_index < 0)  // 边界检查:trip 下标必须合法return -EINVAL;trip = &tz->trips[trip_index];        // 取出对应的 trip 描述(只读的 trip 定义)// 在全局 thermal_tz_list 里确认 tz 确实已注册(防御性检查)list_for_each_entry(pos1, &thermal_tz_list, node) {if (pos1 == tz)break;}// 在全局 thermal_cdev_list 里确认 cdev 确实已注册(防御性检查)list_for_each_entry(pos2, &thermal_cdev_list, node) {if (pos2 == cdev)break;}if (tz != pos1 || cdev != pos2)       // 任意一方没在全局列表中,视为非法return -EINVAL;/* lower 默认 0,upper 默认 max_state */lower = lower == THERMAL_NO_LIMIT ? 0 : lower;   // 如果下限是 NO_LIMIT,等价于 0 档if (upper == THERMAL_NO_LIMIT) {                  // 上限 NO_LIMIT:等价于 cdev->max_stateupper = cdev->max_state;upper_no_limit = true;                       // 记下“上限原本是 NO_LIMIT”的语义} else {upper_no_limit = false;}if (lower > upper || upper > cdev->max_state)     // 区间必须合法:0 <= lower <= upper <= max_statereturn -EINVAL;dev = kzalloc(sizeof(*dev), GFP_KERNEL);          // 分配一个 thermal_instance(绑定对象)if (!dev)return -ENOMEM;dev->tz = tz;                                     // 记录所属 thermal zonedev->cdev = cdev;                                 // 记录关联的 cooling devicedev->trip = trip;                                 // 记录绑定到的 tripdev->upper = upper;                               // 绑定时为该 trip 指定的 cdev 上限 statedev->upper_no_limit = upper_no_limit;             // 是否来自 NO_LIMIT 的标记(便于后续逻辑)dev->lower = lower;                               // 绑定时为该 trip 指定的 cdev 下限 statedev->target = THERMAL_NO_TARGET;                  // 初始没有目标 state(由 governor 决定)dev->weight = weight;                             // 绑定时设置的权重(governor 会用)result = ida_alloc(&tz->ida, GFP_KERNEL);         // 为该 tz 分配一个“本 tz 内唯一”的 instance idif (result < 0)goto free_mem;dev->id = result;                                 // 保存 idsprintf(dev->name, "cdev%d", dev->id);            // 生成在 sysfs 下的符号链接名:cdevNresult =sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);// 在 /sys/class/thermal/thermal_zoneX/ 下创建到 cooling_deviceY 的符号链接// 便于用户从 tz 侧看到绑定了哪些 cdevif (result)goto release_ida;// 在 tz 设备下创建只读属性:cdevN_trip_point(展示该绑定对应的 trip index)snprintf(dev->attr_name, sizeof(dev->attr_name), "cdev%d_trip_point",dev->id);sysfs_attr_init(&dev->attr.attr);dev->attr.attr.name = dev->attr_name;dev->attr.attr.mode = 0444;                       // 只读(所有人可读)dev->attr.show = trip_point_show;                 // 读取函数:打印 trip 信息result = device_create_file(&tz->device, &dev->attr);if (result)goto remove_symbol_link;// 在 tz 设备下创建属性:cdevN_weight(读写;用户可调整该绑定的权重)snprintf(dev->weight_attr_name, sizeof(dev->weight_attr_name),"cdev%d_weight", dev->id);sysfs_attr_init(&dev->weight_attr.attr);dev->weight_attr.attr.name = dev->weight_attr_name;dev->weight_attr.attr.mode = S_IWUSR | S_IRUGO;   // 所有用户可读,owner 可写dev->weight_attr.show = weight_show;              // 展示当前权重dev->weight_attr.store = weight_store;            // 写入新权重result = device_create_file(&tz->device, &dev->weight_attr);if (result)goto remove_trip_file;// 把该绑定实例挂入双方的实例链表(双向关联),注意加锁与去重mutex_lock(&tz->lock);mutex_lock(&cdev->lock);list_for_each_entry(pos, &tz->thermal_instances, tz_node)if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {// 该 tz-trip-cdev 组合已经存在,防止重复绑定result = -EEXIST;break;}if (!result) {// 尾插入 tz 的实例链表和 cdev 的实例链表(各维护一份)list_add_tail(&dev->tz_node, &tz->thermal_instances);list_add_tail(&dev->cdev_node, &cdev->thermal_instances);atomic_set(&tz->need_update, 1); // 标记 tz 需要更新(触发后续 governor 评估)}mutex_unlock(&cdev->lock);mutex_unlock(&tz->lock);if (!result)                                   // 成功到这里则返回 0return 0;// 下面是失败时的清理回滚路径(按创建的逆序回滚)device_remove_file(&tz->device, &dev->weight_attr); // 移除权重属性文件
remove_trip_file:device_remove_file(&tz->device, &dev->attr);        // 移除 trip_point 属性文件
remove_symbol_link:sysfs_remove_link(&tz->device.kobj, dev->name);     // 移除 sysfs 符号链接
release_ida:ida_free(&tz->ida, dev->id);                        // 释放本 tz 内分配的 instance id
free_mem:kfree(dev);                                         // 释放 instance 内存return result;                                      // 返回错误码
}
EXPORT_SYMBOL_GPL(thermal_zone_bind_cooling_device);    // 导出符号,供其它模块使用

关键点:
(1)thermal_instance 的角色
它是 “关系对象”:把 tz(热区)、trip(阈值)和 cdev(冷却手段)三者绑定起来,并在该绑定上携带 上下限 state、权重、目标 state 等运行时信息。这样一个 tz 可以绑定多个不同的 cdev;同一个 cdev 也可以被多个 tz 复用。

(2)上下限处理(THERMAL_NO_LIMIT)
upper = THERMAL_NO_LIMIT → 解释为 upper = cdev->max_state,并记录 upper_no_limit = true(以保留“原本没有上限”的语义)。
lower = THERMAL_NO_LIMIT → 解释为 lower = 0。
区间校验:0 <= lower <= upper <= max_state。

(3)sysfs 可视化
在 thermal_zoneX 下创建到 cooling_deviceY 的 符号链接:cdevN,便于用户空间查看绑定关系。
暴露两个属性:

  • cdevN_trip_point(只读):告诉你这个绑定挂在哪个 trip 上。
  • cdevN_weight(读写):可在线调整 governor 使用该 cdev 的权重(影响调度决策)。

结构图:
在这里插入图片描述

3.5 thermal_zone_unbind_cooling_device

thermal_zone_unbind_cooling_device 说明:
① 作用:解除 cooling device 与 thermal zone 某个 trip point 的绑定关系。
② 调用场景:通常在 thermal zone 的 .unbind 回调中调用。
③ 返回值:0 成功,负数失败(如设备不存在)。

/*** thermal_zone_unbind_cooling_device() - unbind a cooling device from a*					  thermal zone.* @tz:		pointer to a struct thermal_zone_device.* @trip_index:	indicates which trip point the cooling devices is*		associated with in this thermal zone.* @cdev:	pointer to a struct thermal_cooling_device.** This interface function unbind a thermal cooling device from the certain* trip point of a thermal zone device.* This function is usually called in the thermal zone device .unbind callback.** Return: 0 on success, the proper error value otherwise.*/
int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,int trip_index,struct thermal_cooling_device *cdev)
{struct thermal_instance *pos, *next; // pos/next 用于遍历 thermal_instance 链表const struct thermal_trip *trip; // trip 指向对应的 trip point// 加锁,保护 thermal zone 和 cooling device 的内部状态。// 保证解绑过程中不会有并发修改。mutex_lock(&tz->lock);mutex_lock(&cdev->lock);// 获取 tz 中对应索引的 trip point(即解绑的目标 trip)。trip = &tz->trips[trip_index];// 遍历 tz->thermal_instances 链表(保存所有绑定的 thermal_instance)。// list_for_each_entry_safe是个宏定义,用来遍历list_for_each_entry_safe(pos, next, &tz->thermal_instances, tz_node) {// 如果 thermal_instance 同时满足:// (1) 属于当前 tz// (2) trip 相同// (3) cooling device 相同if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {list_del(&pos->tz_node); // list_del(&pos->tz_node):从 thermal zone 的链表里删掉。list_del(&pos->cdev_node); // list_del(&pos->cdev_node):从 cooling device 的链表里删掉。// 解锁,然后跳转到 unbind 执行资源清理。mutex_unlock(&cdev->lock);mutex_unlock(&tz->lock);goto unbind;}}// 如果遍历结束没找到匹配的绑定对象,则返回 -ENODEV(设备不存在)。mutex_unlock(&cdev->lock);mutex_unlock(&tz->lock);return -ENODEV;unbind:// 移除 sysfs 接口(weight 属性、trip 属性)device_remove_file(&tz->device, &pos->weight_attr); device_remove_file(&tz->device, &pos->attr);// 移除 sysfs 符号链接(之前 bind 时创建的 cdevX 软链接)sysfs_remove_link(&tz->device.kobj, pos->name);// 释放 thermal instance 的 IDida_free(&tz->ida, pos->id);// 释放 thermal_instance 内存kfree(pos);return 0;
}
EXPORT_SYMBOL_GPL(thermal_zone_unbind_cooling_device);

【对比】
bind 时会:

  • 分配 thermal_instance
  • 链接到 tz 和 cdev 的链表
  • 在 sysfs 里创建属性和符号链接

unbind 时会:

  • 从链表中删除 thermal_instance
  • 删除 sysfs 节点和链接
  • 回收 ID 和内存

4 设备截图

基于rk Android设备截图:
在这里插入图片描述

http://www.dtcms.com/a/486375.html

相关文章:

  • JavaEE初阶——TCP/IP协议栈:从原理到实战
  • 建设网站要买服务器徐闻网站建设公司
  • 我想网上做网站怎么做卖东西的网站
  • 用于汽车雷达应用的步进频率PMCW波形——论文阅读
  • 使用 python-docx 和 difflib 对比 Word 文档
  • 食品电子商务网站建设规划书开发网站的好处
  • 找人做的网站怎么运行精神堡垒设计
  • 开源 Linux 服务器与中间件(一)基本介绍
  • 开源 Linux 服务器与中间件(二)嵌入式Linux服务器和中间件
  • 公司建设一个网站有什么好处国外网站建设现状图分析
  • 绿色 网站 源码个人建网站怎么赚钱
  • 定时器的学习(二)
  • SpringBoot模特兼职网站zu3n3(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • windows开发中使用flutter开发鸿蒙
  • calibre LVS 跑不起来 就将setup 的LVS Option connect下的 connect all nets by name 打开。
  • 向RAGFlow中上传文档到对应的知识库
  • 网站后台发邮件建设网站都需要哪些内容
  • 惠州网站建设 英语外贸论坛有哪些?
  • 【学习笔记10】C++模板编程深度学习(下):可变参数模板与完美转发核心技术
  • 华为盘古 Ultra-MoE-718B-V1.1 正式开放下载!
  • 【OpenHarmony】AI引擎模块架构
  • 为什么选php语言做网站江苏网站建设网络推广
  • 数据结构算法学习:LeetCode热题100-链表篇(上)(相交链表、反转链表、回文链表、环形链表、环形链表 II)
  • STC亮相欧洲区块链大会,碳资产RWA全球化战略迈出关键一步
  • 使用Electron创建helloworld程序
  • 建设校园网站国外研究现状2020网络公司排名
  • DataEase v2 连接 MongoDB 数据源操作说明-MongoDB BI Connector用户创建
  • PHP 8.0+ 编译器级优化与语言运行时演进
  • 网站运营培训网站被百度收录吗
  • 升级到webpack5