基于 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 ...
随机推荐
- C++字符串转整形、浮点型stof()、atoi()、strtol()等
头文件:#include<stdlib.h>string str;stof:float val=stof(str);atoi:int val=atoi(str);atol:long val ...
- C#算法设计排序篇之06-堆排序(附带动画演示程序)
堆排序(Heap Sort) 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/685 访问. 堆排序是指利用堆积树(堆)这 ...
- Netty 是如何支撑高性能网络通信的?
作为一个高性能的 NIO 通信框架,Netty 被广泛应用于大数据处理.互联网消息中间件.游戏和金融行业等.大多数应用场景对底层的通信框架都有很高的性能要求,作为综合性能最高的 NIO 框架 之一,N ...
- CSS动画实例:一颗躁动的心
在页面中放置一个类名为container的层作为盛放心心的容器,在该层中再定义一个名为heart的子层,HTML代码描述如下: <div class="container"& ...
- windows安装cnpm步骤
1.首先前往nodejs官网下载nodejs 2.安装nodejs 3.打开cmd,输入npm -v,检查npm是否安装成功.成功返回的话返回输出版本号 4.安装cnpm,输入npm install ...
- hge引擎示例教程cmake项目
hge引擎的示例代码在vs2017不能很好的运行,需要调不少东西,所以我将其重新整理成cmake的项目. 所有示例均在vs2017 msvc 下测试可以正常运行. 由于缺少libhgehelp.a所以 ...
- DHCPV6 vs DHCPV4
原文链接:https://blog.csdn.net/kdb_viewer/article/details/83310904 一.DHCPv4 vs DHCPv6 1. 相同点 使用DHCP clie ...
- Java实现树形结构的数据转Json格式
在项目中难免会用到树形结构,毕竟这是一种常用的组织架构.楼主这里整理了两个实现的版本,可以直接拿来使用,非常方便. 楼主没有单独建项目,直接在以前的一个Demo上实现的.第一种,看下面代码: pack ...
- latex:公式的序号
1.排序单位 在文类book或report中,行间公式是以章为排序单位的,即每一新章节开始,公式序号计数器equation就被清零.比如第1章第3个公式的序号是(1.3),第2章第1个公式的序号是(2 ...
- 牛客网PAT练习场-A+B和C
签到题 .题目地址:https://www.nowcoder.com/pat/6/problem/4077 #include<iostream> #include<cstdio> ...