嵌入式linux技术到产品的一些考量

嵌入式linux技术到产品的一些考量

  俗话说“学以致用”,“学”的最终目的是“用”, 特别是技术,如果所学不能运用到实际产品中,那么学习也失去了意义。 从“学”到“用”还是有一段距离要走的,本文章讨论一下嵌入式linux技术到产品中的一些考量。
  ps: 一本书(zlg)上的,觉得写的不错,就整理了一下摘过来了。


1. 做最适合的系统

  贴合硬件, 量身定制。《登徒子好色赋》中用“增之一分则太长,减之一分则太短;著粉则太白,施朱则太赤” 形容一个女子恰到好处的美。 做产品又何尝不是如此? 根据系统软硬件需求,对系统进行精简,不但可以减小系统体积,还能带来系统启动加快,增强系统稳定性。 做出贴合硬件的系统,通常包含两方面:内核和文件系统,在特殊情况下,还会对Bootloader 进行贴合调整。
  从评估板到实际产品,往往需要进行裁剪。 因为通常的评估板系统,出于通用性考虑,往往会集成较多功能软件,而实际产品通常功能明确,不需要冗余的软件。 例如目前很多处理器都带了音频接口,评估板厂商在设计评估板的时候,往往会实现音频功能,如果某个产品最终无需音频功能,则需要将音频裁掉。一方面需要在内核中去掉音频相关驱动,另一方面需要在文件系统中去除音频相关的库、软件以及音频文件。
  从评估板到实际产品,还有可能需要增加功能。 评估板功能较多,仅仅是针对处理器本身外设而言,评估板会尽可能的把处理器外设资源引出,但也不可能涵盖选用这个处理器的产品的全部功能。 例如一个产品可能需要进行复杂算法处理,而 ARM 本身没有这么强的运算能力,通常可能会通过本地并行总线外扩一个 DSP 来实现。在驱动层需要编写一个驱动,实现 ARM 和 DSP 之间的相互通信和数据传输;在应用层需要结合驱动编写程序, 实现最终目的。
  从评估板到实际产品, 也有可能需要进行功能修改。 产品和评估板往往有巨大差异,进行功能修改也是不可避免的。可能是引脚功能复用上的修改,这种情况比较常见,例如某对引脚的功能可在 GPIO/CAN/UART 之间切换,评估板作为 CAN 功能,而某个产品需要作为UART 使用,这就需要在内核中对这对引脚进行复用修改。也有可能是驱动功能上的修改和完善,这类情况也不少,评估板的驱动有的仅仅完成了基本功能测试,或者隐含一些 BUG,在实际产品中,需要完善这些功能,修复已知 BUG。


2. 做可靠的系统

  系统可靠性是一个软硬件紧密相关的综合课题, 仅仅硬件或者软件单方面都不可能彻底解决可靠性问题,只有两方面协同处理才能做到。不过在这里不展开硬件方面的讨论,假定硬件已经是满足可靠性要求,在此基础上讨论系统软件和可靠性处理。目前绝大部分处理器都支持直接从 NAND 启动, 由于 NAND 容量大、 价格低,所以在产品设计中很受青睐,通常将系统启动、存储介质都设计为 NAND。 但由于 NAND 本身的特性,在使用过程中不可避免出现位反转情况。尽管 ECC 能纠正一定范围内的位反转, 但也会出现 ECC 无法解决的意外。 解决这类问题, 系统层面可从两个方面入手:分区域保护和双备份。

2.1 分区域保护

  对 NAND 进行合理规划,如 Bootloader、内核、文件系统等不同分区,并对各不同分区设置不同的 mask_flags。对于 Bootloader、内核分区,可以设置 mask_flags 为MTD_WRITEABLE,禁止该 MTD 分区的可写属性,实现这些分区只读, 防止在系统中意外误操作这些分区。
  设置了 mask_flags 的分区,在进入系统后,将无法用 flash_erase 命令擦除该分区, 也无法改写该分区的数据, 从而保护该分区的数据。

[root@zlg ~]# flash_erase /dev/mtd0 0 0
flash_erase: error!: /dev/mtd0
error 13 (Permission denied)

  对于文件系统分区,则不能通过 mask_flags 来设置只读实现分区保护。不过可以通过在内核 cmdline 中增加 ro 选项实现只读系统保护。例如:

