一方案,妄想
1.jtag 烧写。
jtag主要用于bootloader的烧写。但是同样也可以用于kernel 和rootfs的烧写。可是由于jtag是完全串行的数据输入,所以对于uboot 200k的空间大小,还算勉强可以接受。而对kernel 2M左右,rootfs几十M甚至几百M,变得不可接受。此时,jtag烧写工具对硬件完全是未知的,需要提供配置文档。
2.uboot (bootloader)
当使用jtag将uboot烧写到flash中以后就可以利用uboot进行烧写。目前,uboot支持的所有数据来源有:串口线,usb线,usb storage,网线。其中usb线被模拟成网络接口。
Uboot支持向nand flash,nor flash 烧写。
3.kernel
利用rootfs中提供的mtdtool,向底层的提供的接口中写回数据。这种方法同时也就使用了rootfs,所以要使用这种方法,必须制作rootfs。
二需求
对于一款产品来说,对内核的更新,对用户应该是透明的,也就是不需要用户进行过多,过于复杂的操作,同时最好提供有有好用户界面的烧写程序。
基于此原因,jtag的慢速,以及uboot的需要手动配置,都不是最佳的方案。那么利用内核进行烧写,成了产品级更新内核的最佳方案。
嵌入式系统不同于pc机之一是,其文件系统和资料多数存储于flash之上。flash上通常包含的数据有uboot,kernel,rootfs,initramfs ,以及其他一些分区。而更新内核,则目的是利用现有的内核,下载并烧写新的内核。同理,更新rootfs的原理跟更新内核相似。
----局限:由于在内核最终mount rootfs之后,会启动一些程序,这些程序会hold住一些flash资源,所以在烧写的时候就必须停止这些程序并释放flash资源。这样在烧写flash的时候就不会引起意外。
我们知道,内核在加载过程中,是完全载入内存的,也就是在内核运行过程中,内核并不hold flash资源。所以如果在内核中烧写flash,就不会干扰到flash。但是,在内核态下,并不提供策略层的烧写,只是提供了对于不同类型flash的共同的操作接口。因此最终的指定烧写文件,烧写类型,都是放在用户态的程序中完成的。
用户态的程序必然依赖于rootfs,而通常的rootfs又是基于flash的。所以要制作基于非flash的rootfs----linux内核支持的ramfs。
要使用ramfs(initramfs),则在编译kernel时,需要添加两项支持:
INITRD,BLOCK_RAM。这样在uboot启动内核时,才能有效加载ramfs。 Ramfs,从他的名称就可以得知,他是基于内存的rootfs,既可以作为最终的rootfs,可以作为mount 最后rootfs的桥梁。
三方法
现在读flash操作的局限解除了,还需要考虑的是更新的策略。
以下是我所想到的(有些可能是妄想,也或许有实现方法和实现价值)。
-
-
-
-
利用 ramfs 做单内核的自我更新。Flash 依次存储的是uboot,kernel,ramfs,rootfs。uboot启动后,向kernel的启动参数中添加一项 burn=kernel,或者burn=rootfs。当kernel启动并mount ramfs后,ramfs根据传入的burn参数,从usb storage 或者网络寻找指定名称的文件,并烧写。要实现系统自动的选择参数,必须修改uboot,让其可以知道硬件状态的改变。
-
利用ramfs做双内核的更新。flash依次存储的是uboot,kernel_burn,kernel,ramfs,rootfs。uboot启动后,根据硬件选择指定的kernel启动。如果选择kernel_burn,那么跟随启动的还有ramfs。在ramfs中,对kernel进行跟新。烧写的实现方法跟方法1的相同。
跟方法1比较,由于kernel_burn会永远受保护不会损坏,这种方法可以有效的解决内核被损坏,而无法自身修复的问题。但同时由于有额外的kernel_burn,所以会占用额外的空间。
-
(需要调查,但可行)利用ramfs做完全的在线热更新。
既然我们可以通过ramfs mount rootfs,那么是否可以通过rootfs mount ramfs呢?这样rootfs 中的所有进程停止,最后转到ramfs,也就释放了flash 资源了。问题是在ramfs mount rootfs的时候,使用的是busybox提供的switch_root(run_init),而这个程序必须是在进程号为0的进程中调用,意思是说,必须在init中调用。
而实际在rootfs运行中的init,在进入rootfs之后就不会继续派生进程了。这貌似无法进行下去了,但是是否可以利用命令telinit来更改init的启动级别,使其进入其他的级别中,在其他级别的init中mount rootfs为ramfs呢?如果可行的话,需要修改的地方有两处:
-
-
-
a. init
b. 其他级别的启动脚本(或者需要把这部分放入init)
这样,在其他级别运行后,就进入了ramfs。在ramfs中烧写后,重启系统,就进入了更新后的系统。
同前两个方法比较,这个方法并没有修改uboot,完全的软件层的操作。而uboot的修改又依赖于硬件支持,所以在这个层面上来看,这个方法有一定的通用性(不需要修改硬件和uboot)。另一个方面,由于修改了init,和运行级别,会对其他开发人员造成一定的障碍,这也会降低通用性。但优点也是明显的,他可以实现开机状态的内核更新,不需要关机重启,对用户体验来说,是极大的提升。
这种方法,还有许多细节地方要考虑,但是大致的方向,觉得是可行的。
四共同点
在ramfs中,影响更新内核时间的因素主要存在于两个方面。
1.内核的来源。针对不同的硬件,可以分为
a。串口
b。Usb host
c。Usb client
d。Net
显然从串口传入数据,将会变成整个过程的瓶颈,当然如果只支持串口,就没有办法了。
usbhost。usbhost可以插入usb的storage。在storage中,存放着要烧写的image。这样在ramfs中mount 这个usb storage之后,就可以获得数据了。
usbclient。Usbclient 意味着可以将嵌入式设备当做从设备链接到pc上。在linux 2.6的内核中,有关于linux 对usb client的驱动支持----gadget。这样在连接主机时,我们既可以把设备当做storage,亦可以当做网卡,还可以当做串口。如果当做storage,那么就可以从pc机把数据拷贝到设备。如果当做网卡,就跟下面所说的net一样了。
net。pc机跟设备之间,通过网络传输数据,上面的协议既可以是自定义的数据格式,也可以是通用的数据协议。底层可以简单的使用socket。
这样数据来源问题就解决了。相比而言,后面三种可以提供满意的速度,而基于网络的传输,又可以达到灵活的可控性。
结论:任何支持这四种接口之一的设备都可以进行内核更新。应该优先选择基于网络的方法。
2。数据写入时间。这主要依赖于设备提供的不同的存储介质。不同的flash类型,对应着不同的擦出时间,写入时间。所以对于经常更新的地方,应该尽量使用nand。
五细节
1.flash烧写。Mtd-tools 是linux最常用的flash烧写程序。浏览其源码。选择可用部分到自己的程序中。这样的编辑编译可以有效的减小最终程序的大小。在代码中发现linux内核,对用户层的flash操作已经提供了统一的操作接口。爽!
2.ramfs格式。目录linux 内核支持两种格式的initramfs。gz压缩格式和cpio格式。当前cpio格式的是主流。使用cpio工具可以方便的创建ramfs。
3.内核裁剪。kernel_burn在配置时,只需提供非常小的功能,这样最后的image就会非常的小。
4.uboot的内核参数。如果在kernel启动时,配置了initramfs,那么所有的后续工作都是由ramfs 处理,包括内核参数处理和rootfs 的mount。因此ramfs就有机会进行自己的其他工作。
5.ramfs制作。参考另一篇文章。
6.usbnet配置。参考另一篇文章。
7.busybox。参考另一篇文章。