基于 ramfs 的 OTA
背景
默认的 OTA
方案是基于 recovery
系统完成的。某个产品考虑产品形态和 flash
容量之后,计划去掉 recovery
系统(不考虑掉电安全),这就需要 OTA
方案能支持在只有单个系统的情况下完成升级动作。
默认的 recovery 系统方式
先介绍下默认使用的基于 recovery
系统的升级方式。
主系统由内核和根文件系统组成,分别保存在 flash
上的 kenrel
和 rootfs
分区。另外设置一个 recovery
分区,用于保存 recovery
系统。
此处的 recovery
系统,是一个带 initramfs
的内核,OTA
所需的应用和库都包含在 initramfs
中,因此启动到 recovery
系统之后,可不再依赖 flash
上的其他分区。
当需要进行系统升级时,先设置标志并重启,bootloader
检测到标志后会启动进入 recovery
系统。在 recovery
系统中,kernel
和 rootfs
分区都是处于未使用状态,直接将新的数据写入分区中即可。
更新完主系统之后,设置标志,重启到新的主系统即可。
没有 recovery 带来的问题
系统默认是将 flash
上的 rootfs
分区挂载为根文件系统,即系统运行时随时都可能会读写 rootfs
分区的数据。
若 OTA
不重启到 recovery
系统中,直接在正常系统中,即在 rootfs
分区仍被挂载为根文件系统的情况下,直接从块设备接口将数据写入 rootfs
分区,会有概率导致系统崩溃。
毕竟 OTA
应用和库本身都是放在 rootfs
中的,系统其他活跃进程也随时有可能对文件系统发出请求。
基于 initramfs 的解决方式
问题很明确,不能再挂载着rootfs的时候更新 rootfs
,那先考虑下,在挂载 rootfs
之前进行OTA
。
原本的内核是直接在内核初始化之后挂载 flash
上的 rootfs
分区作为根文件系统。现在 recovery
系统没了,但我们可以借鉴 recovery
系统的形式,为这个内核加上 initramfs
,在其中包含 OTA
所需的程序。
存在initramfs
的情况下,启动时内核会先挂载 initramfs
并执行 rdinit
指定的程序,到了 initramfs
的 init
脚本中,就可以判断是正常启动还是 OTA
了,若为正常启动则直接挂载 rootfs
分区,并进行根文件系统切换,后续的流程就跟原方案的主系统启动流程一致了。
若判断到正在进行 OTA
,则转而执行 OTA
流程,将新的数据写入 kernel
和 rootfs
分区,此时的环境跟原方案的 recovery
系统是一样的。
这种方案的优点是跟之前的流程较为类似,可复用一些成果。缺点是内核带上 initramfs
之后,不可避免地体积会变大,启动时间会变长。
关于标志传递
如何告知 initramfs
中的启动脚本,当前需要进行 OTA
呢?
方式一:通过自定义分区传递标志,在 flash
上的划定某个分区,例如划定一个 misc
分区,约定好标志,OTA
时更新其中的标志即可
方式二:通过 uboot
的 env
分区传递标志,uboot
原生提供了可以在 linux
用户空间读写 env
分区的应用,编译后使用 fw_printenv
和 fw_setenv
应用即可。详见 uboot
文档。
方式三:通过cmdline
传递标志,initramfs
可直接读取方式一和二设置的标志,也可以请 bootloader
约定好,由bootloader
检测到方式一和二设置的标志后,修改传递给 kernel
的 cmdline
方式四:通过芯片提供的寄存器传递标志。例如某些芯片的 RTC
模块中,会预留一些寄存器,供用户自定义使用,不掉电重启数据是不会丢的。
基于临时 ramfs 的解决方式
initramfs
是在挂载 rootfs
之前进行 OTA
,那有没有办法在挂载 rootfs
之后进行 OTA
呢?也是有的,先把 rootfs
分区卸载掉就可以了。
当然,直接 umount
是不行的,rootfs
分区现在还是尊贵的根文件系统,要想卸载,就得先切换到另一个根文件系统去。那另外的根文件系统从何而来呢?没有现成的,但可以造!
我们看看 openwrt
如何做的。切换根文件之前,先调用 kill_remaining
函数 kill
掉无关进程,这样可以让构造的 ramfs
只需包含 OTA
所需的应用和库。
kill_remaining() { # [ <signal> [ <loop> ] ]
local loop_limit=10
local sig="${1:-TERM}"
local loop="${2:-0}"
local run=true
local stat
local proc_ppid=$(cut -d' ' -f4 /proc/$$/stat)
echo -n "Sending $sig to remaining processes ... "
while $run; do
run=false
for stat in /proc/[0-9]*/stat; do
[ -f "$stat" ] || continue
local pid name state ppid rest
read pid name state ppid rest < $stat
name="${name#(}"; name="${name%)}"
# Skip PID1, our parent, ourself and our children
[ $pid -ne 1 -a $pid -ne $proc_ppid -a $pid -ne $$ -a $ppid -ne $$ ] || continue
local cmdline
read cmdline < /proc/$pid/cmdline
# Skip kernel threads
[ -n "$cmdline" ] || continue
echo -n "$name "
kill -$sig $pid 2>/dev/null
[ $loop -eq 1 ] && run=true
done
let loop_limit--
[ $loop_limit -eq 0 ] && {
echo
echo "Failed to kill all processes."
exit 1
}
done
echo
}
然后拷贝所需文件到 ram
中,构造出所需的 ramfs
switch_to_ramfs() {
# 将一些基础文件拷贝到ram中,构造ramfs
for binary in \
/bin/busybox /bin/ash /bin/sh /bin/mount /bin/umount \
pivot_root mount_root reboot sync kill sleep \
md5sum hexdump cat zcat bzcat dd tar \
ls basename find cp mv rm mkdir rmdir mknod touch chmod \
'[' printf wc grep awk sed cut \
mtd partx losetup mkfs.ext4 nandwrite flash_erase \
ubiupdatevol ubiattach ubiblock ubiformat \
ubidetach ubirsvol ubirmvol ubimkvol \
snapshot snapshot_tool \
# 除了上面列出来的,还可以将自定义的一些文件赋值到 $RAMFS_COPY_BIN 中,这样就无需改动官方的这份文件
$RAMFS_COPY_BIN
do
local file="$(which "$binary" 2>/dev/null)"
[ -n "$file" ] && install_bin "$file"
done
install_file /etc/resolv.conf /lib/*.sh /lib/functions/*.sh /lib/upgrade/*.sh /lib/upgrade/do_stage2 /usr/share/libubox/jshn.sh $RAMFS_COPY_DATA
[ -L "/lib64" ] && ln -s /lib $RAM_ROOT/lib64
接着进行关键的根文件系统切换
supivot $RAM_ROOT /mnt || {
echo "Failed to switch over to ramfs. Please reboot."
exit 1
}
切换后收个尾
#原本的根文件系统,变成挂载在 /mnt 下,现在可以卸载掉
/bin/mount -o remount,ro /mnt
/bin/umount -l /mnt
grep /overlay /proc/mounts > /dev/null && {
/bin/mount -o noatime,remount,ro /overlay
/bin/umount -l /overlay
}
}
最后在 ramfs
中调用真正的 OTA
命令
# Exec new shell from ramfs
exec /bin/busybox ash -c "$COMMAND"
这种做法的好处是,避免了 intiramfs
带来的体积和启动速度问题,且 OTA
过程只有一次重启。
更具体请参考 openwrt
官方的升级脚本(旧版本搜索run_ramfs
,新版本搜索 switch_to_ramfs
)。
毕竟是 shell
脚本,很容易便可以移植到其他的环境中使用的。
基于 ramfs 的 OTA的更多相关文章
- OTA升级详解(三)
君子知夫不全不粹之不足以为美也, 故诵数以贯之, 思索以通之, 为其人以处之, 除其害者以持养之: 出自荀子<劝学篇> 终于OTA的升级过程的详解来了,之前的两篇文章OTA升级详解(一)与 ...
- nrf52——DFU升级USB/UART升级方式详解(基于SDK开发例程)
摘要:在前面的nrf52--DFU升级OTA升级方式详解(基于SDK开发例程)一文中我测试了基于蓝牙的OTA,本文将开始基于UART和USB(USB_CDC_)进行升级测试. 整体升级流程: 整个过程 ...
- Linux设备管理(四)_从sysfs回到ktype
sysfs是一个基于ramfs的文件系统,在2.6内核开始引入,用来导出内核对象(kernel object)的数据.属性到用户空间.与同样用于查看内核数据的proc不同,sysfs只关心具有层次结构 ...
- 深入理解Linux字符设备驱动
文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...
- Linux Communication Mechanism Summarize
目录 . Linux通信机制分类简介 . 控制机制 0x1: 竞态条件 0x2: 临界区 . Inter-Process Communication (IPC) mechanisms: 进程间通信机制 ...
- linux概念之分区与文件系统
分区类型 [root@-shiyan dev]# fdisk /dev/sda WARNING: DOS-compatible mode is deprecated. It's strongly re ...
- sysfs - 用于导出内核对象(kobject)的文件系统
sysfs - _The_ filesystem for exporting kernel objects.sysfs - 用于导出内核对象(kobject)的文件系统Patrick Mochel & ...
- Docker背后的容器管理——Libcontainer深度解析
Libcontainer 是Docker中用于容器管理的包,它基于Go语言实现,通过管理namespaces.cgroups.capabilities以及文件系统来进行容器控制.你可以使用Libcon ...
- 使用 /sys 文件系统访问 Linux 内核
sysfs 与 /sys sysfs 文件系统总是被挂载在 /sys 挂载点上.虽然在较早期的2.6内核系统上并没有规定 sysfs 的标准挂载位置,可以把 sysfs 挂载在任何位置,但较近的2.6 ...
随机推荐
- golang 的 string包
前言 不做文字搬运工,多做思路整理 就是为了能速览标准库,只整理我自己看过的...... 注意!!!!!!!!!! 单词都是连着的,我是为了看着方便.理解方便才分开的 1.string 中文文档 [英 ...
- python 文件读写with open模式r,r+,w,w+,a,a+的区别
模式 可做操作 若文件不存在 是否覆盖 r 只能读 报错 - r+ 可读可写 报错 是 w 只能写 创建 是 w+ 可读可写 创建 是 a 只能写 创建 否,追加写 a+ 可读可写 创建 否,追加写
- 安装黑苹果MoJave记录
说实话安装黑苹果并不是一件很简单的事情,它既费时,而且还需要一定的计算机知识,最重要的是对于你来说可能黑苹果并没有Linux或者Windows好用. 好了废话不多说,开始吧. 1.硬件准备 并不是什么 ...
- 对‘example_app_new’未定义的引用
将头文件添加到add-executable()中 cmake_minimum_required(VERSION 3.12) project(SGTK3application2 C) set(CMAKE ...
- Linux与Windows的区别(后面了解后继续更新)
1.转自知乎上的@Haoyuan Xing得答案: Linux是一个以开发者为中心的操作系统,Windows是以消费者为中心的操作系统.这是最根本的区别,也是Linux相对于Windows的优势/劣势 ...
- Java引用类型之弱引用与幻像引用
这一篇将介绍弱引用和幻像引用. 1.WeakReference WeakReference也就是弱引用,弱引用和软引用类似,它是用来描述"非必须"的对象的,它的强度比软引用要更弱一 ...
- 浏览器自动化的一些体会4 webBrowser控件之零碎问题2
1. DocumentCompleted的多次执行问题 有的网页,会多次触发DocumentCompleted事件,由于它是异步的,不会阻塞,所以如果不恰当处理,会造成某些代码被错误地多次执行,造成意 ...
- 分享一个bootstrap的上一步,下一步的插件
效果图: 下载链接: https://www.daimabiji.com/index.php?m=content&c=down&a_k=ae0fI1gZyLT7oao56Pgu-dye ...
- Robot Framework(1)——环境搭建及安装
一.了解Robot Framework Robot Framework不是一个测试工具,准确来说,它是一个自动化测试框架,或者说它是一个自动化测试平台 特性如下: 1.支持关键字驱动.数据驱动和行为驱 ...
- 个人项目wc
github地址:https://github.com/YTFFBX/wc 1.题目描述 Word Count1. 实现一个简单而完整的软件工具(源程序特征统计程序).2. 进行单元测试.回归测试.效 ...