[root@xxx ~]# cat /proc/cmdline
ubi.mtd=5 root=ubi0:rootfs rootfstype=ubifs ro console=ttyO0,115200n8 mem=256M

  加了 ro 启动的系统都是只读的,对文件系统进行任何改写都会提示“Read-only file system”,例如:

[root@xxx ~]# touch a
touch: a: Read-only file system

  对这样的只读系统,如果要进行系统修改,可用 mount 命令经系统重新以可写方式挂载:

[root@xxx ~]# mount -o remount,rw /
[root@xxx ~]# touch a

  操作完毕,再用 mount 命令将系统挂载为只读,实现系统保护:

[root@xxx ~]# mount -o remount,ro /
[root@xxx ~]# touch a
touch: a: Read-only file system

  如果一个系统全部都是只读属性,在实际应用中会很不友好,如果用户需要存储数据,建议单独分出一个 MTD 分区为好,并将这个 MTD 分区单独挂载到文件系统的某个目录,如/opt 目录。单独挂载了 MTD 分区的这个目录的读写权限是不受 cmdline 的 ro 所控制的,也就是说,即使通过 cmdline 实现了根文件系统只读, 但这个目录也是可写的。

2.2 双备份

  系统分区域保护能够在很大程度上避免软件意外操作可能引起的系统数据损坏,但不能解决 NAND 特性引起的数据损坏或者外部环境导致的数据损坏。 如果一旦出现这样的数据损坏,很有可能导致系统无法运行或者而运行出错。采用多重备份或者双备份方式可以在很大程度上避免系统起不来的意外。对 Bootloader 区域和内核区域,可以考虑采用多重备份和双备份的方式, 增强系统的抗损坏性。
  进行双备份的镜像文件, 需要进行校验,启动过程中对读取到的镜像进行校验,如果校验通过则运行该镜像,否则读取下一个备份镜像并校验, 如果备份镜像也被损坏导致校验失败,则系统挂起。


3. 做用户满意的系统

  通常来说,一个用户满意的系统,除了功能和性能这两方面的基本要求之外,其他的生产、测试、维护等方面,也都是需要考量的因素。
  易于生产的系统。一旦产品研发完成,将会交付工厂进行批量生产。除了进行硬件生产和测试之外,还需要烧写系统、安装应用程序、系统测试或者进行产品配置等。整个过程要经过多道工序,在产品设计特别是软件和系统方面,要多为批量生产考虑,加速批量生产的速度。 对于系统烧写,通常按照如下顺序来选择:贴片前烧录器烧写、贴片后脱机烧写、贴片后在线烧写。
  贴片前烧写速度最快, 适合于大批量生产, 需要借助烧录器完成。通过烧录器软件制作烧录工程,可以实现快速批量烧写。但由于 Linux系统各存储分区对 NAND 使用方式有差异,以及文件系统的特性,烧录器并不一定能够很好支持。在经过验证可实现的情况下,这种方式是最佳选择。
  贴片后脱机烧写, 可以脱离上位机, 但并不适用于所有处理器,只有支持 SD/TF 卡等可移动介质启动的处理器才可以实现。制作好启动卡和镜像,配合一定的外部引脚设置,实现上电后自动完成烧写,最终给出烧写完成指示。
  贴片后在线烧写,需要上位机配合,效率较低,但可能是最常用的一种方式了。 这种方式最终实现也与处理器密切相关,根据处理器的特性来实现烧写方式。可能需要将 Bootloader、内核和文件系统等分开烧写,也有可能需要用到多种方式, 如 JTAG、串口、 USB、网口等各种接口。但是对于一个特定的处理器,在设计量产工序的时候,尽量简化工序和操作步骤,能实现脚本自动化或者半自动化的一定要尽量实现,最大程度上减小人工干预。 例如 TeraTerm 终端软件就支持脚本功能,灵活使用该特性能够简化操作步骤并减少出错,从而提高生产效率。
  易于测试的系统。 测试包括两方面内容,一是出厂前产品功能测试,二是在用户端进行产品功能自测。用户端功能自测不是必须的,但出厂前产品功能测试是必不可少的。 出厂前功能测试又分两种,一种是单项功能测试,另一种是整机老化测试。 无论是单项功能测试还是老化测试,操作过程一定要简便,程序设计必须考虑自动化、批量测试的可能性, 因为这些程序的使用对象是工厂的测试人员,一定要做到用最简单的操作来完成测试,测试结果要能提供可读报表,便于进行测试结果汇总。
  易于维护的系统。维护系统包括两方面,一方面是对底层系统的维护,另一方面是对应用程序的维护。通常来说,底层系统维护相对较少,但也不排除会有这种需求。对于底层系统,主要是内核和驱动,对有可能需要进行后期升级的部分,建议编译成内核模块,进入系统后再进行加载,后续一旦需要维护或者升级,可以在应用中进行升级替换,而无需重烧内核。应用程序维护,可实现的手段较多,例如可以通过网络进行远程升级,或者通过 U 盘、SD/TF 卡等进行本地升级,甚至可以通过 2G/3G/4G、 ZigBee 进行无线升级等等。无论通过何种方式升级,都需要考虑升级失败能恢复旧版本程序或者能够进行再次升级,避免升级失败导致系统故障。


