减少Linux的系统占用,为应用程序保留更多的资源,减少设备的硬件成本。“你能做到多小?”这个问题通常在嵌入式工程师开始他们的项目时问到。大多数时候,问这个问题的人是想减少RAM和Flash资源,减少设备的单位成本或能源需求。
因为Linux最初是为桌面或服务器系统设计的,默认情况下,它没有为尺寸大小进行过优化,无论如何,Linux正被越来越多地用在嵌入式设备中,要让Linux变小不是一件容易的事情,这里有几个减少系统占用内存的方法。
许多工程师是从减少内核大小开始的,这里有个容易着手的方法,本文将详细介绍如何减少内核的大小,主要通过移除那些在一个典型的嵌入式系统中用不到的代码。
在一个系统中根文件系统(RFS)可能是内存资源最大的消耗者。根文件系统包括了应用程序和c库使用到的基础结构代码。为RFS选择文件系统对最后的大小有非常大的影响,标准是ext3,从一个嵌入式工程师的眼光来看它的效率是非常低的,但那是另一篇文章的主题了。
实际中,如何减小?
即使最小的Linux发行版至少也有两部分:内核和根文件系统。有时,这些部件驻扎在同一个文件中,但是它们仍然分成不同的部件。从内核中移除特征几乎差不多的所有代码,这样一个系统很容易就减少到不到1MB大小。但是,许多用户选择Linux支持网络和不同设备,因此这不是一个现实的做法。
内核
Linux内核非常有趣,尽管在编译时它依赖GCC,但运行时它却不依赖。那些工程师将目光转向Linux初始化RAM磁盘(所谓的initrd),它是内核运行时的附属物。Initrd首先是由内核加载的,程序运行时询问系统需要载入什么模块以便支持设备,这样真实的根文件系统才能被加载。实际上,有两步加载过程,加载initrd后再加载真实的根文件系统,很少发现嵌入式系统中有根文件系统,因为这样在一个系统中会增加灵活性,对这个系统做改动要花费额外的空间或时间,嵌入式系统一般不需要灵活性。但本文稍后将讨论根文件系统。
可载入模块支持
内核载入模块是重新定位运行时内核连接到它自身的代码,典型的可载入模块的例子是允许从用户空间载入驱动到内核中(某些探测进程执行后),以及不关闭系统升级设备驱动。对于大多数嵌入式系统而言,一旦它们处于该范围之外,修改根文件系统要么不现实要么不可能。因此系统设计者直接将模块连接到内核中,移除那些可载入的模块,节约出来的空间对于内核来说是很多的,无论如何,程序管理可载入的模块(如insmod\rmmod\lsmod)和shell脚本载入它们不是必需的。
Linux-tiny补丁
Linux-tiny补丁集已经变得时有时无的项目了,最初是由Matt Mackall在主持。消费电子Linux论坛(CLEF)正在努力恢复这个项目,CLEF开发者WiKi已经给2.6.22.5内核(写本文时的最新版本)发布了补丁,同时,Linux-tiny项目的许多改变已经包括在主线内核中,尽管许多原始的Linux-tiny补丁已经集成到内核中,但实质上节约空间的补丁还没有集成进去。
如:
1、 Fine-grain printk support【细粒度printk支持】:用户可以控制什么文件可以使用printk。工程师将从不使用printk的文件大小中受益。
2、 Change CRC from calculation to use table lookup【改变CRC算法从计算到使用表查询】:以太网数据包需要一个CRC来校验数据包的准确性。这个CRC算法实现使用表查询替换了计算,节约了大约2K。
3、 Network tweaking【网络调整】:几个补丁包减少支持的网络协议,缓存大小和打开的套接字。许多嵌入式设备仅支持少量的协议,不需要有成百上千连接的服务。
4、 No panic reporting【无应急报告】:如果设备有三个状态灯,一系列的连接,用户不能看到,更少的影响,应急信息显示在一个不存在的终端上。如果设备发生内核应急失效,用户只需要重新启动设备即可。
5、 Reduction of inlining【减少直接插入】:直接插入是编译器将代码作为宏拷贝到它调用的每个位置,而不是产生一个函数调用。GCC默认将直接插入任何函数。通过抑制直接插入函数,代码运行稍微慢一点,因为编译器需要为调用和返回产生代码,得到的报酬是对象文件更小了。
Linux-tiny补丁发布成一个tar包,它可以一起应用,也可以一个一个单独应用。
内核调整建议补充
尽管Linux-tiny项目涉及到许多的方面,还有几个额外的配置改动可以减少Linux脚印。
1、 移除ext2/3支持,使用另外一个不同的文件系统:ext2/3文件系统通常比较大,大于32K,大部分工程师启用一个Flash文件系统,但是不禁用ext2/3,这样会浪费内存。
2、 移除对sysctl的支持,sysctl允许用户在运行时调整内核参数,在大部分嵌入式设备中,内核配置一旦定了就不需要改动了,使用这个特征会浪费1K。
3、 减少IPC选项,大多数系统没有SysV IPC特性(grep你的msget、msgct、msgsnd和msgrcv代码)和POSIX消息查询(grep mq_*[a-z])一样可以运行,移除它们可以节约18K。
4、 查看你改动的效果
Size命令报告一个对象文件中所有代码和数据的大小,这与ls命令的输入是不同的,ls报告的是在文件系统中的字节数大小。
例如,一个内核用armv51交叉编译器编译报告如下:
# armv5l-linux-size vmlinx
text    data     bss     dec     hex filename
2080300   99904   99312 2279516  22c85c vmlinux
Text小节是编译器发出的代码,data小节包括全局和其他使用初始静态符号的值,bss小节包括作为初始化的一部分被调零的静态数据。
虽然这个数据有启迪作用,但它没有展示系统消耗的内存部分,也无法通过查询vmlinux得出,但是查看连接在一起的文件,创建vmlinux是接下来最好的事情,为了得到这个信息,使用find命令在内核项目中定位built-in.o文件并计算大小:
# find . -name "built-in.o" | xargs armv5l-linux-size
↪--totals | sort -n -k4
这个命令的输出类似下面:
text    data     bss     dec     hex filename
189680   16224   33944  239848   3a8e8 ./kernel/built-in.o
257872   10056    5636  273564   42c9c ./net/ipv4/built-in.o
369396    9184   34824  413404   64edc ./fs/built-in.o
452116   15820   11632  479568   75150 ./net/built-in.o
484276   36744   14216  535236   82ac4 ./drivers/built-in.o
3110478  180000  159241 3449719  34a377 (TOTALS)
这个技术可以确切地指出占用了大量空间的代码,因此工程师可以首先移除这些代码,当这样做的时候,用户应该在创建之前执行编译清除动作,因为从内核中丢掉一个特征并不意味着那个之前编译好的对象文件将会被删除。
对于那些新添加到Linux内核中的东西,一个常见的问题是如何在内核配置程序中使用一个选项来联合一些built-in.o文件,这可以通过查看目录下的Makefile和Kconfig文件来实现,Makefile将包括象下面这样的一行:
obj-$(CONFIG_ATALK)     += p8022.o psnap.o
当用户设置了配置变量CONFIG_ATALK后它将生成右手边的文件。内核配置工具一般不会暴露下属配置变量名。要查出变量名之间的连接以及什么是可见的,在Kconfig中寻找没有CONFIG_前缀的变量名。
find . -name Kconfig -exec fgrep -H -C3 "config ATALK" {} \;
它将产生下面这样的输出:
./drivers/net/appletalk/Kconfig-#
./drivers/net/appletalk/Kconfig-# Appletalk driver configuration
./drivers/net/appletalk/Kconfig-#
./drivers/net/appletalk/Kconfig:config ATALK
./drivers/net/appletalk/Kconfig-  tristate "Appletalk protocol support"
./drivers/net/appletalk/Kconfig-        select LLC
./drivers/net/appletalk/Kconfig-        ---help---
还有一些事情需要做,因为用户需要在配置树中找到Appletalk协议支持,但是至少要清楚要查找的是什么。
根文件系统
对于许多新转到嵌入式Linux的工程师而言,他们认为在一个嵌入式设备上的根文件系统是一个外部概念,在Linux之前的嵌入式解决方案通过直接连接应用程序代码到内核中进行工作,因为Linux在内核和根文件系统之间有一个非常好的分割,最小化系统工作不是以缩小内核为终止点,在优化之前,根文件系统的大小与内核相比要小得多,无论如何,对于传统的Linux而言,系统部分有许多需要调整的部分,调整它们以减少系统的大小。
第一个需要回答的问题是“我究竟需不需要根文件系统?”简单地说,需要。在内核启动进程的结尾,它要查找根文件系统,由它再加载并运行第一个进程(通常是init,执行ps aux|head -2将告诉你在你系统上是什么进程),缺少根文件系统或初始化程序中的任何一个,内核将启动不了。
最小的根文件系统可以是一个文件:设备应用程序。假如这样,内核要指定一个文件,它是用户空间的第一个进程。只要那个进程在运行,系统就能工作,但是,如果这个程序基于任何原因退出了,内核将挂起,设备将需要重新启动。大多数空间有限的系统也会选择一个初始化程序,因为内务消耗小,初始化包括重新生成进程已死的代码,预防遇到应用程序崩溃时内核挂起。
大多数Linux系统非常复杂,包括很多可执行文件和常用共享库(包含运行在该设备上的应用程序共享库代码),对于这些文件系统,有很多选项存在用于减少RFS的大小。
改变C库
结合GCC,大多数用户都不想c库作为一个独立的实体,c语言包括32个关键字(give、take等),因此c程序的大多数字节来自这些标准库,权威的c库(glibc)被设计成高度兼容、国际化和跨平台支持,但它也比较大,有很多可选择的比较小的c库:
uClibc:这个项目是为没有内存管理单元(MMU-less)的处理器实现的c库。uClibc从开始创建就很小,它提供与c库相同的功能,但删除了如国际化、大字符集支持和二进制兼容性等特征。而且,uClibc配置工具给用户很大自由选择什么代码进入库,允许用户进一步减小大小。
uClibc++:对于那些使用c++的程序而言,这个库是在相同设计原则下的实现,支持标准c++库的大部分功能,工程师可以很容易地在板子上部署基于c++的应用程序,仅需要几兆字节空间。
Newlib:Newlib出自Red Hat,它有一个非常完整的数学库实现,有很多做控制和测量应用程序的用户喜欢它。
dietlibc:dietlibc是glibc的最佳代替品,它仍然是最小的分支,非常小,实际上只有70K,它删除了如动态链接库等特征。它对ARM和MIPS支持得很好。
使用一个交替的c库
Newlib和dietlibc通过提供一个包装的脚本调用编译器使用正确的参数集,忽略包括在编译器中的标准c库,使用一个替换的c库。uClibc有点不同,它需要工具链。
一旦你知道如何调用GCC,接下来就要为项目更新Makefile文件或建立脚本,大多数情况下,为项目在Makefile文件中使用下面这样一行:
CC=CROSS_COMPILE-gcc
假如这样,所有用户都需要从命令行运行make命令覆盖CC变量:
make CC=dietc
这将导致makefile为c编译器调用diet,尽管看起来很诱人,不要在这个宏中添加参数,用CFLAGS变量代替,例如:
make CC="gcc -Os"
应该是:
make CC=gcc CFLAGS="-Os"
这个很重要,因为某些规则将调用CC编译,参数将没有意义,并会产生错误。
回到根文件系统
在选择了c库后,所有在根文件系统中的代码需要用新的编译器编译,那样代码就可以使用最近的、更小的c库。在这一点上,值得对静态与共享库进行评估,对于目标究竟该选择哪个,如果设备将运行任意的代码,而且在部署时该代码是未知的,共享库是最好的选择。如:设备可能暴露一个API允许最终用户或专业工程师编写模块。假如这样,设备上的库应该为这些新特征实现提供最大的灵活性。
如果系统包括许多分隔的程序共享库也是最佳的选择,假如这样,共享代码的拷贝将比复制几个文件的相同代码更小。
当只有几个程序在使用时,最佳做法是为每种用途创建一个系统然后比较最后的大小,大多数情况下,较小的系统是没有共享库的,而且还有一个额外的受益,没有共享库的系统载入和启动程序时更快(因为没有连接这一步了),因此用户从效率角度来说也受益了。
总结
尽管没有象魔术一样的工具使系统变得更小,但也不缺少工具帮助使系统仅可能变得更小,而且,使Linux变小比减小内核大小更困难,根文件系统需要严格检查,因为这个部件比内核消耗得更多空间,本文主要叙述了可执行映像大小,减少运行中程序内存需求。
资源
1、Linux-tiny补丁: [url]www.selenic.com/linux-tiny[/url].一系列减少内核映射大小和运行时资源消耗的小补丁,这里面的许多补丁已经集成到内核中了。
2、GNU C库: [url]www.gnu.org/software/libc[/url]. GNU C标准库是c库的规范实现,可以在几乎所有平台运行,而且可以向后兼容。
3、uClibc: [url]www.uclibc.org[/url]. 更小的c库。
4、Newlib: sourceware.org/newlib. Red Hat的小C库。
5、dietlibc: [url]www.fefe.de/dietlibc[/url]. 最小的c库。