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

内核的“瘦身”艺术:从臃肿BSP到精悍映像的雕刻之路

在嵌入式Linux的领域里,我们常常从芯片厂商或方案商那里拿到一份BSP(板级支持包)。它能让系统跑起来,但往往“臃肿不堪”。一个通用的内核映像,动不动就几十MB大小,对于Flash只有128MB甚至64MB的消费电子产品来说,简直是资源浪费的“重罪”。

这种臃肿不仅仅是占用存储空间那么简单。

一个“肥胖”的内核,意味着更长的解压时间、更高的内存占用(包括代码段和数据段),以及启动时执行冗长初始化调用序列所带来的时间损耗。在某些对启动时间有严苛要求的产品上——比如智能家居设备或工业控制器——这种臃肿是完全不可接受的。

今天,我们就来深入探讨内核“雕刻”的艺术。我们将拿起手术刀(menuconfig),深入内核的配置文件,将那些无用的“脂肪”——多余的驱动、调试代码、文件系统和网络协议——一层层剥离。

这不仅仅是一份配置清单,更是一套完整的方法论。

准备工作:搭建你的“手术台”和“测量仪”

在动刀之前,我们必须建立一个可重复、可测量的环境。没有准确的测量,优化就无从谈起。

建立基线环境

你需要一个能100%复现的编译环境。这通常意味着固定的交叉编译工具链和目标板架构。

# 示例:为ARM 32位架构编译
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

第一步是获取一个“能跑”的配置。这通常来自BSP提供商或通用的defconfig。

# 使用供应商提供的默认配置
make vendor_board_defconfig

现在编译它,并记录下尺寸。

make -j8 zImage modules

编译完成后,我们关注两个关键文件:

  • arch/arm/boot/zImage:这是压缩后的内核映像,是你最终要烧录到Flash的文件。

  • vmlinux:这是未压缩的、包含所有符号的ELF文件,是我们分析尺寸的“金矿”。

关键测量工具:size命令

不要只看zImage的大小——压缩算法(如Gzip vs XZ)会扭曲你的认知。你必须看vmlinux,它反映了内核真正的代码量。

$ size vmlinuxtext    data     bss     dec     hex filename
20971520 4194304 2097152 27262976 1a00000 vmlinux
  • text:代码段,这是我们裁剪的主要目标。

  • data:已初始化的数据段。

  • bss:未初始化的数据段。

把这个text段的大小(这里是20MB)记录下来,这就是我们的“基准体重”。

更精细的分析工具

我们怎么知道具体是哪些函数占用了空间?

nm(GNU Binutils的一部分)是个好帮手:

# 按尺寸倒序列出vmlinux中的所有符号(函数和数据)
$ nm -S --size-sort vmlinux | tail -n 20

这能告诉你最大的“罪魁祸首”是哪些函数。你常会在这里看到庞大的驱动probe函数,或者复杂的协议栈实现。

更现代的工具是bloaty(来自Google),它能提供更精美的报告,显示空间被哪些编译单元或符号占用:

$ bloaty vmlinux -d symbols | head -n 20

迭代优化循环

现在,我们的工作流程很清晰了:

  1. 运行make menuconfig(或nconfigxconfig等)。

  2. 只改动一小撮相关的配置。

  3. 保存配置。

  4. 运行make -j8 zImage(不需要每次都编译模块,除非改了模块相关配置)。

  5. 测量size vmlinux,看看text段减少了多少。

  6. 至关重要的一步:把新内核烧录到板子上,确保它还能正常启动!

你永远不知道关掉的某个看似无用的CONFIG_选项,是不是被某个关键驱动隐性依赖。不要一次修改上百个配置,那等于自找麻烦。建议一次改一个“功能大类”(比如关闭所有NFS相关选项),然后立即测试。

menuconfig的“屠宰场”:通用设置与调试选项

打开make menuconfig,我们迎来了第一个战场。大部分“立竿见影”的优化成果都在这里。

General setup(通用设置)

