在 2026 年的嵌入式项目选型里,Zephyr 已经不是需要反复论证的小众 RTOS,而是经常出现在需求评审和方案对比里的正式候选。尤其是当设备要同时承担蓝牙、传感器采集、低功耗和远程升级任务时,Zephyr 的构建方式、Kconfig 配置和设备树模型会让你在前期感到复杂,但在后期获得明显的复用收益。这篇 Higgsfield 原创系列特别篇不做产品概念盘点,而是以一块开发板的最小 GPIO 点灯工程为主线,把 Zephyr 环境搭建、west 构建、Kconfig 配参、与 FreeRTOS 的选型对比,以及从学习环境到生产环境的差异一次讲清楚。学完之后,你会得到一条可以直接照做的 Zephyr 项目启动路径:从空目录到运行日志,再到排查问题和做选型判断。
1. 为什么 Zephyr 不是又一个 RTOS,而是一套构建框架
很多开发者一开始按 FreeRTOS 的习惯去理解 Zephyr,结果会在工程结构上卡住。原因很简单:Zephyr 不只是提供内核调度和队列,它把构建系统、板级描述、驱动模型、协议栈和配置机制绑定在一起,形成一个完整开发框架。
1.1 从嵌入式需求变化看 Zephyr 的定位
传统 RTOS 的价值通常在任务调度、信号量、队列和内存管理这些内核能力上。Zephyr 同样具备这些能力,而且支持多线程、信号量、消息队列、条件变量、邮箱等机制。但它的核心差异在于,Zephyr 把“一个应用在多个目标板上如何复用”当成头等问题。
实际项目中经常遇到这类情况:同一种产品的 WiFi 版和 4G 版使用不同主控,或者同一家公司不同项目之间要共享传感器驱动。如果用裸机或传统 RTOS,BSP 移植和驱动适配相当耗时。Zephyr 通过设备树描述目标板硬件,通过 Kconfig 控制软件特性,通过 west 管理多仓库源码,最终让同一个应用目录在更换 board 参数后重新编译,就能跑在另一块开发板上。
你可以把 Zephyr 理解为“内核 + 驱动模型 + 板级描述 + 构建工具链”的组合。它更适合被当作一个可裁剪的嵌入式 Linux 式开发框架,而不是一个单纯的内核库。
1.2 核心组成:west、Kconfig、Devicetree 和子系统
理解 Zephyr 之前,先分清四个概念。
west 是 Zephyr 的元工具,负责 init、update、build、flash 等操作,还负责拉取多个仓库并保持版本同步。Kconfig 是编译期配置系统,决定哪些代码被编译、哪些特性被开启。Devicetree 是硬件描述机制,用 dts/dtsi 文件描述引脚、外设地址、中断号、时钟等板上信息。子系统则覆盖蓝牙、Wi-Fi、传感器、日志、Shell、设置存储、OTA 等领域。
如果把项目启动过程拆开,就是先用 west 创建工作区并更新源码,再在应用目录写 CMakeLists.txt、prj.conf 和 main.c,然后通过 west build 指定 board 完成编译。编译时,Zephyr 会把 prj.conf、board 的 defconfig 和多个 Kconfig 碎片合并成最终配置,同时把设备树源文件和 overlay 合并成完整硬件描述。
常见误区是只改 prj.conf 不重新编译,或者以为设备树和 Kconfig 是同一层概念。实际上 Kconfig 解决“要哪些功能”,设备树解决“操作哪个引脚、哪个外设”。两者的错位是 Zephyr 新手最常见的故障来源之一。
2. 环境准备:从零搭一套可复现的 Zephyr 开发环境
Zephyr 的学习环境对前置依赖有明确要求。虽然在 Windows 上可以通过 IDE 简化一部分步骤,但这里更推荐使用 Linux 或 WSL2,因为工具链、调试器权限和命令行的操作链路更稳定,也更容易复现。
2.1 工具链与依赖总览
搭建环境前,先确认系统里有以下组件:
| 组件 | 作用 | 典型检查命令 |
|---|---|---|
| Python 3 | west 工具和构建脚本依赖 | python3 --version |
| west | 仓库管理和构建入口 | west --version |
| CMake | 生成构建系统 | cmake --version |
| Ninja | 加速构建 | ninja --version |
| DTC | 编译设备树 | dtc --version |
| 目标平台工具链 | 编译目标代码 | arm-none-eabi-gcc --version |
Zephyr 官方维护 Zephyr SDK,里面包含交叉编译器、调试器、QEMU 和 OpenOCD 等工具。建议优先使用官方 SDK,而不是只依赖系统包管理器里的 arm-none-eabi 工具链,因为 SDK 的版本和 Zephyr 的依赖关系已经经过完整验证。
2.2 创建虚拟环境并用 west 拉取 Zephyr 源码
先创建独立的 Python 虚拟环境,避免把 west 安装到全局 Python 环境里。后续升级依赖时,只要重建虚拟环境即可。
python3 -m venv ~/.zephyr-venv source ~/.zephyr-venv/bin/activate pip install --upgrade pip pip install west再初始化 Zephyr 工作区。west 会把 Zephyr 源码、hal 仓库、第三方库统一放到一个目录下。
west init ~/zephyrproject cd ~/zephyrproject west update west zephyr-export pip install -r zephyr/scripts/requirements.txt这里的关键点在于west update不是简单的 git pull,而是按照 manifest 文件解析各仓库的 commit 版本,把整套工程锁定到一致的快照上。如果换了一台机器,只需要重建虚拟环境并重复上述命令,就能得到完全一致的环境。生产项目还可以把 manifest 里的版本和自定义补丁纳入公司内部仓库管理。
2.3 配置 Zephyr SDK 与目标板
解压官方 SDK 后,需要把路径导出给构建系统。常见做法是把环境变量写入~/.bashrc或当前会话:
export ZEPHYR_TOOLCHAIN_VARIANT=zephyr export ZEPHYR_SDK_INSTALL_DIR=/opt/zephyr-sdkZEPHYR_TOOLCHAIN_VARIANT=zephyr表示使用官方 SDK 作为编译器来源。如果不设置,Zephyr 可能会尝试查找系统里的其他工具链,导致不同机器构建结果不一致。
先用 hello_world 样例做冒烟测试是验证工具链最直接的方法:
cd ~/zephyrproject west build -b native_sim zephyr/samples/hello_world -d build/hellonative_sim是 Zephyr 提供的原生模拟目标,它不要求真实开发板,适合用来验证安装链路。生成的可执行文件可以直接在宿主机运行:
./build/hello/zephyr/zephyr.exe如果能看到 Hello World 输出,说明 west、CMake、Ninja、设备树编译和基础内核对象都正常。之后再换成你手头的真实开发板型号,例如west build -b nucleo_f401re zephyr/samples/hello_world,开始验证交叉编译链路。
2.4 开发环境与生产环境的工具链区别
学习环境里可以频繁用-p always做全量编译,开发环境里需要保留 ccache 缓存,生产环境则要在一套固定的 CI 镜像里固化工具链版本。不要把生产构建放在本地开发者机器上,否则不同人本地的 CMake 版本、Python 包版本和 SDK 路径都会引入不可控差异。
注意:不要只验证“能编译”,还要验证编译出来的
.config是否包含预期配置、设备树 overlay 是否生效,以及最终固件能否在目标板上响应引脚变化。
3. 用 west build 构建一个最小 Blinky 工程
环境就绪后,用一个最小 GPIO 点灯工程跑通完整链路。这个工程会覆盖应用目录结构、CMake 接入、prj.conf 配置、设备树 overlay 和 main.c 逻辑。
3.1 创建应用目录与 CMakeLists.txt
在~/zephyrproject之外或内部创建应用目录都可以。这里建议把应用放到独立目录,便于后续迁移到版本库。
mkdir -p my_blinky/src cd my_blinky应用根目录必须有CMakeLists.txt,它负责把当前应用注册到 Zephyr 构建系统中。
cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(my_blinky) target_sources(app PRIVATE src/main.c)find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})是接入 Zephyr 的核心。构建时 system 已经导出ZEPHYR_BASE,如果这个变量没设置,CMake 会直接报错。项目中不要硬编码 SDK 路径,应用源码应该与具体工具链路径解耦。
3.2 prj.conf 与设备树 overlay 的作用
prj.conf负责开启软件层面的功能。这个最小工程需要 GPIO 和日志:
CONFIG_GPIO=y CONFIG_LOG=y为了让main.c里的DT_ALIAS(led0)能找到真实引脚,需要使用设备树 overlay。如果使用的开发板自带led0alias,这一步可以省略;如果需要点自定义 LED,可以在应用根目录创建app.overlay,内容按实际板卡调整。
/ { aliases { led0 = &board_led0; }; leds { compatible = "gpio-leds"; board_led0: led_0 { gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>; label = "Board LED0"; }; }; };&gpio0 5表示使用 GPIO0 端口第 5 脚,GPIO 号必须对照板卡原理图或厂商手册修改。设备树描述的是一种硬件事实,不能靠 prj.conf 修改替代。
3.3 编写 main.c:GPIO 操作与内核延时
main.c 的逻辑不复杂:获取 led0 对应的设备,检查设备是否 ready,配置为输出,然后循环翻转电平。
#include <zephyr/kernel.h> #include <zephyr/device.h> #include <zephyr/drivers/gpio.h> #include <zephyr/logging/log.h> LOG_MODULE_REGISTER(main, LOG_LEVEL_INF); #define LED0_NODE DT_ALIAS(led0) int main(void) { const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); int ret; if (!device_is_ready(led.port)) { LOG_ERR("LED device not ready"); return -ENODEV; } ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); if (ret < 0) { LOG_ERR("gpio config failed: %d", ret); return ret; } while (1) { gpio_port_toggle_dt(&led); k_msleep(500); } return 0; }GPIO_DT_SPEC_GET(LED0_NODE, gpios)会从设备树节点里取出 port 指针和 pin 号,省去手动查找 GPIO 控制器名称的过程。gpio_pin_configure_dt配置输出模式,gpio_port_toggle_dt翻转电平,k_msleep(500)让当前线程让出 CPU 并延时 500 毫秒。
这样写的好处是代码里不出现具体板卡的 GPIO 控制器名字。换板子时只要 overlay 里led0指向正确节点,同一份 main.c 可以复用。
3.4 构建、烧录和验证预期输出
回到~/zephyrproject,执行构建:
west build -b <你的开发板型号> ~/my_blinky -d build/my_blinky west flash在native_sim上验证链路也可以:
west build -b native_sim ~/my_blinky -d build/my_blinky ./build/my_blinky/zephyr/zephyr.exe预期输出有两种:一是编译成功,生成固件文件;二是在日志里看到主循环运行。真实板卡上 LED 会以约 1Hz 频率闪烁。如果 LED 不亮,先检查device_is_ready是否返回失败,再检查 GPIO 号、激活电平和板卡供电。
4. Kconfig 才是 Zephyr 项目配置的核心
很多开发者第一次接触 Kconfig 时,以为只是在 prj.conf 里写几行CONFIG_XXX=y。实际上 Kconfig 是一套有依赖关系的编译期配置系统,理解它之后,很多“配置不生效”的问题都能自己定位。
4.1 Kconfig 在 Zephyr 里的层级与符号
Zephyr 配置来源分为多层:应用级 prj.conf、板级 defconfig、SoC 级 Kconfig、子系统 Kconfig。构建系统会把这些配置合并到build/zephyr/.config。最终编译只认.config,不直接认 prj.conf。
常见的 Kconfig 符号有:
| Kconfig 符号 | 含义 | 典型值 | 错误配置现象 |
|---|---|---|---|
| CONFIG_GPIO | 是否编译 GPIO 驱动框架 | y | GPIO API 找不到 |
| CONFIG_LOG | 是否启用日志子系统 | y | LOG_ERR 无输出 |
| CONFIG_MAIN_STACK_SIZE | main 线程栈大小 | 2048 或 4096 | 栈溢出导致硬件 fault |
| CONFIG_HEAP_MEM_POOL_SIZE | 内核 heap 池大小 | 0 或 4096 | 动态内存分配失败 |
修改 Kconfig 后不要只看 prj.conf,要检查.config是否真的包含了对应符号。一个典型坑是:
grep CONFIG_GPIO build/zephyr/.config如果期望的CONFIG_GPIO=y没有出现,说明该符号可能因为依赖关系没有被选入。
4.2 menuconfig 与 Workbench 的作用
Kconfig 的依赖关系很复杂,靠记事本改 prj.conf 容易漏依赖。Zephyr 提供了可视化配置入口:
west build -b <你的开发板型号> ~/my_blinky -d build/my_blinky -t menuconfigmenuconfig 里可以搜索符号、查看帮助文本、确认依赖是否满足。很多 IDE 插件和厂商的 Zephyr Workbench 也提供 Kconfig 图形编辑界面,但它们本质上仍然是操作 Kconfig 并回写配置,并不改变配置模型。使用 Workbench 时不建议只依赖图形界面生成的配置,还是要回到代码仓库里检查 prj.conf 和 overlay 文件的可追踪性,否则团队成员用命令行构建时会出现行为不一致。
4.3 Kconfig 与 Devicetree 的分工边界
Kconfig 和 Devicetree 经常被放在一起讨论,但职责完全不同。
Kconfig 决定“哪些功能编译进固件”,例如CONFIG_BT=y会把蓝牙协议栈编进来。Devicetree 决定“代码运行在什么硬件上”,例如蓝牙接在哪个 UART、中断号是多少、引脚在哪。
用一句话记忆:Kconfig 是软件开关,Devicetree 是硬件拓扑。如果要新增一个 I2C 传感器驱动,先确认CONFIG_I2C=y,再确认设备树里有该传感器的 node,并且 node 的 status 为 okay,最后驱动代码才能通过DEVICE_DT_GET拿到设备。漏掉任何一个层面,运行阶段都可能出现设备 not ready 或 probe 失败。
4.4 Kconfig 常见配置错误和排查方法
第一类是 prj.conf 里写了未定义的符号。Kconfig 会对未知符号给出警告,但不会阻止编译。这时应该到 menuconfig 搜索确认是否存在该符号,以及它是否依赖其他符号。
第二类是修改了 prj.conf 但构建没有重新生成配置。west 通常能识别文件变化,但如果是自定义 overlay 或复杂脚本,建议用 pristine 构建强制刷新:
west build -p always -b <board> ~/my_blinky -d build/my_blinky第三类是私有配置写在板级 defconfig 里,导致其他 board 编译时行为不一致。生产项目应该把应用级配置放到 prj.conf 或按环境拆分的 overlay 文件里,不要散落在各开发者的本地目录。
5. Zephyr vs FreeRTOS:2026 年项目选型怎么取舍
Zephyr 和 FreeRTOS 的对比讨论在 2026 年依然高频。两者不是简单的优劣势对比,而是定位不同。FreeRTOS 本质上是一个轻量内核,你可以把它嵌入自己的工程;Zephyr 则更像一套完整嵌入式开发框架,选择它等于选择一整套工作流。
5.1 内核能力对比
从内核能力看,FreeRTOS 的优点是小、直接、文档繁多,任务、队列、信号量、软件定时器足以覆盖大部分传统 MCU 场景。Zephyr 的内核同样覆盖这些能力,并且支持 SMP 多核、内存域、用户态和部分架构的 MPU 特性。
在资源受限的小内存单片机上,FreeRTOS 的裁剪速度比 Zephyr 快;在需要多核调度、复杂同步、动态内存和内存保护的场景中,Zephyr 原生支持更完整,不需要引入太多第三方补丁。
| 对比维度 | Zephyr | FreeRTOS |
|---|---|---|
| 项目定位 | 内核 + 驱动 + 构建生态 | 以内核为主 |
| 代码组织 | west 多仓库工作区 | 内核源码可嵌入自有工程 |
| 板级适配 | Devicetree + 官方 board 支持 | 依赖厂商 SDK |
| 配置方式 | Kconfig + prj.conf | FreeRTOSConfig.h |
| 协议栈 | 内置蓝牙、Wi-Fi、Thread 等 | 需要额外集成 |
| 学习成本 | 较高 | 较低 |
| 最小资源占用 | 相对较大 | 可以做到很小 |
5.2 生态与接入成本对比
FreeRTOS 的优势是生态极其分散但成熟,几乎所有 MCU 厂商的 SDK 都有 FreeRTOS 集成示例。你把 FreeRTOS 加入自己的 Makefile 或 CMake 工程往往很快,但后续的驱动适配、低功耗管理和协议栈集成,需要自己或厂商补齐。
Zephyr 的优势是官方维护了大量开发板和驱动,移植到新板卡时,BSP 工作量集中在设备树和 pinmux 描述上。缺点是一旦碰到官方未支持的新 SoC,你需要理解 Zephyr 的设备树和底层启动流程,排错难度远高于改一个 FreeRTOS 任务函数。
在 2026 年做选型时,不要只看论坛里的支持声音,而要看团队是否愿意接受 Kconfig、Devicetree 和 west 这套工作流。如果团队主要经验是裸机开发,Zephyr 前两周的学习成本会明显高于 FreeRTOS。
5.3 选型决策表:哪些场景选 Zephyr,哪些选 FreeRTOS
| 项目特征 | 推荐方向 | 原因 |
|---|---|---|
| 需要蓝牙、Wi-Fi、Thread 等多协议 | Zephyr | 内置协议栈,驱动模型统一 |
| 产品生命周期要做 OTA、安全启动 | Zephyr | MCUboot、分区表、安全子系统支持更完整 |
| 同一应用跨多家 MCU 复用 | Zephyr | board 抽象和设备树能减少适配成本 |
| 传感器种类多、驱动复用要求高 | Zephyr | 官方和社区驱动数量较多 |
| 内存极小、团队时间紧 | FreeRTOS | 内核轻量,上手更快 |
| 已有厂商 SDK 深度定制 | FreeRTOS | 很多厂商生态仍以 FreeRTOS 为中心 |
| 团队对设备树不熟悉 | FreeRTOS | 不需要理解复杂配置系统 |
| 只做简单任务调度 | FreeRTOS | 内核机制足够,避免过度设计 |
实际项目中不存在“哪个更强”,只存在“哪个更适合当前交付约束”。如果项目已经有成熟厂商 SDK,不要为了 Zephyr 而 Zephyr;如果需要统一多产品软件平台,Zephyr 的长期收益往往更高。
5.4 混合使用与迁移边界
有些项目会同时接触 FreeRTOS 和 Zephyr,例如旧产品继续跑 FreeRTOS,新产品切换到 Zephyr。这时不要在同一个二进制里强行混合两套内核,否则任务栈、中断优先级和资源初始化都会冲突。更稳妥的做法是,先把对外接口抽象成 driver API,再逐步迁移单个模块,让新模块跑在 Zephyr 侧,旧模块留在旧工程中,直到对应外设驱动全部移植完成。
6. 常见坑与排查路径:从构建失败到运行异常
Zephyr 的问题往往不在语法本身,而在配置链路。下面按现象、原因、检查方式、解决方案的顺序整理一套排查路径。
6.1 west 命令找不到或 CMake 找不到 Zephyr
现象:
west: command not found或:
CMake Error: Could not find Zephyr. ZEPHYR_BASE is not set.原因通常是虚拟环境未激活,或者west zephyr-export没有执行。检查顺序是:
which west echo $ZEPHYR_BASE如果which west没有输出,就重新激活虚拟环境。如果ZEPHYR_BASE为空,执行west zephyr-export并确认环境变量导出到当前 shell。
6.2 prj.conf 配置不生效
现象:代码里使用了CONFIG_XXX对应 API,编译时报 undefined symbol,或者运行行为不受 prj.conf 影响。
检查方式:
grep CONFIG_XXX build/zephyr/.config如果.config里没有目标符号,打开 menuconfig 搜索,查看该符号是否被依赖条件隐藏。例如CONFIG_I2C=y只在CONFIG_I2C=y基础上开放部分驱动,某些驱动还需要CONFIG_GPIO=y才能工作。
解决方案是在 menuconfig 里从上到下逐层打开依赖项,或阅读 Kconfig 的 depends on 和 select 关系。不要硬编码一个不存在的 Kconfig 符号。
6.3 设备树节点找不到或设备 not ready
现象:DT_ALIAS(led0)编译报错,或者运行日志输出LED device not ready。
前者通常是没有 overlay 文件,或者 alias 名字拼写错误。后者可能是设备树节点 status 为 disabled,也可能是 GPIO 控制器驱动没有编译。
检查方式:
grep led0 build/zephyr/zephyr.dts确认 overlay 是否合并成功。如果找不到 led0,就检查app.overlay是否被构建系统识别。只要节点出现且 status 为 okay,再回头检查CONFIG_GPIO=y是否位于最终.config里。
6.4 运行后行为异常,但没有任何日志
现象:程序看起来编译通过,也烧录进去了,但 LED 不闪、串口无输出、程序似乎卡死。
排查优先级:
- 检查日志后端是否使能。只有
CONFIG_LOG=y不一定有串口输出,还要确认日志 backend 和 UART 引脚配置。 - 检查程序是否进入了 fault。Zephyr 内核发生致命错误时,如果调试器没接,日志可能没有回传。增加
CONFIG_THREAD_ANALYZER=y或使用调试器查看 PC 指针。 - 检查是否有线程栈溢出。
CONFIG_MAIN_STACK_SIZE太小时,main 线程可能启动即崩,建议先用默认较大值跑通,再逐步裁剪。 - 检查 flash 是否成功。
west flash成功不代表当前固件确实运行,必要时用串口工具观察启动 log,或使用调试器读取复位向量。
7. 生产环境落地清单与扩展方向
从能编译到能上线,中间还有不少距离。Zephyr 在开发板上跑通只是第一步,生产环境还要考虑版本、签名、日志、监控和回滚。
7.1 从学习环境到生产环境的七个差异点
| 事项 | 学习环境 | 生产环境 |
|---|---|---|
| 工具链 | 系统安装 | 固定 SDK 版本并固化到 CI 镜像 |
| 源码版本 | 直接 west update 到 master | manifest 锁定 commit 和补丁 |
| 构建 | 本地目录 | CI 容器 + ccache 缓存 |
| 配置 | 手写 prj.conf | 按环境拆分 overlay 文件 |
| 日志 | printk/LOG | 分级日志 + 远程日志 + 崩溃转储 |
| 烧录 | west flash | 签名固件 + MCUboot 引导 |
| 安全 | 默认关闭 | 安全启动、密钥管理、固件版本校验 |
7.2 可复用上线前检查清单
每个新项目启动前,可以把下面清单作为评审模板:
- 开发机环境和 CI 镜像使用同一份依赖描述,west manifest 已锁定。
- 能使用
west build -p always从零构建一次。 - 最终
.config里确认目标功能符号均开启,不存在意外残留。 - 设备树 overlay 已按实际开发板核对,GPIO 号和引脚复用正确。
- 日志能够区分正常启动、外设错误和内核 fault。
- 固件签名和升级通道已经规划,bootloader 分区表与 app 分区匹配。
- 已知的裁剪项有性能或内存测试支撑,不靠猜测。
- 关键外设具备超时和重试逻辑,不因硬件异常导致线程永久阻塞。
7.3 扩展方向:OTA、分区表与安全启动
Zephyr 生态中值得深入的方向包括 MCUboot 引导、DFU over Bluetooth、分区表管理和安全启动。这些能力在普通 RTOS 里通常要做大量集成,而 Zephyr 通过分区表设备树描述和配套工具把流程标准化了。
学习时可以先做本地 MCUboot 引导,再尝试把固件打包成可升级镜像。真正进入产品阶段后,还要考虑密钥保存位置、防回滚策略和升级失败后的备份分区恢复。这里的复杂度和单芯片选型、外部存储、调试器支持都强相关,落地前要结合项目实际 BOM 来测试。
7.4 关于 Zephyr 最值得记住的判断
Zephyr 的真正门槛不是语法,而是工作方式。从 west build 到 Kconfig,再到 Devicetree,每一条链路都要求开发者在“功能配置”和“硬件描述”之间保持清晰边界。如果你愿意花两周时间接受这套模型,后续在多板卡复用、协议栈集成和产品化能力上的收益会非常明显;如果你只是想在最小资源上快速跑一个简单调度器,FreeRTOS 依然是更直接的选择。
对新手最有效的练习路径是:先跑通 hello_world,再改一个 GPIO,再给工程加一个传感器驱动,最后手动写一个 device tree overlay。把这条链路完整走一遍,自然就能理解 Zephyr 为什么值得被当作一套平台来对待。