基于openwrt架构 双分区dual image之实现自动切换分区的功能!

本文介绍了一种在嵌入式系统中实现双分区自动切换的机制,通过检查分区CRC值判断文件系统是否损坏,若当前分区损坏则自动切换至另一分区,确保系统稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我现在用的板子是Realtek产商的,flash是nornand的,有两个分区,分别为rootfs和rootfs_1
板子切换分区的功能是由环境变量priority_root=rootfs来控制的
通过fw_printenv来查看环境变量,如下:

root@:/# fw_printenv 
flash_type=0
hw_mac_addr=00:A0:11:22:33:44
.......省略
netmask=255.255.255.0
stderr=serial
stdin=serial
stdout=serial
priority_root=rootfs

当priority_root=rootfs_1时,再重启,板子就会从rootfs_1这个分区启动进入。

那么现在有个问题:这两个分区中有一个损坏了,如何自动切换到另外一个好的分区呢?
例如现在我的分区是rootfs,但是这个rootfs分区损坏了,进不去文件系统了,怎么切换到rootfs_1分区?

(注意,进不去文件系统证明系统已经无法正常工作!如果你的板子只有一个分区且分区又损坏了,那么已经GG了,重新烧code或者换另外一块板子继续工作吧。
因为一般分区损坏便是flash出问题,硬件出问题的机会更大,如果软件有问题,那么这版软件是不允许发布的!所以能发布的软件必须是OK的!)

那么针对上面提出的问题,提供两种解决方法!
一种是通过检查分区的CRC值来检验,二是初始化出两个分区信息

先看下效果图:

在这里插入图片描述
怕图片不清晰,附上信息

flash_type: 0
Hit any key to stop autoboot:  0 
=========support upload error code off============
Support Dual Image
Start to check crc...
current is rootfs.now get rootfs_crc     #可以看到现在的分区是rootfs

NAND read: device 1 offset 0x0, size 0x14c0000
 21757952 bytes read: OK
env_crc:[123456]		#这个env_crc的值是之前就获取出来保存好的,但这里我为了测试这个机制,便直接修改此值,让这个机制生效!
CRC for 84000000 ... 854bffff ==> ded0e7c7	#这个值ded0e7c7,是对当前的整个分区做crc校验的返回值,此值每次重启系统都会重新检验一次分区
#接下来的工作就是检查两个值是否一致,不一致那么证明系统挂掉了,于是切换分区
Check crc fail!try to switch to another image...
switch rootfs_1
check crc done
#至此,已经切换分区了,下面开始跑另外一个分区rootfs_1
priority_root=[rootfs_1]
prepare_start_rootfs_1
Check partition 2 image kernel&rootfs
Check partition 2 image kernel
check kernel cmd: nand device 1 && set mtdids nand1=nand1 && set mtdparts mtdparts=nand1:0x3f00000@0x4000000(fs),${msmparts} && ubi part fs && ubi read 0x84000000 kernel &&
Creating 1 MTD partitions on "nand1":

上面图片简单解释:文件系统损坏时,那么里面的东西就发生变化了,所以对文件系统再次做crc的检验时,得到的crc值和正常时是绝对不会一样的!
第一种方法便是根据crc校验值来判断系统是否损坏!
下面附上系统正常时的情况:
在这里插入图片描述

可以看到,正常时两个值都是ded0e7c7!
CRC值检验成功,那么便不会做任何事情,正常跑原来的rootfs_1分区!

说下需要用到的工具:

1、nanddump命令,#此命令用来讲所要校验的分区导出到文件
2、crcsum命令		#此命令用来校验分区信息

说下大概思路:
1、在系统升级的时候,将要升级的当前分区信息用nanddump命令导出到文件里面,然后使用crcsum命令对导出的分区镜像文件做一个crc的校验,返回值就是当前分区的校验值,然后保存到环境变量。
2、然后在重启系统的时候,在uboot层就做crc的校验,成功则运行,失败则切换到另外一个分区启动!
注意:大概说下系统启动顺序:内核启动->uboot启动->文件系统启动->服务程序配置。

机制流程

1、首先用nanddump命令将分区的信息导出来,目的是为了做crc的检验

