尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Linux内核thermal框架解析:从热区注册到冷却策略实现
1. thermal framework先搞清楚它在整个功耗体系中的位置做Linux内核功耗和热管理的人几乎都绕不开thermal framework。不管你是做手机、平板、机顶盒还是搞服务器、车载系统只要是带芯片的设备就会有发热问题。芯片一旦热了CPU会降频、GPU会掉帧、风扇会狂转用户体验直线下滑极端情况下直接关机保护。而这一切动作的调度中枢就是kernel里的这套thermal框架。我最早接触thermal framework是在做某款双摄手机的散热调试时。当时机器跑游戏半小时后背面烫到没法握硬件同事说“这是软件调度的问题”软件同事说“这是硬件散热能力不行”最后两边一起盯上了内核的thermal代码。那时候我对这套框架的印象还停留在“一个温度节点 一个风扇节点”的阶段等真正深入去看drivers/thermal/目录下的代码才发现它远比想象的复杂——它不是简单的一两个驱动而是一整套包含热区注册、温度上报、冷却策略、设备绑定、sysfs反馈的通用子系统。这套机制真正解决的问题通俗点说就是芯片热了到底应该由谁来决定怎么降温是降频、关核、还是拉风扇。如果没有统一框架每个SoC厂商都自己写一套逻辑每换一个平台就要重写一遍行业根本没法玩。thermal framework就是把这些共同逻辑抽出来做成可配置、可扩展的标准框架让温度传感器、冷却设备、控制策略三者能够解耦协作。我后面会按自己的理解把这套框架的完整脉络梳理一遍。这篇文章适合三类人看一是刚接触内核功耗子系统的初学者想搞懂thermal框架有哪些核心对象二是已经在调试温度问题的工程师想理清温度上报到冷却动作的完整链路三是准备做平台适配的驱动开发需要知道DTS该怎么配、驱动该怎么注册。2. 通用架构的核心对象与数据结构我在看内核源码的时候有个习惯先抓核心数据结构再顺着数据的流向读代码。因为内核的抽象层级很多如果一上来就扎进函数调用栈很容易迷失在thermal_zone_device_update这种层层嵌套的调用里。thermal framework也一样核心对象其实就那么几个把它们的关系捋清楚整个框架的骨架就出来了。2.1 thermal_zone_device整个框架的枢纽struct thermal_zone_device是thermal框架里最核心的对象可以理解为“一个温度监控区域”。每个热区对应一个或多个温度传感器代表芯片上某一片需要监控温度的区域。比如一颗SoC可能划分出cpu-thermal、gpu-thermal、ddr-thermal好几个热区因为它们发热特性不同、冷却手段不同、温控阈值也不同。我们看这个结构体重点不是把所有字段背下来而是抓住几个关键部分struct thermal_zone_device { char type[THERMAL_NAME_LENGTH]; // 热区名字比如cpu-thermal struct device *device; // 对应sysfs设备节点 struct thermal_zone_device_ops *ops; // 操作回调集 struct thermal_governor *governor; // 当前绑定的温控策略 struct thermal_trip *trips; // 阈值点数组 struct thermal_instance *thermal_instances; // 冷却绑定实例链表 long temperature; // 当前温度 int passive_delay; // 被动冷却轮询间隔 int polling_delay; // 常规轮询间隔 int num_trips; // trip点数 ... };这里的ops非常关键它是一组由底层驱动实现的操作函数框架层通过它们来获取温度、设置阈值、开关轮询。最核心的是get_temp框架想知道当前多少度就是调用它。set_trips则是用来设置硬件温度中断阈值的如果SoC的传感器支持中断触发就不需要一直轮询。在比较新的内核版本里thermal_zone_device是通过thermal_zone_device_register()注册的但对于大部分SoC平台更常用的是devm_thermal_zone_of_sensor_register()——它会让框架去解析DTS里的thermal-zones节点自动完成热区注册和传感器绑定。这个我后面在驱动接入部分再展开。2.2 thermal_trip与thermal_instance阈值与绑定的最小单元接着看thermal_trip这就是我们常说的“trip点”。每个热区可以配若干个trip点代表不同级别的温度阈值。比如一个典型的配置是85度触发被动冷却降频、100度触发临界关机。每个trip点有三个关键属性temperature是绝对阈值hysteresis是回差用来防振荡type则是动作类型。trip的type有四种passive代表需要软件介入的被动降温通常是降频降核active代表需要主动激活冷却设备最典型的是开风扇hot表示温度已经危险可能要执行紧急动作critical是最高级别内核会直接走orderly_poweroff强制关机。实际调试中hysteresis最容易被忽略很多“温度在阈值附近反复跳变导致频率来回波动”的问题其实都是回差配得太小。再看thermal_instance。它描述的是“某个cooling device分担某个trip点降温任务的绑定关系”。一个热区有多个trip点一个冷却设备可能同时要响应多个trip点而每个trip点也可能绑定多个冷却设备。这种多对多的关系就是靠thermal_instance这个中间结构体串起来的。它记录了三个关键值target是当前目标冷却档位、lower和upper是允许调节的档位范围。2.3 thermal_cooling_device真正干活的“执行器”thermal_cooling_device是执行降温动作的对象。它抽象出“冷却设备”这个通用概念——可以是CPU调频、GPU降频、风扇转速、甚至big.LITTLE架构下的关核操作。每个冷却设备通过get_max_state暴露出自己有多少个冷却档位通过set_cur_state被请求调整到某个档位通过get_cur_state让外部随时查询当前档位。内核里最常见的冷却设备实现是cpufreq_cooling。它把一个CPU频率调节策略policy封装成一个冷却设备get_max_state返回最高频率到最低频率之间的档位数set_cur_state时内部换算成目标频率并下发到cpufreq核心。还有devfreq_cooling原理类似针对的是非CPU设备比如GPU、DDR控制器的动态调频。此外很多SoC会把风扇抽象成一个fan cooling device底层操作PWM占空比。这几个对象的关系我用一句话概括热区是“体温计”冷却设备是“退烧药”trip点是“吃药条件”governor是“开药方的医生”instance是“医嘱”。体温计测出温度异常医生对照吃药条件判断病情等级然后按医嘱从药柜里拿药操作instance对应的冷却设备。2.4 thermal_governor温控策略的大脑thermal_governor是决定“到底怎么降温”的策略模块。它是一个很典型的策略模式实现——内核定义了策略的接口规范具体的决策算法由不同的governor实现。当前主流内核中step_wise和power_allocator是关注度最高的两个另外还有user_space和fair_share。struct thermal_governor { struct list_head governor_list; char name[THERMAL_NAME_LENGTH]; int (*bind_to_tz)(struct thermal_zone_device *tz); int (*unbind_from_tz)(struct thermal_zone_device *tz); int (*throttle)(struct thermal_zone_device *tz, int trip_id); };governor不直接面向硬件它只在温度更新事件到来时被调起来读取当前温度、遍历热区的所有trip点、对比温度是否达到阈值然后计算出每个冷却设备该处于什么档位最后通过thermal_zone_bind_cooling_device时记录的instance关系落实下去。为什么把决策逻辑单独拆出来而不是写死在框架里因为不同场景的需求差异实在太大了。服务器场景可能希望温度一高就立刻拉满风扇手机场景则希望尽量少让用户听到风扇声、优先降频有的场景追求性能优先有的场景追求功耗优先。把这些策略做成可插拔的governor平台工程师就能通过DTS里的thermal-governor属性随时切换策略甚至编写自己的governor注册进内核完全不需要改动框架代码。3. 一次温度上报到冷却执行的完整链路光知道有哪些对象还不够关键要看它们如何协作。我调试时最喜欢干的一件事就是顺着一次温度变化从头到尾走一遍代码路径把每个环节的调用关系用笔记下来。这里我就用最典型的step_wise场景把整条链路拆开讲。3.1 温度从哪里来温度数据有几个来源路径。最简单粗暴的是轮询——框架每隔一段时间调用get_temp去读一次温度。轮询周期由polling_delay和passive_delay决定前者是正常情况下的采样间隔后者是已经进入被动冷却状态后的采样间隔通常会缩短因为温度变化变快了需要更频繁监控。第二种是基于中断的触发式上报。SoC的TMUThermal Management Unit热管理单元硬件模块可以设置阈值当温度超过阈值时触发一个中断。内核里对应的机制是thermal_zone_device_update配合thermal_zone_set_trips在中断处理中唤醒thermal线程去处理。这种方式的好处是不需要持续轮询功耗更低反应也更快。很多新平台的get_temp其实只是读一个寄存器真正的实时性靠的是硬件中断。但不管哪种路径最终都会汇聚到一个关键函数——thermal_zone_device_update()。这个函数做了三件事更新当前温度、调用绑定的governor做决策、根据情况调整轮询周期。void thermal_zone_device_update(struct thermal_zone_device *tz, enum thermal_notify_event event) { ... if (tz-ops-get_temp(tz, temp)) return; tz-temperature temp; if (monitor) { thermal_zone_set_trips(tz); ... } __thermal_zone_device_update(tz, event); }3.2 step_wise的逐级决策逻辑step_wise这个governor最符合直觉温度超过某个trip点就往上升一档冷却强度回落到安全线以下就降一档。它内部的核心逻辑围绕“当前温度处于哪个区间”以及“温度趋势”来调整每个instance的目标档位。具体拆解一下step_wise内部会遍历热区所有已绑定的instance对每个instance计算它的发展趋势。趋势分为上升、下降、稳定三种。然后根据“当前温度是否超过该instance关联的trip点”和“温度趋势”综合判断温度超过trip点且趋势向上冷却档位直接跳到upper也就是全速降温。温度超过trip点但趋势向下保持当前档位不动作让温度自己回落。温度未超过trip点但之前已经升过档每经过一个监控周期降一档直到回到lower。这套逻辑用代码表示就是step_wise里的get_target_state它先判断温度与trip点的关系再看趋势最后决定是增加、减少还是维持当前target。实际用起来你会发现它很“简单粗暴”但胜在可靠、可解释性强。调优时主要调整trip点的温度和hysteresis就能获得不错的温控效果。3.3 从target到实际执行的“最后一公里”governor算完每个instance的target之后真正的动作发生在thermal_cdev_update里。它遍历这个冷却设备关联的所有instance找出所有target中的最大值然后调用冷却设备自己的set_cur_state接口去执行。为什么取最大值而不是平均值因为只要有一个trip点要求全速降温就意味着当前至少有一个温度指标已经告急冷却设备必须按最紧急的需求来响应不存在“折中”的说法。这有点像是消防调度——一个房间着火整个楼层的喷淋系统都得工作不能因为只有一间房着火就只开一半的水。这一步做得好不好直接决定用户体验。比如手机上跑游戏GPU热区和CPU热区同时触发降温但GPU的冷却设备是降频GPU、CPU的冷却设备是降频CPU它们各自取最大值执行互不干扰。如果有个风扇要同时负责给GPU和CPU散热那么它取的是两个热区target里的最大值保证按最热的一方需求来转。4. 驱动接入与设备树配置实操前面铺了这么多架构层面的东西现在落到实操。接入thermal framework的工作通常分两块第一块是让温度传感器被框架识别第二块是把冷却设备挂到对应热区上。设备树在其中起着“胶水”的作用。4.1 DTS里的thermal-zones配置现在主流平台都是基于设备树或者后续的ACPI但嵌入式领域DTS最常见来配置thermal的。一个标准的热区节点长这样thermal_zones: thermal-zones { cpu_thermal: cpu-thermal { polling-delay-passive 250; polling-delay 1000; thermal-sensors tsens0 0; trips { cpu_alert0: cpu-alert0 { temperature 75000; hysteresis 2000; type passive; }; cpu_alert1: cpu-alert1 { temperature 85000; hysteresis 2000; type active; }; cpu_crit: cpu-crit { temperature 105000; hysteresis 2000; type critical; }; }; cooling-maps { map0 { trip cpu_alert0; cooling-device cpu_cooling_dev 0 4; }; map1 { trip cpu_alert1; cooling-device fan0 0 THERMAL_NO_LIMIT; }; }; }; };在这个例子里thermal-sensors指向温度传感器节点tsens0后面那个0是传感器通道号一个TMU设备可以管理多个通道分别对应不同热区。trips定义了我们前面说的阈值点cooling-maps则把trip点和冷却设备关联起来。cooling-device cpu_cooling_dev 0 4这里的三元组第一个是冷却设备节点引用后两个是lower和upper档位限制。0 4表示允许这个冷却设备在0到4档之间调节写THERMAL_NO_LIMIT即ULONG_MAX表示不限制上限。这个限制非常有用——比如某个trip点只是轻度发热只想让风扇转低档就可以把upper设小避免一下全速。4.2 传感器驱动怎么接入框架温度传感器驱动要做的事情就是向framework注册一个热区。以最常用的方式为例在驱动probe阶段调用static struct thermal_zone_device_ops ops { .get_temp qcom_tsens_get_temp, .set_trips qcom_tsens_set_trips, }; static int tmds_probe(struct platform_device *pdev) { ... tz devm_thermal_zone_of_sensor_register(dev, sensor_id, tsens, ops); ... }devm_thermal_zone_of_sensor_register内部会去解析同名热区设备树节点把传感器和热区绑定起来。这个接口的好处是资源自动管理设备unbind的时候自动注销热区。如果平台不使用DTS也可以手动构造thermal_zone_device_register的参数来注册不过现在比较少见。传感器驱动一旦注册成功框架每次需要温度就会回调get_temp。这里有个坑get_temp的返回值要特别小心。内核里多数温度接口用“毫摄氏度”为单位也就是读出来的值直接是75000就是75度但有些老驱动或者某些SoC的私有接口用的是“摄氏度”如果忘记换算会导致整体温度判断偏高一两个数量级直接触发critical关机。我遇到过一起诡异重启问题最后定位就是一个新接手的人把寄存器里读到的原始值当成了毫摄氏度但硬件其实已经帮你转换过单位了。4.3 冷却设备注册的两种常见方式冷却设备注册最常见的是CPU调频冷却。内核提供一个现成的辅助函数cdev cpufreq_cooling_register(cpufreq_policy);这个接口封装了创建thermal_cooling_device的全部过程。它内部会申请一个cpufreq_cooling_device结构体统计该policy下的频率档位数生成get_max_state、set_cur_state、get_cur_state等回调函数然后注册进thermal核心。注册完成后这个冷却设备就会出现在/sys/class/thermal/cooling_device*下。对于非CPU设备比如GPU、DDR、NPU这类带动态调频能力的设备用的是devfreq_cooling_register。如果是风扇这种简单设备很多平台选择写一个简单的热冷却驱动自己实现那三个回调——get_max_state返回PWM的档位数量set_cur_state把档位换算成PWM占空比写进寄存器。自己写风扇冷却驱动时有个经验值得分享一定要在set_cur_state里校验state是否超出get_max_state的范围。内核框架层虽然一般会做限制但DTS里如果配了奇怪的lower/upper还是有越界的可能未校验的话轻则风扇转速异常重则寄存器写入非法值。4.4 governor绑定与切换的细节热区用哪个governor有三种方式决定。最简单的是在DTS的thermal-zones子节点里写thermal-governor step_wise;。框架解析热区时会去注册表中查找同名governor并绑定。第二种方式是在运行时通过sysfs切换echo power_allocator /sys/class/thermal/thermal_zone0/policy第三种是代码里直接在注册热区后手动thermal_zone_device_enable之前bind一个自定义governor。这里注意governor切换是高频踩坑点。比如你DTS里写的thermal-governor名字拼错了框架不会报fatal错误而是静默地使用默认governor通常配置成step_wise。结果就是你明明改了策略配置但实际跑的还是老策略。排查的时候记得去/sys/class/thermal/thermal_zone0/policy看一眼实际生效的是谁。5. 问题排查与调试心得实录写完配置、驱动也能正常probe这只能算完成了一半。真正让人头疼的是后面的调试阶段。我把这几年跟thermal问题打交道的经验整理一下按症状分类讲。5.1 熟悉sysfs里的调试入口调试thermal问题/sys/class/thermal/目录是最好的起点。每个热区对应一个thermal_zoneN目录每个冷却设备对应一个cooling_deviceN目录。热区目录下几个文件是关键temp当前温度单位毫摄氏度。cat temp得到类似65000表示65度。policy当前使用的governor可写。trip_point_N_temp第N个trip点的触发温度。trip_point_N_type第N个trip点的类型。mode热区的启停状态enabled或disabled。如果被设置为disabled整个热区不工作。available_policies系统支持的所有governor列表。冷却设备目录下max_state是最大冷却档位cur_state是当前档位。手动调风扇的时候cur_state是可以直接写的这是验证冷却设备本身工作是否正常的最快方式。比如风扇不转先手动写echo 50 /sys/class/thermal/cooling_device0/cur_state如果手动能转说明驱动和硬件都正常问题在热区和策略配置上。如果手动也转不了那就是风扇驱动或者硬件控制链路本身的问题跟thermal框架无关。5.2 实战排查温度该高却不触发冷却我调试时遇到最多的场景是“温度明明超过trip点了但冷却设备没动作”。排查这类问题我会按下面的顺序快速定位第一步确认热区的实际温度。cat /sys/class/thermal/thermal_zone0/temp如果温度异常偏低甚至为0大概率是传感器驱动读数问题。查get_temp回调里读的寄存器地址和换算公式特别注意单位是毫摄氏度还是摄氏度。第二步确认trip点配置符合预期。cat /sys/class/thermal/thermal_zone0/trip_point_0_temp看实际生效的触发温度和你DTS里写的是否一致。如果不一致往往是因为DTS没被正确解析或者你用sysfs手动改过没还原。第三步确认热区绑定的governor是正确的。cat /sys/class/thermal/thermal_zone0/policy。如果是user_space那内核不会做任何自动冷却动作策略完全交给用户态守护进程用户态程序没跑的话温度再高也没人管。第四步确认冷却设备的max_state不为0并且绑定关系正确。cat /sys/class/thermal/cooling_device0/max_state如果返回0说明冷却设备根本没正确上报档位数。然后去查看热区目录下的cdevN_trip_point、cdevN_weight等文件确认绑定到了正确的cdev上。5.3 实测有效的三个避坑经验经验一hysteresis别设为0。我在调试一个大核局部热点问题时最初配置的trip点是temperature 85000、hysteresis 0结果温度在84度和86度之间小幅度波动时CPU频率跟着在最高档和降频档之间来回横跳实测功耗反而升高了因为频率反复切换的瞬时功耗比稳定降频还要大。后来把hysteresis调到2000毫摄氏度这个问题立刻消失。回差的物理意义就是给阈值加一个“缓冲带”防止噪声温度引起的动作抖动。经验二polling-delay-passive和polling-delay要区分设置。不少平台直接把两个值写成一样的这意味着进入被动冷却后采样频率并没有提高温度变化响应会慢半拍。我的习惯是polling-delay 1000、polling-delay-passive 200热了以后加快采样。经验三debugfs和tracepoint是最后的杀手锏。如果sysfs看不出来问题就需要打开内核的thermal trace事件echo 1 /sys/kernel/debug/tracing/events/thermal/enable cat /sys/kernel/debug/tracing/trace事件thermal_temperature记录每次温度更新thermal_zone_trip记录trip点触发情况thermal_cdev_update记录冷却设备动作。这能看到完整的时间线和调用关系远比printf管用。我排查过一次“降温滞后”问题——从温度上报到cdev动作间隔太长就是用trace确认了其实governor早就被调用但set_cur_state下发后硬件响应慢问题出在固件而不是内核。5.4 关于休眠唤醒后的热状态恢复有一个很容易被忽视的场景设备休眠时thermal framework默认会暂停轮询唤醒后如果温度已经超限需要靠thermal_zone_device_update主动触发一次处理。部分早期内核在恢复流程里没有做这件事导致设备唤醒后一段时间内风扇不转、频率不降直到下一次轮询周期到来才“反应过来”。如果你们平台遇到唤醒后短暂发热失控的问题不妨看看平台驱动里是否在resume阶段调用了thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED)。这个坑我已经见过不止一次了。6. 写在最后的个人体会做thermal调试这几年我最大的感受是框架本身并不难理解难的是建立起“温度-策略-动作”之间的完整联想。每次看到一个温度异常的bug不要急着改trip点数值先问自己——温度从传感器到框架链路通不通、governor有没有被正确触发、冷却设备的target是怎么算出来的、最终有没有落到硬件动作上这四层走一遍80%的问题都能准确定位。另外想对刚入门的朋友说一句内核的thermal框架代码不算多但它是极少数能把“底层硬件、内核框架、上层策略、用户体验”全部串起来的子系统。认真读一遍drivers/thermal/下的代码收获的不只是这一套框架本身更是对内核里“抽象与解耦”这两个词的直观理解。
RELATED

相关推荐

STM32CubeMX入门到实战:从安装配置到HAL库SPI驱动W25Q64完整教程

STM32CubeMX入门到实战:从安装配置到HAL库SPI驱动W25Q64完整教程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/31 6:22:43
STM32CubeMX:嵌入式开发的硬件抽象层操作系统

STM32CubeMX:嵌入式开发的硬件抽象层操作系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/31 6:22:43
RK3588仿生人头:端侧AI人脸追踪与舵机控制实战

RK3588仿生人头:端侧AI人脸追踪与舵机控制实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/31 6:22:43
MORE NEWS

更多资讯

📰

小型自选商场商品管理系统:库存流水闭环与对账设计

简介:《小型自选商场商品管理系统》是一套面向课程设计、数据库与小型零售管理实训的完整项目资料,围绕商品进货、销售、库存和供应商信息管理展开,适合学习进销存业务建模、库存动态刷新与收银台结算流程的读者参考。压缩包共189个文件&…

📰

Windows下vlc-qt源码编译与libVLC集成避坑指南

简介:面向需要在 Windows 环境下集成视频播放能力、又希望省去从源码编译环节的 Qt/C 开发者,这份内容围绕 vlc-qt 提供了一套可直接参考的编译成果与依赖集合。包内包含 VLC 3.0.0 的 Windows 64 位运行库、vlc-qt 1.1.1 源码,以及已经编译好…

📰

技术博文写作规范:如何定义一个可落地的AI项目标题

我无法根据您提供的输入内容生成符合要求的博文。原因如下:输入中项目标题仅包含机构隶属关系描述(“LAMDA隶属于 计算机软件新技术国家重点实验室 和 南京大学计算机科学与技术系…”),但未构成一个可操作、可复现、可延展的具体…

📰

SQL Server关系数据库核心概念与工程实践

简介:《SQL Server 学习笔记》是一份面向数据库初学者、在校学生及需快速梳理 SQL Server 知识框架的从业者的提纲式文档,围绕关系数据库管理系统的主要特点与核心知识点展开,帮助读者建立从概念模型到建库建表的整体认知。内容包括数据库对象…

📰

AUC与ROC曲线本质解析:从排序能力到业务权衡

1. 为什么AUC和ROC总让人一头雾水?从一个真实误判场景说起我第一次在某图像识别项目里看到模型报告里写着“AUC0.87”,旁边还附了一条弯弯曲曲的ROC曲线,当时心里直犯嘀咕:这数字比准确率高,是不是就代表模型特别好&am…

📰

飞书AI助手Clawedbot部署与配置全指南

1. 为什么选择Clawedbot作为飞书AI助手? 在当今快节奏的工作环境中,一个真正懂你的AI助手能带来质的效率提升。Clawedbot(原OpenClaw)之所以成为我的首选,主要基于以下几个核心优势: 跨平台无缝体验 &am…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