这是个“杂物间”,藏着很多历史遗留和通用功能。

  • CONFIG_IKCONFIG和CONFIG_IKCONFIG_PROC

    • 这是什么?它把完整的内核配置文件编译进内核映像,然后可以通过/proc/config.gz读取。

    • 为什么要关闭?它本身可能就占掉几十到上百KB的data段空间。对于发布产品,这是纯粹的浪费。

    • 动作:果断关闭(设为N)。

  • CONFIG_NAMESPACES(命名空间)

    • 这是什么?PID、Network、Mount等命名空间,是Docker和容器化的基石。

    • 为什么要关闭?你的嵌入式设备跑Docker吗?99%的情况是不需要的。这是一个极其庞大和复杂的子系统,引入的开销遍布内核各个角落。

    • 动作:关闭(N)。User、PID、UTS等相关子选项会全部消失。

  • CONFIG_EMBEDDED(嵌入式系统)

    • 这是什么?这是一个“元选项”,选中它会触发其他配置的默认值向“更小”倾斜。

    • 动作:选中(Y)。这像是一个“一键瘦身”的起点。

  • CONFIG_KPROBES、CONFIG_FTRACE、CONFIG_PROFILING

    • 这是什么?内核的动态探测、函数追踪、性能分析框架。

    • 为什么要关闭?这些是给内核开发者调试性能问题用的,会往text段注入大量的“桩”和追踪代码。

    • 动作:全部关闭(N)。

Kernel hacking(内核调试)——脂肪的重灾区

欢迎来到“脂肪中心”。这里的几乎所有选项,在生产环境中都应该被无情地关闭。

  • CONFIG_DEBUG_KERNEL

    • 这是什么?内核调试功能的总开关。

    • 动作:关闭(N)。

  • CONFIG_DEBUG_INFO(调试信息)

    • 这是什么?它告诉GCC把DWARF调试信息编译到vmlinux里。

    • 为什么要关闭?关掉它,你的vmlinux体积可能会瞬间减小70%-90%。虽然不影响压缩后的zImage大小,但会极大拖慢编译和链接速度。

    • 动作:坚决关闭(N)。

  • CONFIG_PRINTK(内核打印)

    • 这是什么?printk()函数的支持。

    • 为什么要调整?彻底关闭它,内核启动时将一片寂静,一旦失败你将一无所知。

    • 妥协方案:保留CONFIG_PRINTK=y,但减小日志缓冲区CONFIG_LOG_BUF_SHIFT。默认可能是17(131072字节),对于嵌入式系统,14(16384字节)甚至12(4096字节)足矣。这能省下上百KB的data/bss段内存。

  • CONFIG_DEBUG_FS(调试文件系统)

    • 这是什么?挂载在/sys/kernel/debug的调试文件系统。

    • 为什么要关闭?它本身的代码,以及所有驱动注册到debugfs的代码和字符串,都是纯粹的开销。

    • 动作:关闭(N)。

  • CONFIG_SLUB_DEBUG、CONFIG_DEBUG_VM、CONFIG_DEBUG_MUTEXES、CONFIG_LOCKDEP

    • 这是什么?内存分配器调试、虚拟机调试、锁调试等。

    • 动作:全部,一个不留,统统关闭(N)。这些是为内核开发者准备的盛宴,不是给你的产品准备的。

  • CONFIG_MAGIC_SYSRQ(魔术键)

    • 这是什么?Alt-SysRq-B重启等功能。

    • 动作:关闭(N)。

仅仅是清理Kernel hacking这一部分,你的vmlinux的text段应该就有了10%-20%的缩减。

处理器与架构:打好“地基”

这部分的配置如果出错,内核可能根本无法启动。但配置对了,也能省下不少空间。

Processor type and features(处理器类型与特性)

  • CONFIG_SMP(对称多处理)

    • 这是什么?多核支持。

    • 抉择:你的CPU是单核(如Cortex-A7单核)还是多核?如果是单核,请务必关闭它(N)。SMP会引入庞大的锁开销和核间同步代码,关掉它text段会有显著下降。

  • CONFIG_HIGHMEM / CONFIG_NOHIGHMEM(高内存支持)

    • 这是什么?只在32位架构上出现。如果物理内存大于约896MB,就需要HIGHMEM。

    • 抉择:如果你的产品只有256MB或512MB内存,请关闭CONFIG_HIGHMEM(N)。这能简化内存管理子系统的代码,减小text段。

  • CONFIG_CPU_FREQ(CPU频率调整)

    • 这是什么?cpufreq子系统,允许CPU在performance、ondemand等策略间切换。

    • 抉择:你的产品需要动态调频吗?如果它是插电全速运行的设备,或者功耗敏感永远低速运行的设备,可能不需要动态调频。关闭CONFIG_CPU_FREQ=n,并移除所有cpufreq驱动,能省下不少空间。

  • CONFIG_CMA(连续内存分配器)

    • 这是什么?为多媒体或GPU预留大块物理连续内存。

    • 抉择:如果你的设备没有摄像头、不播视频,很可能不需要CMA。关闭它(N)。

  • CONFIG_VFP(向量浮点支持)

    • 这是什么?ARM的浮点运算单元支持。

    • 抉择:这个必须开启(Y)。如果关闭,用户空间的浮点运算会引发内核异常,然后用软件模拟浮点,那将是性能灾难。不要动它。

