Background:
瑞芯微在《RV1126/RV1109 低功耗/快速启动产品开发指南》中提到如下内容:
RV1126/RV1109内部有专⻔针对快速启动做了硬件优化设计,可以极⼤地降低快速启动时间,⽐如RV1126/RV1109芯⽚内置硬件解压缩模块-- decom,可以快速解压rootfs和kernel。
所以向测量瑞芯微rv1126内置硬件解压缩模块与gzip解压时间差。
1、 使用硬件解压缩
修改代码添加时间戳:
vi common/spl/spl.c
686 /* cleanup before jump to next stage */
687 void spl_cleanup_before_jump(struct spl_image_info *spl_image)
688 {
……
719 printf("Total: %ld.%ld ms\n\n", us / 1000, us % 1000);
720 printf("\n jump_tick: %ld.%ld ms\n\n", (ulong)(get_ticks() / 24UL) / 1000, (ulong)(get_ticks() / 24UL) % 1000);
722 }
启动过程中时间戳打印如下:
jump_tick: 159.272 ms
2、关闭解压缩功能
make menuconfig
Device Drivers --->
Multifunction device drivers --->
[ ] Enable misc decompress driver support
[ ] Enable misc decompress driver support in SPL
对比配置前后.config关于DECOMPRESS的差别:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lXidLekm-1650866686797)(resources/diff1.jpg)]
修改默认的配置文件:
vi rv1126_decom_dis_defconfig
108 # CONFIG_MISC_DECOMPRESS is not set
109 # CONFIG_SPL_MISC_DECOMPRESS is not set
启动过程中时间戳打印如下:(这种情况下内核无法启动)
jump_tick: 148.547 ms
3、只关闭硬件解压缩模块
make menuconfig
Device Drivers --->
Multifunction device drivers --->
[*] Enable misc decompress driver support
[*] Enable misc decompress driver support in SPL
[ ] Rockchip HardWare Decompress Support
[ ] Rockchip HardWare Decompress Support
对比配置前后.config关于DECOMPRESS的差别:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8kP37NY9-1650866686800)(resources/diff2.jpg)]
修改默认的配置文件:
vi rv1126_decom_dis_defconfig
108 CONFIG_MISC_DECOMPRESS=y
109 CONFIG_SPL_MISC_DECOMPRESS=y
……
112 # CONFIG_ROCKCHIP_HW_DECOMPRESS is not set
113 # CONFIG_SPL_ROCKCHIP_HW_DECOMPRESS is not set
启动过程中时间戳打印如下:(这种情况下内核无法启动)
jump_tick: 149.557 ms
4、关闭硬件解压缩模块,使能gzip解压
make menuconfig
Library routines --->
Compression Support --->
[*] Enable gzip decompression support for SPL build
对比配置前后.config关于GZIP的差别:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AengjWi7-1650866686800)(resources/diff3.jpg)]
修改默认的配置文件:
vi rv1126_decom_dis_defconfig
108 CONFIG_MISC_DECOMPRESS=y
109 CONFIG_SPL_MISC_DECOMPRESS=y
……
188 CONFIG_SPL_GZIP=y
正常这种情况内核应该可以启动,但并没有解压成功,主要报错日志如下:
Checking kernel 0x00608000 (gzip @0x04800000) ... sha256-skipped +
Error: inflate() returned -5
kernel: decompress error, ret=-1
Question:
为什么mcu和optee镜像可以解压成功,但内核不行?
Debug:
问了下度娘,找到如下解决办法:
61130 - PetaLinux - Uncompressing Kernel Image … Error: inflate() returned -5 Message During Boot (xilinx.com)
其中提到:
在提取压缩的Linux内核映像时,UBOOT将压缩映像作为缓冲区存储在DDR中。
如果压缩映像位于内存中,缓冲区和映像解压缩到的位置重叠,则引导过程将失败,因为内存空间冲突。
这可以通过增加UBOOT bootm命令用于提取过程的空间来解决。
主要内容就是修改如下位置:
#define CONFIG_SYS_BOOTM_LEN <size>
检索define CONFIG_SYS_BOOTM_LEN有如下内容:
grep -nr '#define CONFIG_SYS_BOOTM_LEN'
include/configs/rv1126_common.h:26:#define CONFIG_SYS_BOOTM_LEN (64 << 20)
但是修改为(128 << 20)之后发现不可行,但大概了解此问题可能是因为解压目的地址内存空间不足。
检索inflate()有如下内容:
grep -nr 'inflate'
lib/gunzip.c:312: printf("Error: inflate() returned %d\n", r);
lib/zlib/inflate.c:328:int ZEXPORT inflate(z_streamp strm, int flush)
对应上之前启动log中的报错:Error: inflate() returned -5
vi lib/gunzip.c
289 int zunzip(void *dst, int dstlen, unsigned char *src, unsigned long *lenp,
290 int stoponerr, int offset)
……
309 r = inflate(&s, Z_FINISH);
310 if (stoponerr == 1 && r != Z_STREAM_END &&
311 (s.avail_in == 0 || s.avail_out == 0 || r != Z_BUF_ERROR)) {
312 printf("Error: inflate() returned %d\n", r);
……
320 return err;
用ctags追代码发现Z_BUF_ERROR就是-5
#define Z_BUF_ERROR (-5)
百度一下 报错Z_BUF_ERROR具体原因:
(1条消息) Gzip uncompress错误代码Z_BUF_ERROR_林多的博客-CSDN博客
总结一下导致Z_BUF_ERROR的原因:
- source缓冲区长度为0(没有要解压的资源,却调用解压过程)。
- dest缓冲区(解压后的资源)长度不够用来解压。
更加印证无法解压内核的原因是目标空间内存不足的原因。
于是看inflate.c的实现,想知道Z_BUF_ERROR具体含义,发现如下内容:
So the only thing the flush parameter actually does is: when flush is set to Z_FINISH, inflate() cannot return Z_OK. Instead it will return Z_BUF_ERROR if it has not reached the end of the stream。
译文如下:
因此flush参数实际做的唯一事情是:当flush被设置为Z_FINISH时,inflation()不能返回Z_OK。相反,如果它还没有到达流的末尾,它将返回Z_BUF_ERROR。
所以认为应该是目的地址所分配的空间不足造成内核还没有被解压完才出错。
检索decompress error有如下内容:
grep -nr 'decompress error'
arch/arm/mach-rockchip/fit_misc.c:80: printf("%s: decompress error, ret=%d\n",
对应上之前启动log中的报错:kernel: decompress error, ret=-1
vi arch/arm/mach-rockchip/fit_misc.c
76 ret = gunzip((void *)(*load_addr), ALIGN(len, FIT_MAX_SPL_IMAGE_SZ),
77 (void *)(*src_addr), (void *)(&len));
78 #endif
79 if (ret) {
80 printf("%s: decompress error, ret=%d\n",
81 fdt_get_name(fit, node, NULL), ret);
82 return ret
83 }
发现打印之前调用了gunzip函数,检索gunzip,看此函数实现:
vi lib/gunzip.c
74 int gunzip(void *dst, int dstlen, unsigned char *src, unsigned long *lenp)
75 {
76 int offset = gzip_parse_header(src, *lenp);
77
78 printf("\n>>>[%s] %s: %d<<<\n", __FILE__, __func__, __LINE__);
79 printf(">>>dstlen:0x%x<<<\n", dstlen);
80 if (offset < 0)
81 return offset;
82
83 #if defined(CONFIG_MISC_DECOMPRESS) && !defined(CONFIG_SPL_BUILD)
84 int ret;
85
86 ret = misc_decompress_process((ulong)dst, (ulong)src, *lenp,
87 DECOM_GZIP, true, (u64 *)lenp);
88 if (!ret)
89 return 0;
90
91 printf("hw gunzip failed(%d), fallback to soft gunzip\n", ret);
92 #endif
93 return zunzip(dst, dstlen, src, lenp, 1, offset)
94 }
发现在最后调用了zunzip,发现其中有一个实参名字为dstlen,理解为目的长度。
于是在此打印此参数的值:
vi lib/gunzip.c
78 printf("\n>>>[%s] %s: %d<<<\n", __FILE__, __func__, __LINE__);
79 printf(">>>dstlen:0x%x<<<\n", dstlen);
此时的启动log:
## Checking mcu 0x00108000 (gzip @0x00208000) ... sha256+
>>>[lib/gunzip.c] gunzip: 78<<<
>>>dstlen:0x200000<<<
sha256+ OK
>>>mcu_tick<<<: 112.243 ms
## Checking optee 0x00040000 (gzip @0x00140000) ... sha256+
>>>[lib/gunzip.c] gunzip: 78<<<
>>>dstlen:0x200000<<<
sha256+ OK
## Checking fdt 0x01f00000 ... sha256-skipped + OK
## Checking kernel 0x00608000 (gzip @0x04800000) ... sha256-skipped +
>>>[lib/gunzip.c] gunzip: 78<<<
>>>dstlen:0x600000<<<
Error: inflate() returned -5
kernel: decompress error, ret=-1
通过log发现加载不同镜像时打印的值不同,于是找这个值的传递过程,发现在调用gunzip的处发现一些不太明白的地方。
76 ret = gunzip((void *)(*load_addr), ALIGN(len, FIT_MAX_SPL_IMAGE_SZ),
77 (void *)(*src_addr), (void *)(&len));
用ctags追代码看到如下内容:
40 #define ALIGN(x,a) __ALIGN_MASK((x),(typeof(x))(a)-1)
41 #define __ALIGN_MASK(x,mask) (((x)+(mask))&~(mask))
于是问了下度娘:
Linux中ALIGN宏的原理 (360doc.com)
内存对齐宏ALIGN_qwaszx523的博客-CSDN博客
以下是个人对ALIGN理解:
首先:align有对齐的意思,其次typeof不是C语言本身的关键词或运算符(sizeof是C标准定义的运算符),它是GCC的一个扩展,作用正如其字面意思,用某种已有东西(变量、函数等)的类型去定义新的变量类型。typeof()中可以是任何有类型的东西,变量就是其本身的类型,函数的返回值就是它自身的类型。typeof一般用于声明变量。在此处 (typeof(x))(a)-1,表明把a转化为x的类型,不考虑类型,上述代码可以简化为如下:
#define ALIGN(x,a) (((x)+(a)-1)&~(a-1))
上面的计算方法在内核代码中可以经常看到,下面给出几个例子:
(1) 当想向系统申请len字节的空间时, 想将该空间以size为倍数对齐, 而且要得到是比len大的值, 则使用ALIGN宏:
#define ALIGN(len,size) (((len)+(size)-1)&(~((size)-1)))
(2) 与页面对齐相关的宏
#define PAGE_SIZE 4096
#definePAGE_MASK (~(PAGE_SIZE-1))
#define PAGE_ALIGN(addr) -(((addr)+PAGE_SIZE-1)& PAGE_MASK)
(3) 与skb分配时对齐相关的宏
#define SKB_DATA_ALIGN(X) (((X) + (SMP_CACHE_BYTES -1)) & ~(SMP_CACHE_BYTES - 1))
以上操作都是在进行内存对齐,为什么需要内存对齐?
这是因为操作系统在数据读取的时候,其实并不是一个字节一个字节进行读取的,而是一段一段进行读取,我们假如是4bytes。假如我们要读取一个int,这个int是从第1位到第4位。那么读取的时候会发生什么事情呢?首先我们需要先读第一块数据,然后读取后三位的数据。接下来,读取第二块数据,然后只取第一位的数据。最后将两次的数据组合起来,就是我们想要的一个数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9NEvasCM-1650866686801)(resources/align.jpg)]
对于操作系统来说,这种处理数据的方式并不是特别地高效。我们都知道,在计算机领域,有一个特别有名的优化手段,就是空间换时间。我们通过内存对齐,直接跳过部分空的字节,然后一次性读取所需数据。
知道这么做的原因后便追寻这么做的原理,实现原理如下:
有如下两个整数
int a = 14;
int size = 8;
如果想让14变成8的整数倍应该怎么做?
8的倍数有8和16,而14则处在8和16之间,此时发现8和16二进制表示,其后三位都为0,而8 = 2^3,所以直接将14的后三位清0是不是就会变成8的倍数?而要达到这一目标,只要让14和下面这个数进行与运算就可以了:
11111111 11111111 11111111 11111000
而上面这个数实际就是 ~ (size - 1),我们将该数称为size的对齐掩码size_mask.
可这样做求出的是8是一个比14小的最大的8的倍数. 如果要求出比14大的最大的8的倍数是不是需要加上8就可以了?
14这个数好像是可以的,可是如果a本身就是8呢, 这样加8不就错了吗, 所以在14的基础上加上 (size – 1), 然后与size的对齐掩码(size_mask)进行与运算就可得出比14大的最小的8的倍数16。
这样, 我们可以定义下面的宏, 用于计算一个数a以size为倍数的前后两个值:
#define alignment_down(a, size) (a & (~(size-1)) )
#define alignment_up(a, size) ((a+size-1) & (~ (size-1)))
例如:
a=0, size=8, 则alignment_down(a,size)=0, alignment_up(a,size)=0.
a=6, size=8, 则alignment_down(a,size)=0, alignment_up(a,size)=8.
a=8, size=8, 则alignment_down(a,size)=8, alignment_up(a,size)=8.
a=14, size=8, 则alignment_down(a,size)=8, alignment_up(a,size)=16.
RootCause:
之前分析知是dstlen不够造成解压错误,而dsten的实参是ALIGN(len, FIT_MAX_SPL_IMAGE_SZ),
看代码得知,len是压缩后镜像的大小,FIT_MAX_SPL_IMAGE_SZ是要对齐的size。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dd2nVgJ0-1650866686804)(resources/kernel.jpg)]
查看压缩后kernel镜像大小为4.9M
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2tVv5poB-1650866686806)(resources/zboot.jpg)]
查看未压缩的kernel镜像大小为14M
## Checking kernel 0x00608000 (gzip @0x04800000) ... sha256-skipped +
>>>[lib/gunzip.c] gunzip: 78<<<
>>>dstlen:0x600000<<<
Error: inflate() returned -5
kernel: decompress error, ret=-1
打印看出dstlen只有6M空间,不足够放下解压后的kernel镜像,所以会解压失败。
Solution:
分配空间时改为16M字节对齐即可解决此问题:
vi arch/arm/mach-rockchip/fit_misc.c
79 ret = gunzip((void *)(*load_addr), ALIGN(len, FIT_MAX_SPL_IMAGE_SZ),
79 ret = gunzip((void *)(*load_addr), ALIGN(len, SZ_16M),
启动过程中时间戳打印如下:(这种情况下内核已经正常启动)
jump_tick: 1040.326 ms
5、软硬件解压缩时间对比
- 硬件解压:>>>jump_tick<<<: 159.272 ms
- 关闭硬件解压缩模块,不使能gzip解压:>>>jump_tick<<<: 149.557 ms
- gzip解压不加内核校验:>>>jump_tick<<<: 1040.326 ms
根据以上数据推测硬件解压缩所用时间大概为10ms,
软件解压缩时间为890ms,相差880ms左右。