Linux 2.6.29.4内核移植mini2440:从源码修改到YAFFS2根文件系统启动

Linux 2.6.29.4内核移植mini2440:从源码修改到YAFFS2根文件系统启动 1. 项目概述与背景折腾嵌入式开发板的乐趣有一大半都来自于把内核、文件系统这些“灵魂”装进那块小小的板子里。最近我手头有一块经典的友善之臂 mini2440 开发板心血来潮想给它移植一个相对较新的 Linux 2.6.29.4 内核并搭配 YAFFS2 文件系统。这个版本的内核在 2.6 系列里算是一个比较稳定和功能完善的节点对 S3C2440 这类 ARM9 芯片的支持也相对成熟。整个过程下来从环境搭建、内核配置、源码修改到最终烧录启动踩了不少坑也积累了一些心得。这篇文章我就把这次移植 Linux 2.6.29.4 内核到 mini2440 的全过程包括关键的修改点、背后的原理以及那些容易让人栽跟头的细节完整地梳理一遍。无论你是刚接触嵌入式 Linux 的新手还是想重温一下经典平台的老手希望这篇详尽的记录都能给你提供一份可靠的“路书”。我的实验环境是 VMware 虚拟机下的 Fedora 9 系统交叉编译工具链用的是 arm-linux-gcc 4.3.2。选择这个老版本的编译器和系统主要是为了最大限度地匹配那个时代的内核源码和库依赖减少因工具链版本过高带来的兼容性问题。目标板是友善之臂的 mini2440核心是三星的 S3C2440A 微处理器板载 64MB NAND Flash 和 64MB SDRAM。Bootloader 使用的是 Supervivi这是一个在 2440 开发板上非常常见的引导程序。整个移植的核心思路就是让官方的、通用的 Linux 内核源码能够正确识别我们这块特定开发板的硬件如时钟、NAND 分区并最终挂载上我们准备好的 YAFFS2 根文件系统完成启动。2. 开发环境搭建与源码准备2.1 工具链的选择与验证工欲善其事必先利其器。对于嵌入式开发交叉编译工具链就是最核心的“器”。我选择的是 arm-linux-gcc 4.3.2。这个版本在 2008-2009 年间非常流行与 Linux 2.6.29 内核的编译匹配度很高。更高版本的 gcc比如 4.4 以上在编译一些老内核时可能会因为语法检查更严格或默认选项不同而报错增加不必要的调试成本。下载并安装好工具链后第一件事就是验证其是否可用以及确认它的路径。打开终端输入arm-linux-gcc -v你应该能看到类似gcc version 4.3.2的输出并且没有找不到命令的错误。记下你的工具链安装路径比如我的是/usr/local/arm/4.3.2/bin。为了方便通常我们会把这个路径加入到系统的PATH环境变量中这样在任何目录下都能直接使用arm-linux-gcc命令。你可以编辑~/.bashrc文件在末尾加上一行export PATH$PATH:/usr/local/arm/4.3.2/bin然后执行source ~/.bashrc使其生效。注意有些现成的工具链包解压后其bin目录下的可执行文件可能缺少执行权限。你需要使用chmod x /usr/local/arm/4.3.2/bin/*命令为其添加权限否则在编译时会遇到Permission denied的错误。2.2 内核源码获取与解压接下来是获取 Linux 2.6.29.4 的内核源码。你可以从内核官网的镜像站下载例如https://mirrors.edge.kernel.org/pub/linux/kernel/v2.6/linux-2.6.29.4.tar.gz。使用wget命令下载到你的工作目录然后用tar xzvf linux-2.6.29.4.tar.gz解压。解压后会生成一个linux-2.6.29.4的目录这就是我们所有工作的起点。进入内核源码目录cd linux-2.6.29.4。在开始任何修改之前我强烈建议你先执行一次make distclean。这个命令会清除所有之前编译生成的中间文件、配置文件将源码恢复到最干净的状态避免旧编译结果对新配置造成干扰。2.3 YAFFS2 文件系统源码准备我们计划使用 YAFFS2 作为根文件系统。YAFFS (Yet Another Flash File System) 是专门为 NAND Flash 设计的文件系统在嵌入式领域应用非常广泛。你需要去 YAFFS 的官网或开源仓库下载对应版本的源码。对于 2.6.29 内核通常使用当时较新的 yaffs2 版本即可。下载后你会得到一个类似yaffs2.tar.gz的包。解压后里面应该包含yaffs2目录存放核心源码、README、patch脚本等文件。我们的目标是将 YAFFS2 的代码集成到内核的fs/目录下。但先别急着复制因为内核配置菜单Kconfig和编译脚本Makefile也需要相应修改才能让内核认识并编译这个新的文件系统。这一步我们留到后面内核配置时一起做这里只需知道源码包已经准备好。3. 内核顶层配置与交叉编译环境设置3.1 修改顶层 Makefile这是让内核知道“为谁编译”和“用谁编译”的关键一步。内核顶层的Makefile文件里有两个变量控制着编译的目标架构和使用的交叉编译器。用文本编辑器打开内核根目录下的Makefile文件找到大约第 193 行不同版本行号可能有细微差异但变量名是固定的你会看到类似这样的两行ARCH ? $(SUBARCH) CROSS_COMPILE ?ARCH变量指定目标 CPU 的架构对于我们的 S3C2440ARM920T核心需要将其改为arm。CROSS_COMPILE变量指定交叉编译工具的前缀。我们的工具链是arm-linux-gcc那么前缀就是arm-linux-。注意末尾的短横线-不能省略因为编译时内核会在这个前缀后面加上gcc、ld、objcopy等命令组合成完整的工具链命令。因此将这两行修改为ARCH ? arm CROSS_COMPILE ? arm-linux-修改后保存。这个操作的意义在于后续所有make命令如make menuconfig,make zImage都会自动使用我们指定的架构和交叉编译器无需在每次命令中额外指定大大简化了操作。3.2 加载默认配置文件Linux 内核为许多流行的开发板提供了默认的配置文件。对于基于 S3C2410/S3C2440 的板子通常有一个s3c2410_defconfig。虽然我们的板子是 S3C2440但其与 S3C2410 在软件层面高度兼容尤其是对于内核的基础配置使用 2410 的默认配置是一个很好的起点可以帮我们快速建立一个能编译通过的基础配置。在终端中确保你位于内核源码根目录并且已经完成了上一步的Makefile修改然后执行make s3c2410_defconfig这条命令会将arch/arm/configs/s3c2410_defconfig这个默认配置文件的内容复制到内核根目录下的.config隐藏文件中。如果执行成功你会看到configuration written to .config的提示。这个.config文件就是后续make menuconfig进行图形化配置的基础。实操心得一定要先修改Makefile中的ARCH和CROSS_COMPILE再执行make s3c2410_defconfig。如果顺序反了make defconfig可能会尝试调用系统默认的gcc去编译一些用于生成配置的本地工具虽然不一定会失败但可能产生警告或不可预知的问题。按标准流程来最稳妥。4. 针对 mini2440 开发板的关键源码修改默认配置只是提供了一个能编译的框架要让它能在我们的 mini2440 上正确运行还需要针对硬件进行几处关键的源码修改。这些修改集中在时钟、NAND 分区和机器 ID 上。4.1 修改系统时钟频率系统时钟是芯片的“心跳”内核需要知道外部晶振的频率来计算和设置内部各种总线时钟如 FCLK, HCLK, PCLK。mini2440 开发板上焊接的外部晶振通常是 12MHz。然而内核源码中 SMDK2440 平台的默认配置可能设置为其他频率如 16.9344MHz。如果频率设置错误最直接的表现就是串口输出乱码因为串口波特率是基于系统时钟分频计算出来的。我们需要修改的文件是arch/arm/mach-s3c2440/mach-smdk2440.c。找到s3c24xx_init_clocks()函数调用处。在我这个内核版本中它位于第 163 行左右s3c24xx_init_clocks(16934400);将其中的参数16934400即 16.9344MHz修改为 mini2440 实际的晶振频率12000000即 12MHzs3c24xx_init_clocks(12000000);修改后保存。这个修改确保了内核初始化时能基于正确的基准频率推导出 CPU 主频、内存时钟和串口波特率等是系统稳定运行的基础。4.2 修改 NAND Flash 分区表我们的内核镜像和根文件系统最终都要烧写到板子的 NAND Flash 中。BootloaderSupervivi在烧写时已经对 Flash 进行了物理上的分区。内核必须知道这个分区的布局才能正确找到内核镜像和根文件系统所在的位置否则无法挂载根文件系统导致启动失败。分区信息定义在arch/arm/plat-s3c24xx/common-smdk.c文件中注意在有些版本或补丁中这个文件可能是common-smdk2440.c请根据你的源码实际情况确定。我们需要找到static struct mtd_partition smdk_default_nand_part[]这个结构体数组并将其修改为与 Supervivi 一致的分区表。根据我使用的 Supervivi 和常见的 mini2440 布局分区通常如下第0分区Bootloader 区存放 Supervivi大小为 0x30000 (192KB)。第1分区内核区从 0x50000 开始大小为 0x200000 (2MB)。这里留出了 0x20000 (128KB) 的空隙可能是给参数区预留的。第2分区根文件系统区从 0x250000 开始大小直到 Flash 末尾64MB Flash 约为 0x3ffc000。修改后的分区表如下static struct mtd_partition friendly_arm_default_nand_part[] { [0] { .name supervivi, .size 0x00030000, .offset 0, }, [1] { .name Kernel, .offset 0x00050000, .size 0x00200000, }, [2] { .name root, .offset 0x00250000, .size 0x03dac000, // 64MB - 0x250000 } };注意事项size字段也可以使用MTDPART_SIZ_FULL宏表示占用剩余所有空间。但显式写出大小更清晰。务必确保这里的偏移量offset和大小与你在使用 Supervivi 烧写时设定的分区完全一致这是内核能成功挂载根文件系统的关键。4.3 修改机器类型 ID (MACH_TYPE)当 Bootloader 引导内核时会通过寄存器传递一个机器类型 ID 给内核。内核根据这个 ID 来判断自己正在哪种具体的开发板上运行从而调用正确的平台初始化代码。如果 ID 不匹配内核会因找不到对应的机器描述而 panic提示 “Error: unrecognized/unsupported machine ID”。机器 ID 的定义在arch/arm/tools/mach-types这个文件中。这个文件很长里面列出了各种 ARM 开发板对应的数字 ID。我们需要找到 S3C2440 对应的那一行并确保其 ID 值与 Bootloader 传递的值一致。对于 mini2440 和常用的 Supervivi这个值通常是782。在mach-types文件中搜索 “S3C2440” 或 “SMDK2440”。你可能会找到类似这样的一行s3c2440 ARCH_S3C2440 S3C2440 782请确认这一行的最后一个数字确实是782。如果不是请将其修改为782。在较新的内核中这个值可能已经定义正确但手动检查一遍是很好的习惯。这个数字是 Bootloader如 U-Boot, Supervivi和内核之间的一个“暗号”必须对上。5. 内核功能配置与 YAFFS2 集成5.1 启动图形化配置菜单完成上述关键源码修改后我们就可以进行详细的内核功能配置了。使用最经典的make menuconfig命令make menuconfig这会启动一个基于 ncurses 的文本图形界面。在这里你可以通过方向键导航空格键选中/取消选中[*]表示编译进内核[M]表示编译为模块[ ]表示不编译回车键进入子菜单。5.2 配置内核支持 EABI 编译这是解决后续可能出现的 “Kernel panic - not syncing: Attempted to kill init!” 错误的关键一步。EABI (Embedded Application Binary Interface) 是一种更高效的 ARM 二进制接口规范。新的工具链如 arm-linux-gcc 4.x默认使用 EABI 编译用户空间应用程序。如果内核不支持 EABI就无法运行这些新规范编译的程序包括/sbin/init从而导致内核恐慌。在menuconfig界面中按如下路径导航Kernel Features ---进入后找到以下两个选项[*] Use the ARM EABI to compile the kernel(确保被选中即前面有*)[*] Allow old ABI binaries to run with this kernel (EXPERIMENTAL)(这个也建议选中以提供更好的兼容性)将它们都选中按空格键直到出现[*]。选中后者允许内核运行旧 ABI 规范的二进制文件对于使用混合库的环境有一定好处。5.3 集成 YAFFS2 文件系统现在我们需要将之前准备好的 YAFFS2 源码集成到内核中。假设你的 YAFFS2 源码解压后目录为yaffs2。复制源码将yaffs2目录下的yaffs2文件夹注意是子目录完整地复制到内核源码的fs/目录下。cp -r /path/to/yaffs2/yaffs2 /path/to/linux-2.6.29.4/fs/修改fs/Kconfig文件这个文件控制着make menuconfig中文件系统相关的配置选项。我们需要在合适的位置比如在JFFS2配置项附近添加 YAFFS2 的配置入口。添加内容大致如下menu Miscellaneous filesystems ... source fs/jffs2/Kconfig source fs/yaffs2/Kconfig # 添加这一行 source fs/ubifs/Kconfig ...这行source指令告诉配置系统去读取fs/yaffs2/Kconfig文件。修改fs/Makefile文件这个文件控制着哪些文件系统会被编译。在文件中找到编译 JFFS2 的地方在附近添加一行obj-$(CONFIG_YAFFS_FS) yaffs2/这行表示如果配置中定义了CONFIG_YAFFS_FS则编译yaffs2目录。检查 YAFFS2 自带的 Kconfig 和 Makefile确保fs/yaffs2/目录下存在Kconfig和Makefile文件。通常 YAFFS2 源码包中会提供如果没有你可能需要从其他地方获取或手动编写简单的版本。一个基本的Kconfig应该定义CONFIG_YAFFS_FS等配置选项。完成以上文件修改后重新执行make menuconfig。这时在File systems-Miscellaneous filesystems子菜单中你应该能看到YAFFS2 file system support的选项了。将其选中按Y键编译进内核。常见问题如果menuconfig里找不到 YAFFS2 选项请检查fs/Kconfig中添加的source语句路径是否正确。fs/yaffs2/Kconfig文件是否存在且语法正确。执行make menuconfig前最好先make distclean再make s3c2410_defconfig一次以确保配置系统重新扫描所有文件。5.4 其他可能需要的配置根据你的需求可能还需要配置其他选项例如网络驱动在Device Drivers-Network device support-Ethernet (10 or 100Mbit)下找到DM9000支持并选中。mini2440 通常使用 DM9000AEP 网卡。串口驱动Device Drivers-Character devices-Serial drivers-Samsung S3C2410/S3C2440/S3C2442/S3C2412 serial port support必须选中。NAND 驱动Device Drivers-Memory Technology Device (MTD) support-NAND Device Support-NAND Flash support for Samsung S3C SoCs必须选中。EABI 兼容性如前所述在Kernel Features中确保 EABI 相关选项已选。配置完成后保存并退出menuconfig。配置会被保存到.config文件。6. 内核编译与镜像生成6.1 执行编译配置妥当后就可以开始编译内核了。在终端输入make zImagezImage是经过压缩的内核镜像格式适用于大多数 ARM 平台。编译过程会持续一段时间取决于你的电脑性能。如果一切顺利你将在最后看到类似这样的提示OBJCOPY arch/arm/boot/zImage Kernel: arch/arm/boot/zImage is ready编译生成的内核镜像文件zImage位于arch/arm/boot/目录下。这个文件就是我们最终要烧写到开发板“内核分区”的文件。6.2 编译过程问题排查如果编译过程中出现错误请根据错误信息进行排查头文件找不到检查交叉编译工具链的路径和库文件是否完整。可能是工具链安装有问题。语法错误检查你对源码的修改如分区表、时钟是否有语法错误比如少了分号、括号不匹配等。未定义的引用可能是某些驱动配置为[*]编进内核但其依赖的选项没有被选中。需要回到menuconfig仔细检查相关依赖。YAFFS2 相关错误检查fs/yaffs2/下的源码是否完整特别是Kconfig和Makefile是否适配 2.6.29 内核。有时需要为老内核打上特定的 YAFFS2 补丁。7. 制作与烧写 YAFFS2 根文件系统内核启动后需要挂载一个根文件系统rootfs才能提供完整的操作系统环境。我们选择 YAFFS2 格式。7.1 制作 YAFFS2 文件系统镜像你需要一个基本的根文件系统内容。可以从网上下载针对 mini2440 编译好的 busybox 文件系统或者自己使用 busybox 构建。假设你有一个包含/bin,/sbin,/etc,/lib等目录的根文件系统文件夹命名为rootfs。制作 YAFFS2 镜像需要使用专门的工具mkyaffs2image。这个工具通常包含在 YAFFS2 源码包的utils目录下需要自己编译。进入 YAFFS2 源码的utils目录执行make即可生成。然后将生成的mkyaffs2image工具复制到系统路径如/usr/local/bin。制作镜像的命令如下mkyaffs2image rootfs rootfs.yaffs2这条命令会将rootfs目录下的所有内容打包成一个 YAFFS2 格式的镜像文件rootfs.yaffs2。重要提示mkyaffs2image工具可能有针对不同页大小Page Size和 OOB (Out-Of-Band) 大小的版本。mini2440 使用的 NAND Flash 通常是 512 字节页大小 16 字节 OOB 的。确保你使用的工具参数与硬件匹配。有些工具叫mkyaffs2image-128m针对 128MB/页大小 512B或mkyaffs2image-2k针对页大小 2KB。如果不确定查看工具帮助或源码说明。7.2 通过 Supervivi 烧写镜像将编译好的zImage内核镜像和rootfs.yaffs2文件系统镜像通过某种方式如 TFTP 网络、USB 下载线传输到 Windows 宿主机因为 Supervivi 的下载工具通常是 Windows 程序。启动 mini2440 开发板在 Supervivi 启动菜单中选择进入下载模式通常按空格键。在 Windows 上打开 Supervivi 对应的 USB 下载工具如 DNW。首先烧写内核镜像选择[v] Download vivi(或其他类似选项具体看版本可能是[k] Download Linux Kernel)。选择zImage文件工具会将其下载到开发板内存的指定地址如 0x30008000。下载完成后选择[y] Download root (yaffs)。选择rootfs.yaffs2文件开始烧写到 NAND Flash 的 root 分区。这个过程会比较慢因为是在擦除和编程 Flash。烧写完成后重启开发板。在 Supervivi 启动时按回车键或其他指定键让 Supervivi 不进入下载模式而是直接从 NAND 启动。8. 启动过程分析与问题调试8.1 解读启动日志将开发板的串口连接到电脑用串口终端软件如 SecureCRT, Putty, minicom打开对应串口波特率设置为 115200。重启开发板你会看到大量的启动信息输出。这是我们诊断问题最重要的依据。以你提供的日志为例我们关注几个关键点内核版本与编译器Linux version 2.6.29.4 (gcc version 4.3.2)确认内核版本和编译器正确。机器 IDMACH_TYPE 782这与我们在mach-types文件中修改的值一致说明 Bootloader 传递的参数正确内核识别了板子。时钟S3C244X: core 405.000 MHz, memory 101.250 MHz, peripheral 50.625 MHz。这表明内核时钟初始化正常基于我们修改的 12MHz 晶振计算出了正确的频率。NAND 识别与分区NAND device: Manufacturer ID: 0xec, Chip ID: 0x76 (Samsung NAND 64MiB 3,3V 8-bit) Creating 3 MTD partitions on NAND 64MiB 3,3V 8-bit: 0x000000000000-0x000000030000 : supervivi 0x000000050000-0x000000250000 : Kernel 0x000000250000-0x000003ffc000 : root这显示内核成功识别了 NAND Flash 芯片并且创建的分区与我们代码中定义的完全一致。这是成功挂载根文件系统的前提。文件系统挂载yaffs: dev is 32505858 name is mtdblock2 yaffs: Attempting MTD mount on 31.2, mtdblock2 yaffs_read_super: isCheckpointed 0 VFS: Mounted root (yaffs filesystem) on device 31:2.这几行是“胜利的曙光”它表明内核成功在 MTD 块设备mtdblock2对应第三个分区即我们的 root 分区上挂载了 YAFFS2 文件系统。用户空间启动挂载根文件系统后内核会尝试执行根文件系统中的初始化程序默认为/sbin/init或由内核命令行参数init指定。随后你看到了Please press Enter to activate this console.的提示并成功获得了[rootFriendlyARM /]#的 shell 提示符。这说明系统已完全启动成功8.2 常见启动问题与解决方案串口无输出或全是乱码检查接线和波特率确认串口线连接正确终端软件波特率设置为 1152008N1。检查时钟修改这是导致乱码最常见的原因。确认mach-smdk2440.c中的s3c24xx_init_clocks参数已改为12000000。检查 Bootloader 参数有些 Bootloader 会设置串口时钟分频确保其与内核配置不冲突。内核启动到一半卡住提示 “Error: unrecognized/unsupported machine ID”这是机器类型 ID 不匹配的典型错误。请仔细核对arch/arm/tools/mach-types文件中 S3C2440 对应的 ID 值。BootloaderSupervivi传递给内核的 ID 值。有时需要在 Supervivi 的命令行中设置mach_type782。确保你修改并保存了mach-types文件并且重新编译了内核。内核 Panic: “Kernel panic - not syncing: Attempted to kill init!”首要原因内核配置中未启用 EABI 支持。请确认Kernel Features-Use the ARM EABI to compile the kernel和Allow old ABI binaries...已被选中。次要原因根文件系统制作有问题或者内核命令行参数root指定错误导致内核找不到或无法执行/sbin/init或/linuxrc。检查root/dev/mtdblock2是否正确以及文件系统中是否存在可执行的init程序。VFS: Cannot open root device “mtdblock2” or unknown-block(31,2)内核找不到指定的根设备。可能原因NAND 驱动未编译进内核。检查menuconfig中 MTD 和 S3C NAND 驱动是否选中。分区表错误。内核识别到的分区号可能不是 2。请根据内核启动日志中打印的 MTD 分区信息调整root参数例如可能是mtdblock3。内核命令行参数错误。确认 Uboot 或 Supervivi 传递给内核的root参数正确。YAFFS2 挂载失败内核未包含 YAFFS2 支持。确认menuconfig中已选中YAFFS2 file system support。YAFFS2 源码集成有问题。检查fs/Kconfig、fs/Makefile以及fs/yaffs2/目录下的文件是否正确。文件系统镜像格式错误。确保使用正确的mkyaffs2image工具匹配 Flash 页大小制作镜像。网络接口 eth0 启动失败 在你的日志末尾有Try to bring eth0 interface up......失败的信息。这是因为内核虽然编译了 DM9000 驱动但可能默认的 MAC 地址或 IO 地址/中断号与 mini2440 板子不完全匹配或者文件系统中缺少网络配置脚本。驱动问题检查内核配置中DM9000驱动的选项确保其支持正确。有时需要在板级初始化代码如mach-smdk2440.c中添加 DM9000 的平台设备资源基地址、中断号。文件系统问题文件系统中/etc/init.d/或/etc/rc.d/下的启动脚本可能试图配置一个不存在的网络接口或者缺少必要的网络工具ifconfig,route。可以暂时忽略或后续在文件系统中完善网络配置。9. 后续完善与优化建议系统成功启动到命令行只是第一步。一个可用的嵌入式 Linux 系统还需要很多工作完善根文件系统现在的文件系统可能很精简。你需要根据需要添加各种应用程序、库、配置文件。Busybox 提供了很多基础工具但你可能还需要添加如dropbearSSH 服务器、ntpd时间同步、你自己的应用程序等。配置网络解决 eth0 启动失败的问题。检查内核中 DM9000 的平台设备定义是否正确通常在arch/arm/mach-s3c2440/mach-smdk2440.c或类似的板级文件中添加dm9000_platform_data。在文件系统中正确配置/etc/network/interfaces或对应的脚本。解决 RTC 警告启动日志中有hwclock: cant open /dev/misc/rtc: No such file or directory的警告。这是因为内核支持了 RTC 驱动S3C24XX RTC但文件系统中没有创建对应的设备节点。可以在文件系统的/dev目录下创建或者更规范地使用udev或mdevBusybox 提供在启动时自动创建设备节点。在 busybox 初始化脚本中启用mdev是一个常见做法。优化内核配置使用make menuconfig进一步裁剪不需要的驱动和功能让内核更精简启动更快占用内存更少。这对于资源有限的嵌入式环境非常重要。固化环境变量可以将成功的编译配置保存下来。使用make savedefconfig可以将当前配置生成一个精简的defconfig文件以后可以通过make xxx_defconfig快速恢复。移植一个可用的 Linux 系统到开发板就像完成一个复杂的拼图。每一步修改都需要理解其背后的硬件知识和软件原理。这次将 Linux 2.6.29.4 移植到 mini2440 的过程涵盖了从环境搭建、源码修改、内核配置、文件系统制作到问题调试的完整链条。希望这份详细的记录能帮助你少走弯路更深入地理解嵌入式 Linux 系统的构建过程。当你最终在终端看到那个熟悉的#提示符时所有的努力都是值得的。