文件系统“大扫除”:只留你用的那一个

文件系统是内核text段的另一个“大户”。开发者图省事,往往把ext4、vfat、nfs、cifs等全选上。

File systems(文件系统)

基本原则:只保留你的根文件系统所用的那一个。

  • 如果你的rootfs是ext4(对应CONFIG_EXT4_FS=y),那么CONFIG_BTRFS_FS=nCONFIG_XFS_FS=nCONFIG_JFS_FS=nCONFIG_F2FS_FS=n等全部关闭(N)。

  • CONFIG_NETWORK_FILESYSTEMS(网络文件系统)

    • 这是什么?NFS、CIFS等。

    • 抉择:你的设备需要在启动时挂载NFS根目录吗?这是开发时用的,发布产品100%不需要。CONFIG_NFS_FS=nCONFIG_CIFS_FS=n,这将省下巨量的代码。

  • CONFIG_AUTOFS_FS(自动挂载)

    • 这是什么?自动挂载U盘、光盘的autofs。

    • 抉择:嵌入式系统通常使用mdev或udev处理热插拔,autofs几乎不用。关闭(N)。

  • CONFIG_DNOTIFY

    • 这是什么?一个古老的文件变更通知机制。

    • 抉择:现代系统都用CONFIG_INOTIFY_USER。关闭(N)。

  • CONFIG_MISC_FILESYSTEMS(杂项文件系统)

    • CONFIG_SQUASHFS:这个很有用。如果你的rootfs是只读的,SquashFS(压缩的只读文件系统)是最好的选择,能极大压缩rootfs体积。

    • CONFIG_CRAMFS:古老的压缩文件系统,不如SquashFS。关闭(N)。

  • DOS/FAT/NT Filesystems

    • 抉择:你的设备需要支持U盘(通常是FAT32)吗?如果需要,保留CONFIG_VFAT_FS=y(它依赖CONFIG_FAT_FS)。如果设备是个“黑盒”,完全没有USB口,大胆关闭它们(N)。

清理完文件系统,你的text段应该又会迎来一次“跳水”。

网络栈的“净化”:你的设备真的需要IPv6吗?

网络协议栈是vmlinux里最庞大、最复杂的组件之一。

Networking support -> Networking options

  • CONFIG_INET:这是IPv4的总开关。关了它,设备就断网了,所以必须保留(Y)。

  • CONFIG_IPV6(IPv6支持)

    • 这是什么?IPv6协议栈。

    • 抉择:这是一个巨大的优化点。你的嵌入式设备(如智能插座、温控器)跑在纯IPv4局域网里吗?很可能不需要IPv6。

    • 动作:关闭(N)。这会一次性干掉IPv6、NDISC、ADDRCONF以及所有IPv6相关的netfilter模块,收益巨大。

  • CONFIG_NETFILTER(防火墙)

    • 这是什么?iptables/nftables的内核支持。

    • 抉择:你的设备是“路由器”吗?需要做NAT转发、包过滤吗?如果只是终端设备,很可能不需要复杂防火墙。

    • 动作:关闭(N)。CONFIG_NETFILTER会引入大量hook到网络包处理路径中,即使没有规则,这些hook也存在。关掉它能减小text段,甚至提升网络性能。

注意:CONFIG_NF_CONNTRACK(连接跟踪)常被某些驱动依赖。如果关不掉NETFILTER,至少试着关掉CONNTRACK。

  • CONFIG_WIRELESS(无线支持)

    • 这是什么?Wi-Fi和cfg80211框架。

    • 抉择:你的设备用Wi-Fi吗?如果只用有线以太网,关闭(N)。cfg80211和mac80211是两个极其庞大的子系统。

  • CONFIG_UNIX(Unix域套接字)

    • 这是什么?本地进程间通信的socket。

    • 抉择:这个几乎必须保留(Y)。太多用户空间程序(如syslog、dbus)依赖它。

  • CONFIG_INET_DIAG、CONFIG_UNIX_DIAG

    • 这是什么?ss命令所需的支持。

    • 抉择:调试时有用,发布产品不需要。关闭(N)。