4. 快速启动

  系统启动时间的长短直接影响到产品的用户体验, 几乎所有产品开发者和用户都希望系统启动时间越短越好,甚至期望能够做到无操作系统环境的那样秒启动。 当然,引入操作系统后,对系统启动时间是会有很大影响的,但系统经过优化,也是可以做到比较快速启动的。对嵌入式 Linux 系统启动速度进行优化,可以从 Bootloader、内核以及文件系统等方面着手。

4.1 精简 Bootloader

  如果系统能支持内核 XIP,这样可以无需 Bootloader,可以省略不少时间。但是如果系统不支持内核 XIP,必须通过 Bootloader 引导,那就只能对 Bootloader 进行瘦身了。
 尽量精简 Bootloader 的功能, 把用不到的功能和命令去掉, 特别是开机硬件自检功能。如果所使用的 Bootloader 中有诸如对以太网、 USB 等外设进行自检功能,则可将这些功能去掉,这样能缩短启动时间。 就 U-Boot 而言, 如果启动过程中,无需以太网、 SD 卡、 USB等功能,可以在配置头文件中将这些外设定义去掉,同时去掉以太网、 SD、 USB 相关的命令。 这样会引入一个问题,去掉这些功能和命令后,对开发工作会带来影响,解决办法是调试阶段的 U-boot 和产品发布的 U-Boot 分开配置,编译成不同的镜像,在开发阶段用完整功能的 U-Boot,在产品发布阶段用快速启动版本的 U-Boot。
  另缩减 Boot 过程中的等待时间,例如 U-Boot 的 Autoboot 过程可以被中断,通常默认有 3 秒的等待时间。 在发布版本的 U-Boot 中,可将这个等待时间设置为 0。但是这会带来无法进入 U-Boot 命令行,如果确定后期无需再进入命令行进行操作那倒也无大碍。如果还希望能进入命令行,还有一个变通的办法就是将原来的等待时间单位从“秒”调整为“百毫秒”或者“十毫秒”,这样既能缩短等待时间,也能在必要的时候进入命令行。 修改的函数是<common/main.c>的 abortboot(int bootdelay)函数, 程序清单所示程序是将等待时间单位修改为“十毫秒”的范例。

static __inline__ int abortboot(int bootdelay)
{
	int abort = 0;
	#ifdef CONFIG_MENUPROMPT
		printf(CONFIG_MENUPROMPT);
	#else
		printf("Hit any key to stop autoboot: %2d ", bootdelay);
	#endif
	……
	while ((bootdelay > 0) && (!abort)) {
		int i;
		--bootdelay;
		/* 循环 100 次 */
		for (i=0; !abort && i<100; ++i) {
			if (tstc()) { /* we got a key press */
			abort = 1; /* don't auto boot */
			bootdelay = 0; /* no more delay */
			......
			}
			//udelay(10000); //原来为 udelay(10000), 10 毫秒, 总体单位是 100x10 毫秒, 即 1 秒
			udelay(100); //现在修改为 udelay(100),总体单位是 100x100 微秒,即 10 毫秒
		}
		printf("\b\b\b%2d ", bootdelay);
	}
	putc('\n');
}

  修改后,等待时间变得很短,出现“Hit any key to stop autoboot:”提示信息后再按键盘
按键通常来不及中断 Autoboot,就进不了命令行,需要提前按着键盘按键不松开,直到进入
命令行。

4.2 精简内核

  对于非 XIP 的内核,内核相关的时间有两方面: Bootloader 搬运内核的时间和内核自解压后以及运行时间。
