嵌入式系统代码与内核大小优化指南
在嵌入式系统开发中,资源通常是有限的,因此优化代码和内核的大小至关重要。本文将介绍一些有效的方法来减少代码和内核的占用空间,提高系统的资源利用率。
查找所需共享库
使用工具链中的
readelf
程序可以优雅地查找程序所需的共享库。以下是具体操作步骤:
1. 执行命令
arm-linux-readelf -d <your program> | grep NEEDED
,该命令会解析程序的 ELF 头,
-d
参数让程序仅打印动态信息,
grep
命令则显示该部分中共享库的条目。例如:
$ arm-linux-readelf -d <your program> | grep NEEDED
0x00000001 (NEEDED) Shared library: [libfontconfig.so.1]
0x00000001 (NEEDED) Shared library: [libiconv.so.2]
0x00000001 (NEEDED) Shared library: [libfreetype.so.6]
0x00000001 (NEEDED) Shared library: [libz.so.1]
0x00000001 (NEEDED) Shared library: [libexpat.so.1]
0x00000001 (NEEDED) Shared library: [libc.so.0]
- 对所有可执行文件执行此操作,就能得到需要包含在目标根文件系统中的文件列表。
在从工具链收集文件时,要注意每个共享库都有一个文件和几个指向该文件的符号链接。例如:
$ ls -l $ROOTFS/lib/libc*
-rw-r--r-- 1 gene gene 13043 2009-06-30 20:57 libcrypt-0.9.30.1.so
lrwxrwxrwx 1 gene gene 20 2009-06-30 23:57 libcrypt.so.0 -> libcrypt-0.9.30.1.so
lrwxrwxrwx 1 gene gene 21 2009-06-30 23:57 libc.so.0 -> libuClibc-0.9.30.1.so
由于在许多系统中根文件系统不会改变,符号链接会浪费空间。因此,可以将库复制到根文件系统并将其重命名为
libc.so.0
以节省空间。
编译优化以节省空间
GCC 的 -Os 选项
使用 GCC 的
-Os
参数编译程序可以使编译器生成尽可能小的代码。操作命令如下:
$ arm-linux-gcc -Os <program>
不过,较小的代码并不总是意味着更快的代码,所以这个选项可能并不适用于系统中的所有程序。
-Os
选项与
-O2
类似,但不包含以下会增加代码大小的优化选项:
-
-falign-functions
-
-falign-jumps
-
-falign-loops
-
-falign-labels
-
-freorder-blocks
-
-freorder-blocks-and-partition
-
-fprefetch-loop-arrays
-
-ftree-vect-loop-version
这些以
falign
开头的标志会将代码移动到新页面开始,使地址从内存中获取更快;
freorder
函数会调整生成的代码以减少 CPU 执行的分支数量;最后两个选项通过在循环执行前缓存数据来优化循环,减少等待从内存中获取指令或数据的时间。
需要注意的是,GCC 的大小优化不会大幅减少代码大小,不能指望它神奇地缩小程序的大小。
静态链接
静态链接通过在链接过程中将所有代码包含在应用程序的二进制映像中来减少代码依赖。创建静态链接的应用程序只需在链接过程中添加
-static
标志,例如:
$ arm-linux-ld -static -l corefunctions file1.o file2.o -o app
如果链接的是 GCJ 生成的目标文件,还需要添加
-static-libgcj
:
$ arm-linux-ld -static -static-libgcj -main=Class1 Class1.o Class2.o -o app
但静态链接可能会导致程序包含大量未使用的代码(死代码)。例如,假设
libcorefunctions
库包含
core1.o
和
core2.o
,
file1.o
调用
functiona()
,
file2.o
调用
functionz()
,则整个
core1.o
和
core2.o
都会包含在最终映像中,导致程序包含
functionx
、
functiony
和
functionb
等死代码。
为避免这个问题,应将每个函数放在单独的
.o
文件中,这样链接器就能创建尽可能小的可执行文件。可以使用工具链中的
ar
命令查看库的结构,例如:
$ arm-linux-ar t ./busybox-1.14.2/libbb/libxfuncs.o
(clipped)
xreadlink.o
xrealloc_vector.o
xregcomp.o
还可以使用
wc
命令统计存档中目标文件的数量:
$ arm-linux-ar t ./busybox-1.14.2/libbb/libxfuncs.o | wc -l
115
如果项目中变量被多个函数共享,可创建一个包含共享变量的文件,并提供
get
和
set
函数,然后用函数调用替换直接引用。
去除调试信息
ELF 格式的二进制文件包含主要用于调试的元数据,在目标系统上部署时,这些数据无用且浪费空间。可以使用工具链中的
strip
实用程序去除这些数据,操作步骤如下:
1. 去除单个文件的调试信息:
$ arm-linux-strip <file>
- 批量去除根文件系统中所有文件的调试信息:
$ find <rfs root> -type f -exec arm-linux-strip {};\
strip
命令会在尝试更改文件之前检查文件是否为 ELF 格式的可执行文件,因此无需专门配置
find
命令来查找特定类型的文件。
减少内核大小
内核与普通可执行文件类似,减少其大小的第一步是找出永远不会执行的代码并移除。内核配备了一个很好的工具——配置编辑器,可用于选择要包含在构建中的代码。这项工作主要分为两部分:移除未使用的驱动程序和移除内核功能。
移除不需要的功能和驱动程序
要移除内核中支持目标板上不存在的硬件的驱动程序,可按以下步骤操作:
1. 列出设备上的硬件。
2. 移除对不存在的外设的支持。
以下是一些可以考虑移除的内容:
| 类别 | 说明 |
| ---- | ---- |
| 文件系统 | 系统可能只使用少数几种文件系统,可移除当前和未来都不会使用的文件系统驱动,如未连接网络的设备上的 NFS 文件系统。 |
| 硬件驱动 | 不使用的硬件不应在内核中构建相应的驱动,未使用的硬件驱动还会带来安全风险。 |
| 输入和输出 | 对于使用串口或帧缓冲图形驱动的设备,鼠标和 VGA 控制台驱动会浪费内存和资源;只有触摸屏的设备,键盘支持也是浪费。 |
| 调试和分析 | 在生产系统中,可禁用主菜单中“Kernel Hacking”下的所有调试选项。 |
| 网络 | 没有网络硬件且不使用网络进行进程间通信的设备,可移除网络栈,但要确保程序不使用 TCP 连接到
localhost
或
127.0.0.1
进行进程间通信。 |
嵌入式系统的建议
在
General Setup
菜单中,找到“Configure standard kernel features (for small systems)”并启用它,会出现以下可调整的选项:
-
Enable 16-bit UID system calls
:用于与使用单字节 UID 系统调用的旧程序兼容,由于通常使用较新的软件,可禁用此选项以节省少量内存。
-
Sysctl syscall support
:如果不使用旧的访问
sys
文件系统的方式,可移除该支持以节省几 KB 空间。
-
Load all symbols for debugging/ksymoops
:取消选择此选项可移除崩溃转储中的符号信息,对于无法查看崩溃转储信息的嵌入式系统,可大幅减少内核大小(超过 200KB)。
-
Support for hot-pluggable devices
:如果设备没有 USB 或已为已知设备创建
/dev
条目,可禁用此功能。
-
Enable support for printk
:禁用此选项会使
printk
变成一个基本不做任何事情的宏,可减少内核大小,但会失去有价值的诊断数据。对于没有用户界面且无法存储
printk
数据的设备,可移除该支持;对于有存储和访问方式的设备,可仅在最终生产版本中禁用。
-
BUG() support
:对于大多数嵌入式系统,可移除该支持,因为处理此信息的机会有限。
-
Enable ELF core dumps
:如果系统没有空间存储核心转储文件或无法轻松获取该文件,可禁用此选项以节省几 KB 空间。
-
Enable full-sized data structures for core
:取消选择此选项可通过减少内核中的各种数据结构和缓冲区来降低运行时的内存使用,有助于控制 Linux 运行时所需的 RAM 量。
-
Enable futex support
:移除该选项会取消对快速用户空间互斥锁的支持,由于大多数应用程序依赖此代码,不建议移除。
-
Enable eventpoll support
:大多数程序不使用此功能,可从内核中移除。
-
Enable signalfd() system call
:除非确定系统不使用此功能,否则应保持启用,因为它正迅速成为程序处理信号的常见方式。
-
Enable timerfd() system call
:该功能占用空间小,应保持启用。
-
Enable eventfd() system call
:这是一种低资源的通知系统,除非确定不使用,否则应保持启用。
-
Use full shmem filesystem
:对于没有交换内存的嵌入式系统,禁用此代码可将用于管理内存资源的内部文件系统替换为更简单的基于 RAM 的解决方案,节省几 KB 内存。
-
Enable AIO support
:如果嵌入式系统不需要控制 I/O 以避免系统阻塞,可禁用异步 I/O 支持。
综上所述,通过合理运用这些方法,可以有效减少嵌入式系统代码和内核的大小,提高系统的资源利用率。在实际操作中,需要根据具体的系统需求和硬件环境进行选择和调整。
嵌入式系统代码与内核大小优化指南
优化效果评估与持续改进
在完成上述代码和内核的优化操作后,需要对优化效果进行评估,以确定是否达到了预期的资源节省目标。可以通过对比优化前后的代码和内核大小,以及系统运行时的内存占用情况来进行评估。以下是具体的评估步骤和持续改进建议:
评估步骤
-
记录优化前数据
:在进行任何优化操作之前,记录代码的大小、内核的大小以及系统运行时的内存占用情况。可以使用以下命令来获取相关信息:
- 查看代码大小:
$ du -sh <your program>
- 查看内核大小:
$ du -sh <kernel image>
- 查看系统内存占用:
$ free -h
-
执行优化操作
:按照前面介绍的方法,如使用
-Os选项编译代码、进行静态链接、去除调试信息、移除不必要的内核驱动和功能等。 - 记录优化后数据 :优化完成后,再次使用上述命令记录代码的大小、内核的大小以及系统运行时的内存占用情况。
- 对比分析 :将优化前后的数据进行对比,计算出代码和内核大小的减少量以及内存占用的变化情况。例如,如果优化前代码大小为 10MB,优化后为 8MB,则代码大小减少了 2MB。
持续改进建议
- 定期回顾优化策略 :随着系统功能的不断增加和硬件环境的变化,之前的优化策略可能不再适用。因此,需要定期回顾优化策略,根据实际情况进行调整。
- 关注新的优化技术 :技术在不断发展,新的优化技术和工具也在不断涌现。关注行业动态,学习和应用新的优化技术,可以进一步提高系统的资源利用率。
- 进行性能测试 :除了关注代码和内核的大小,还需要关注系统的性能。在进行优化操作后,进行性能测试,确保优化不会对系统的性能产生负面影响。
总结与流程图
通过以上介绍,我们了解了多种减少嵌入式系统代码和内核大小的方法,包括查找所需共享库、编译优化、静态链接、去除调试信息、移除不必要的内核驱动和功能等。这些方法可以根据具体的系统需求和硬件环境进行选择和组合使用。
下面是一个总结优化过程的流程图:
graph TD;
A[开始] --> B[查找所需共享库];
B --> C[编译优化];
C --> D[静态链接];
D --> E[去除调试信息];
E --> F[减少内核大小];
F --> G[评估优化效果];
G --> H{是否达到目标};
H -- 是 --> I[结束];
H -- 否 --> C;
在实际应用中,我们可以根据这个流程图来指导整个优化过程。首先,查找所需的共享库并进行合理的处理;然后,通过编译优化、静态链接和去除调试信息等方法减少代码的大小;接着,移除不必要的内核驱动和功能来减少内核的大小;最后,对优化效果进行评估,如果没有达到预期目标,则继续进行优化。
总之,嵌入式系统的资源优化是一个持续的过程,需要我们不断地探索和实践。通过合理运用各种优化方法,可以有效提高系统的资源利用率,使嵌入式系统在有限的资源下发挥出最大的性能。希望本文介绍的方法和建议能够对大家在嵌入式系统开发中的资源优化工作有所帮助。
超级会员免费看
800

被折叠的 条评论
为什么被折叠?