驱动程序:精兵简政的艺术

这是最耗时、最繁琐,但也最能体现“定制”价值的地方。原则是:了解你的硬件原理图。

Device Drivers

  • Generic Driver Options

    • CONFIG_UEVENT_HELPER_PATH:古老的机制,内核会fork/exec用户空间程序处理热插拔。

    • 抉择:现代系统用netlink和udev/mdev。关闭(N),并清空路径字符串。

  • Block devices(块设备)

    • CONFIG_BLK_DEV_LOOP:Loopback设备,允许把文件挂载成块设备。

    • 抉择:开发时有用,产品不需要。关闭(N)。

    • CONFIG_BLK_DEV_RAM:RAM disk。

    • 抉择:古老的initrd才用它,现在都用initramfs。关闭(N)。

  • SCSI device support(SCSI设备)

    • 抉择:你的设备用SATA硬盘吗?如果用的是eMMC/SD卡(属于MMC/SD/SDIO驱动)或SPI-NAND(MTD驱动),整个SCSI子系统都可以关闭(N)。这又是一个巨大的胜利。

  • Network device support

    • 抉择:只保留你的设备真正拥有的网卡驱动。

    • 比如,你用DM9000(CONFIG_DM9000=y),那么所有Realtek、Intel、Marvell等驱动全部关闭(N)。

    • CONFIG_PPPCONFIG_SLIP:拨号上网,关闭(N)。

  • Input device support

    • 抉择:只保留你有的输入设备。

    • CONFIG_KEYBOARD_...:你的设备有PS/2或AT键盘吗?没有就关闭(N)。

    • CONFIG_MOUSE_...:有PS/2鼠标吗?没有就关闭(N)。

    • CONFIG_INPUT_TOUCHSCREEN_...:如果有触摸屏,只保留你那个型号的驱动。

    • CONFIG_INPUT_EVDEV:这个要保留(Y)。用户空间的libinput或tslib依赖它读取输入事件。

  • Graphics support(图形支持)

    • CONFIG_DRM / CONFIG_FBDEV:DRM是现代图形框架,FBDEV是古老的。

    • 抉择:尽量只用一个。

    • CONFIG_VGA_CONSOLE:关闭(N),这是x86的遗产。

    • CONFIG_BOOT_LOGO:内核启动时的小企鹅logo。

    • 抉择:它会把logo图片编译进内核的data段。关闭(N),能省下几十KB。

  • Sound card support

    • 抉择:没声卡?CONFIG_SOUND=n,这将干掉ALSA或OSS整个子系统。

  • USB support

    • 抉择:没USB口?CONFIG_USB_SUPPORT=n

    • 有USB口,但只作为Host(插U盘)?关闭CONFIG_USB_GADGET(N)。Gadget是作为Device的框架,非常庞大。

    • 只作为Device?关闭CONFIG_USB_HOST_...(N)。

    • 在Host模式下,只保留你支持的USB类驱动。需要支持USB打印机吗?需要USB转串口吗?按需索取。

模块与内建的权衡

在menuconfig里,每个驱动有三个选项:

  • [ ] (N):不编译。

  • [y] (Y):编译为内建,代码链接到vmlinux,成为zImage的一部分。

  • [m] (M):编译为模块,代码编译成.ko文件,放在rootfs下,按需加载。

这是一个核心的哲学问题。

  • 全部内建 (Y)

    • 优点:启动快(不需要initramfs去modprobe驱动),rootfs简单。

    • 缺点:zImage巨大,所有驱动都被加载到内存。

  • 大量使用模块 (M)

    • 优点:zImage极小,内存占用低。

    • 缺点:启动慢,rootfs复杂。

给嵌入式系统的“黄金法则”

  • 启动必需的驱动必须内建 (Y):包括eMMC/SD卡驱动、根文件系统驱动、控制台驱动。

  • 非必需的、热插拔的驱动编译为模块 (M):包括USB驱动、Wi-Fi驱动、Sound驱动。

  • 硬件上根本没有的驱动彻底关闭 (N)。

