"); //-->
犹记得,在英特尔吊打AMD、苹果横踢安卓手机商的黄金年代里,每每这两家巨头推陈出新之际,总会被一众吃瓜群众冠上“挤牙膏”的标签冷嘲热讽一番。
似乎这哥俩明明可以在新品上惊艳绝伦,闪瞎望眼欲穿的粉丝们的双眼,却愣是气沉丹田,硬憋不放,控制节奏,就这么吊住胃口,反复搜刮大家的钱包。
这还真是,周瑜打黄盖,一个愿打,一个真的不愿挨!
因为,强大如英特尔,也得遵守它自家的摩尔定律,傲娇如苹果,也要考虑整个产业链的协同。不可能动不动甩出几条街,像乔老爷子那样拉风地搞个“One more thing”!
技术文的写手,其实也有类似的困扰。
如果掰开了、揉碎了把一个问题说透吧,文章篇幅就像勤快婆娘的裹脚布一样,虽然不臭但是太长。不说透吧,又不免给人虎头蛇尾的既视感。
比如,山人之前就写过一篇文章《海可枯石可烂 程序存储的空间也会变》,有一个有趣的评论说“是不是在转载文章的时候没有转全?”。
转载?巧了不是,这不是巧了吗不是?
山人最初有些哑然失笑,然后便是失语,接着,便张着空洞的大嘴巴迷思起来:
技术文章到底应该怎么写?要写出所有该写的细节,就不能限制字数,要考虑受众的阅读心理,就不能写得过长。
思来想去,斟酌再三,既然饭要一口一口吃,水要一口一口喝,文章呐,当然也要一篇一篇地写。一篇写不完就再写一篇呗,地也久,天也长,总能把该填的坑都填上。
就像鹿鼎记里,多伦色眯眯地对着建宁公主“表白”:韦大人的屁股,奴才也是愿意擦的。。。
所以,今天就书接上文,就如何验证程序数据的一致性,补充一些必要的细节。
1
要想思路不断档,必要的背景还是要讲一件。
首先,这个世界上从来都不存在永远不变的东西,如果有,那就是时间还不够长。
记得新冠疫情初初爆发后美股第一次熔断时,巴菲特老爷子出来喊话:韭菜们不要慌,我八十九了,到现在只见过两次熔断。
后来的事情也颇具喜剧效果,接下来两周内,美股接连四次熔断,手里攥着大把现金等着抄底的老爷子终于明白了:
我还是太年轻了。。。
其次,虽说人心不古,这年头,社会人各个变脸比翻书还要快,比起来,电子产品要可靠得多,但是,也保不齐会出现产品内部程度代码突变的意外。
之前那篇“海可枯石可烂...”的文章里,拿飞思卡尔的S19文件为例,说明了可以通过Bootloader提取S19文件中的程序代码,并给代码数据加上CRC32的验证信息。
“可以用一个PC端的软件把程序数据的地址、内容、长度解析出来。从PC端下载第一台产品的程序,Bootloader在解析并存储程序数据的过程中,同时对程序数据做CRC32运算,。。。程序存好了,完整性标识-CRC32运算结果也存好了。
在产品运行阶段,上电后,在应用程序的初始化阶段,读取存储在数据Flash中的校验信息,根据校验信息中的分段尺寸,读取各个分段中的Flash数据,进行CRC32校验,并将计算结果和校验信息中的CRC32校验值进行比对。。。”
这里面,有几个比较关键的细节,前文并没有给出详细的解释。
比如,MCU上电后验证程序的一致性时,要根据校验信息中的分段尺寸读取各个分段中的Flash数据。
为什么代码是分段的,而不是分布在一大段连续的地址空间中?
再比如,PC端软件怎么在S19文件中把程序数据的地址、内容、长度解析出来?
各位看官不要慌,当哩个当,当哩个当,且容小弟慢慢讲!
2
先说说程序代码为啥不是一大段连续的数据,却分成了几段。
在MCU内部程序Flash中,程序并不像一般人所理解的那样,一股脑地连续存放在一个有头有尾、地址线性递增的“大”段内,而是分成了好几个分段进行存储,专业的词汇叫SEGMENT。
真的,代码们不是小燕子和五阿哥、紫薇和尔康,即使海可枯,石可烂,也要手牵着手,肩并着肩!
从地址上来说,这几个SEGMENT可以连续,也可以不连续。另外,在一个SEGMENT内,代码数据可能填满了该分段的空间,也可能只占据该分段空间的一部分。
为啥会这样呢?把代码存在一大段(而不是多个分段)地址空间里,烧写、读取、校验不都是更方便吗?
讲真,山人也困惑于这个问题好久。现在的MCU技术完全可以做到一大段连续存储和读取,干嘛还分段呢?
岳不群当年练葵花宝典,抹泪挥刀自宫,那是为了练就绝世武功,可MCU如此自苦,所为何来?
带着探究的目的,秉持钻研的精神,山人小小研究了一番,并形成了自己的一点思考,不敢藏私,与诸君分享,当然可能也说的不对,敬请海涵哈~~
飞思卡尔MCU程序Flash的分段以16KB为单位,这也许是一个历史的原因。
忆往昔峥嵘岁月稠,恰同学少年,风华正茂,正熬鹰走狗,挥斥方遒,却得学那计算机系统的大部头。
当时,教科书上还是以8086为原型讲解的(暴露年龄了?没错,请叫我大叔!)。说为了让CPU能够寻址到1M,8086把1M地址分成16K个字节为一段,总共64个段。也许,飞思卡尔MCU里的分段机制和8086里面的分页机制是一脉相承的吧。
总之,这可能就是一个历史包袱,导致了目前这种别扭的局面。
外插一句话,现在人们为什么那么追捧RISC-V,主要原因还不是因为它没有那么多历史包袱,不会为了向下兼容几十年前的老古董而设计蹩脚的寻址方式、指令等吗。
好了,只要程序代码不小于16KB,那它肯定要分成几个SEGMENT进行存储的,所以,当您在第一台产品中通过Bootloader下载代码的时候,需要判断出代码存在了哪些分段,以及在每个分段里的代码尺寸。
还有,您千万不要以为,前几个段会秉承着“节约光荣、浪费可耻”的革命精神,存满数据,实际上,那种巧合基本上是不存在的。
因为,为了保证代码的执行效率,在存储代码时,不会把一个函数的代码拆开了放到两个段里面,所以,最有可能的情况就是,这个段还剩下100个字节的时候,如果要存储的下一个函数的长度超过了100个字节,这个段就收工大吉了,这个大函数得放到下一个段中。
3
行文至此,嵌入式软件开发人员对MCU端的疑问应该不多了,那么,PC端呢?
PC端软件怎么在S19文件中把程序数据的地址、内容、长度解析出来?
山人把当年搞bootloader时下载的代码S19文件里的两行数据拿出来,各位看官就明白了。
S12341200102040810204080003FE00000402000007FFF014000000880000008BFFF0240AE
S123414000000980000009BFFF034000000A8000000ABFFF044000000B8000000BBFFF05D9
S1表示程序数据,地址长度为2个字节(对应于4个ASCII字符),0x23表示该行剩下的数据长度。第一行里,0x4120为首地址,剩下的一直到0xAE之前都是程序数据,0xAE是校验和。第二行里,0x4140为首地址,剩下的一直到0xD9之前都是程序数据,0xD9是校验和。
在山人这个S19文件里,每一行的程序数据有0x20个字节,正好是0x23-2(地址长度)-1(校验和长度)。
具体代码实现就不用说了吧,这点小活简直就是张飞吃豆芽-小菜一碟。
写在最后
现在的IDE把应用程序编译、链接后会生成的程序数据文件并非固定一种,山人不才,知道的有hex、elf、bin、s19格式,也许还有其它的格式。
每一种格式都有自己的历史渊源和用武之地,山人今天分享的方法里用到的是S19文件格式,其实也适用于其它格式。
现在,自己动手写bootloader的人已经很少了,后浪们也大多不用知道,自然也不知道其内部的机理了。
也许有一天,山人会写一篇怀旧文,好好讲一讲bootloader,顺便讲一讲我们那个年代。
流水带走了光阴的故事,也带走了多愁善感的青春。。。
文:马步
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。