非 XIP 内核被 Bootloader 加载到内存特定地址后才能运行,内核镜像文件的大小直接影响加载时间, 减小内核体积, 就能减少加载时间,从而缩短启动整体时间。
  内核被搬运到内存后,开始自解压并运行, 这段时间的长短与内核所包含的功能有直接关系,内核功能越复杂,所需要处理的事情越多,需要的时间也就越长。缩减这个阶段的时间,就要从功能上对内核进行精简和处理。
  精简内核的一般思路:一是把冗余不必要的功能去掉,二是把能推后加载的功能和驱动编译为模块,进入系统后再加载。
  裁剪冗余功能,需要紧贴硬件和产品需求进行裁剪。假如一个系统没有 CAN 功能,就把 CAN 驱动和相应的协议裁剪掉。如果产品无需用到 WiFi,则不要在内核中选中 WiFi 相关协议和驱动模块支持。 另外,对于开发完毕的产品,在内核中把各驱动模块的调试支持功能去掉,以及在 Kernel Hacking 中关闭各种系统调试功能。
  合理模块化, 只在内核中保留必要的模块和驱动,像处理器内置串口、 NAND 驱动、系统 RTC 等这样内核必备功能,通常建议静态编译在内核中。而对于网卡、 CAN、键盘、UVC 摄像头或者声卡等这些外设,或者一些功能模块例如 IPv6、 Wireless 相关的协议、以及 FAT 文件系统等,这些都可以编译为内核模块,在进入系统后再加载。
  精简内核, 还可以打开 Kernel Hacking 中的“Show timing information on printks”功能,这样在启动过程中能很清楚的知道哪个地方耗时长,可以有针对性的进行时间优化。
  在实际调试时稍微注意一下就可以发现,对于内核显示的启动所用时间,与实际掐表测试的时间是有差异的,这个问题不是内核打印的时间不准确,而是处在串口打印信息这个环节。嵌入式 Linux 调试串口波特率通常为 115200,也有 38400 这样更低波特率的,打印内核 LOG 信息会耗费大量时间,从而延长了实际启动时间。要避免这个问题,可以在内核启动参数中增加 quiet 参数, 将这些信息屏蔽。例如:

Kernel command line: ubi.mtd=5 root=ubi0:rootfs 
rootfstype=ubifs ro console=ttyO0,115200n8 mem=128M quiet 
4.3 精简根文件系统

  内核启动后期,会寻找并挂载根文件系统。 成功挂载根文件系统后,将启动根文件系统的 init 程序, 并完成一系列系统初始化和服务的启动,最终进入 Shell 或者用户程序,影响这段时间的长短有几方面因素:一方面是根文件系统镜像的格式;另一方面是根文件系统本身体积的大小,这直接关乎挂载时间;还有就是 init 程序以及根文件系统所启动的服务和程序的多少。
  根文件系统镜像格式的选择,须根据硬件系统所采用的存储介质类型来选择,可参考第11.2 小节,选择最佳匹配的文件系统镜像格式。 不同类型格式的文件系统镜像,体积会有差异,在挂载时间上也有很大差异,例如同样是 NAND FLASH, JFFS2 镜像挂载时间最长,且挂载时间与文件系统体积有直接关系,而 YAFFS2 时间较短, UBIFS 格式挂载时间最短。
  对某些格式的文件系统而言,文件系统体积的大小直接影响挂载时间,如 JFFS2 文件系统,但对于 YAFFS2 影响较小,对 UBIFS 文件系统则几乎无影响。 如果系统采用了 JFFS2文件系统, 就必须考虑文件系统的大小。
  init 进程是内核启动的第一个用户级进程, 它开始运行后,通过执行一些管理任务来结束引导进程,例如检查文件系统、清理临时目录、启动各种服务,为每个终端和虚拟控制台启动 getty 等。 System V init 是传统的 Linux init 程序,近年来逐渐淡出,在不同的发行版中分别被 Systemd init和 upstart所替代。在嵌入式 Linux 中使用更多的是 Busybox init,与 System V、 Systemd 以及 upstart 相比, Busybox init 启动过程会简单一些。
  在不影响系统功能的情况下,减少系统服务可以减少启动时间;如果一些系统服务在日后会用到, 可以推后启动,保证用户程序优先启动。
  特别说明一下设备文件生成和管理。 现在内核和系统支持生成动态设备节点,通常在用户态用 udev 或者 mdev 来产生和管理系统设备节点。 动态设备节点很方便动态的管理系统所支持的外设,特别是热插拔设备, 但动态生成设备节点会增加启动时间,如果对于一个外设相对固定的系统,可以不采用动态设备管理,而改用静态设别节点,能节省一些启动时间。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值