//在upgrade/common.sh脚本中增加导出分区信息的机制
//我的是comman.sh脚本,该机制要加在系统升级的机制函数do_upgrade()里面,不清楚升级机制的,去搜下openwrt的do_upgrade()升级机制了解下
//系统升级的时候完成后会重启,这个机制就是在系统升级完之后,重启之前,对当前分区的crc校验完再进行重启。
do_upgrade() {
	echo  "Performing system upgrade..." > /dev/console
	if type 'platform_do_upgrade' >/dev/null 2>/dev/null; then
		platform_do_upgrade "$ARGV"
	else
		default_do_upgrade "$ARGV"
	fi

	sleep 1

	if [ "$SAVE_CONFIG" -eq 1 ] && type 'platform_copy_config' >/dev/null 2>/dev/null; then
		platform_copy_config
	fi
	##########上面的是openwrt自身的升级机制,到这里,系统已经完成升级了,下面开始对分区做CRC校验##################################
       echo "nanddump start" > /dev/console
        mtdname=`fw_printenv |grep priority_root|cut -d "=" -f2`	#判断当前的那个分区
        if [ "$mtdname" == "rootfs" ]; then
                echo "nanddump mtd11"
                nanddump /dev/mtd11 -a -f /tmp/image.bin -l 0x014c0000		#导出分区信息到tmp下的/image.bin文件中
                rootfs1_crc=`crcsum /tmp/image.bin | awk -F' '  '{print $1}'`	#做crc的校验,得到一个校验值
                fw_setenv rootfs1_crc $rootfs1_crc			#CRC的校验值保存到环境变量里面,现在CRC校验值已经保存到rootfs1_crc 变量里面了
        else
                echo "nanddump mtd10"
                nanddump /dev/mtd10 -a -f /tmp/image.bin -l 0x014c0000
                rootfs_crc=`crcsum /tmp/image.bin | awk -F' '  '{print $1}'`
                fw_setenv rootfs_crc $rootfs_crc
        fi

        fw_size=`crcsum /tmp/image.bin | awk -F' '  '{print $2}'`
        fw_setenv fw_size "0x$fw_size"

        rm /tmp/image.bin		#对分区做CRC的校验得到的值保存进环境变量之后,导出的文件便不需要了
        echo "nanddump completed" > /dev/console

该功能一句话概括就是:升级时nanddump出当前分区mtd的crc值,保存到环境变量

2、在uboot中一个檢查crc的機制:检查现在的crc值和env环境变量中的crc值并进行比较,crc值一样证明分区信息没有损坏,正常往下启动系统,失敗就切換到另外一个分區

我的项目uboot启动流程主要由cmd_bootqca.c控制
该文件在openwrt架构的build_dir/目录下的uboot相关目录里面
不清楚的先去了解下openwrt架构的uboot结构

//这个是我写的一个16进制的字符串转换成整形的函数,下面的check_crc函数有用到
//该函数只是为了我的这个功能而写的,可能不具有通用性
static int htoi(char s[])  
{  
    int i;  
    int n = 0;  
    if (s[0] == '0' && (s[1]=='x' || s[1]=='X'))  
        i = 2;  
    else  
        i = 0;  
    for (; (s[i] >= '0' && s[i] <= '9') || (s[i] >= 'a' && s[i] <= 'z') || (s[i] >='A' && s[i] <= 'Z');++i)  
    {   
        if (s[i] > '9')  
            n = 16 * n + (10 + s[i] - 'a');  
        else  
            n = 16 * n + (s[i] - '0');  
    }   
    return n;  
}  

static int check_crc(char* fw_size){
        ulong addr, length;
        ulong crc,num_crc;
        char *new_crc;
	char *addr_crc="0x84000000";
	//char *length_crc="0x14c0000";
	char runcmd[256];

	//我们之前已经把分区的crc检验值保存到环境变量了,现在把它获取出来
        printf("Start to check crc...\n");
        if(!strcmp(getenv("priority_root"), "rootfs"))
        {
		printf("current is rootfs.now get rootfs_crc\n");
                new_crc=getenv("rootfs_crc");
       		snprintf(runcmd, sizeof(runcmd),"nand read 0x84000000 0 %s",fw_size);
        	run_command(runcmd, 0); 
        }
        else
        {
		printf("current is rootfs_1.now get rootfs1_crc\n");
                new_crc=getenv("rootfs1_crc");
       		snprintf(runcmd, sizeof(runcmd),"nand read 0x84000000 0x4000000 %s",fw_size);
        	run_command(runcmd, 0);
        }

        //获取到crc值后,要做一个转换,因为获取到的是0x00000000这种16进制形式的,并且是字符串,我们需要转换为Int数据
        num_crc=htoi(new_crc);
        printf("env_crc:[%x]\n",num_crc);

	//下面获取当前的mtd分区的crc值,与环境变量里的crc值做比较
        addr = simple_strtoul (addr_crc, NULL, 16);
        length = simple_strtoul (fw_size, NULL, 16);
        crc = crc32_wd (0, (const uchar *) addr, length, CHUNKSZ_CRC32);

        printf ("CRC for %08lx ... %08lx ==> %08lx\n",
                        addr, addr + length - 1, crc);

        //check crc,如果两个相等,那么分区正常,不需要做什么
        if(crc == num_crc)
        {
                printf("Check crc pass!\n");
        }
        else
        {
				printf("Check crc fail!try to switch to another image...\n");
                if(!strcmp(getenv("priority_root"), "rootfs"))
                {
					if(getenv("rootfs_crc") != NULL)
					{
		                setenv("priority_root","rootfs_1");//所有的东西都是为了这句话,切换分区
		                printf("switch rootfs_1\n");
					}
					else
					{
						printf("rootfs_crc is null ! do nothing\n");
					}
               }
               else
               {
					if(getenv("rootfs1_crc") != NULL)
					{
                        	setenv("priority_root","rootfs");//所有的东西都是为了这句话,切换分区
                        	printf("switch rootfs\n");
					}
					else
					{
							printf("rootfs1_crc is null!do nothing\n");
					}
                }
        }
		printf("check crc done\n");

        return 0;
}
//这个函数就是我的项目uboot启动的检查机制入口,在这个函数开始的地方增加crc的检验机制
static int dual_image_start(void){
//........................
	if( getenv("fw_size") != NULL )
	{
		fw_size = getenv("fw_size");
		check_crc(fw_size);		
		//调用check CRC的检查机制,对当前正在运行的分区做crc的检验,然后和之前在环境变量里面保存好的crc值比较,看是否一致
	}
//..........................
}

我这个功能还需要做一个产测的接口,给工厂调用,这里不再细言。
注意事项:
项目不同,控制分区启动的方法就不同。
1、首先我的分区信息可以用nanddump命令来获取,如果你没有类似功能的命令或函数,那么该方法GG。
2、其次我的分区启动方式是由环境变量控制,如果你的启动方式不是这种控制方式,那么核心的切换分区动作需要变更。
3、该方法仅供参考,至于第二种方式需要详细的研究内核、uboot层、mtd系统以及系统启动流程这些,有兴趣自行研究吧☺。
建议:凡是涉及到uboot层的功能修改,最好先做测试,测试OK了再编译源码烧写镜像,否则uboot的损坏很容易让系统崩溃并且有无法修复的可能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值