终极瘦身:CONFIG_MODULES=n

如果你的设备硬件完全固定(比如没有USB口的路由器),可以采取最激进的策略:

  • 把所有需要的驱动全部设为[y]

  • 把所有不需要的驱动设为[N]

  • 在General setup里,关闭CONFIG_MODULES=n(禁用可加载模块支持)。

效果:

  • 内核不再支持.ko模块。

  • modprobelsmodrmmod全部失效。

  • 内核的text段进一步减小,移除了整个“模块加载器”子系统。

  • 你的vmlinux将是一个纯粹的、单一的二进制文件。

这是“雕刻”的极致,也是嵌入式系统最推荐的最终形态。

最后的“一榨”:压缩算法与编译器选项

你已经把vmlinux的text段从20MB砍到了5MB,但zImage还是有点大。

General setup -> Kernel compression mode

  • Gzip:压缩率不错,解压速度中等。

  • LZO/LZ4:压缩率差,解压极快(利于启动速度)。

  • XZ:压缩率最高,解压最慢(牺牲启动速度)。

  • ZSTD:平衡。

抉择:如果你的Flash空间极其宝贵,不惜牺牲一点启动时间,请选择CONFIG_KERNEL_XZ。它能把zImage压到最小。

Kernel hacking -> Compiler optimization level

  • CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE (-O2):优化速度。

  • CONFIG_CC_OPTIMIZE_FOR_SIZE (-Os):优化尺寸。

抉择:在嵌入式系统中,text段的尺寸和Cache-Hit同样重要。选择-OsCONFIG_CC_OPTIMIZE_FOR_SIZE=y)。这会告诉GCC优先考虑减小代码体积,通常能带来text段5%-10%的缩减,性能损失微乎其微。

LTO(链接时优化)

  • CONFIG_LTO_CLANG / CONFIG_LTO_GCC

  • 这是什么?链接时优化,编译器在链接所有.o文件时再次进行全局优化。

  • 抉择:这是一个“黑科技”,可能让vmlinux更小,也可能引入bug,并极大增加编译时间。

  • 建议:作为最后的手段,尝试开启它,看看尺寸收益。

雕刻至此,你的内核应该已经从一个臃肿的胖子,变成了一个精悍的“李小龙”。它启动飞快,占用内存极低,Flash里只留下它应有的那一份。

 

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

相关文章:

  • 网站建设公司-跨界鱼科技网站返回503的含义是
  • 泉州找工作哪个网站好手机版网站建设价格
  • 网站制作费用遵义网上房地产
  • pc门户网站是什么意思建设银行济宁分行网站
  • 免费网站建设策划宿迁东岸网络技术有限公司
  • 南昌有哪些做网站的公司58同城网站建设推广
  • 啥时候用抽象类,啥时候用接口【示例】
  • GB/T 4857.3:运输包装静载堆码试验要点
  • 【Windows】Windows 11 添加IE浏览器 解决浏览器兼容问题
  • 高职大数据技术专业需要的基础
  • 织梦网站建设asp.net 微信网站
  • 时尚网站电子商城网站开发
  • Spark专有名词
  • wordpress 创建子主题外链seo招聘
  • 广告公司网站设计策划设计ui是什么意思
  • 怎么在Adobe Photoshop中调用banana与flux、即梦4.0等AI图像模型?AI+工作流已成为平面设计师必备技术手段!不用等着被淘汰吧!
  • 免费的网站域名查询565wccwordpress交互式地图
  • 先建网站还是先做app好特产网站源码
  • 轻云服务器 多个网站wordpress表白模板下载
  • 腾冲做兼职的网站专业企业展厅设计公司
  • 前端导出大量数据到PDF方案
  • 全自动分液站在实验室自动化中的关键作用与性能解析
  • C2S-Scale 27B 模型: AI 解码 “细胞语言“,发现癌症疗法新途径
  • 学做网站需要文化嘛seo网站优化推广教程
  • 电介质的主要电气特性:液体电介质的损耗--与温度和频率的关系
  • 求最大连续bit数
  • C++入门(一)(竞赛)
  • 差分隐私随机梯度下降(DP-SGD)详解
  • AUTOSAR 通信栈深度解析:PduR 与 CanTp 的交互机制(图文详解)
  • 大学网站策划方案网站制作费用及后期运营