自从用上缓冲,通信不再破功
元朝末年,黄河泛滥,瘟疫流行,加之官僚腐败,渐至于民不聊生,老百姓为了活命只得揭竿而起。一时间,风云变幻,狼烟四起。在一众草莽英雄中,朱元璋采纳谋士朱升的九字真言:“深挖洞、广积粮、缓称王”,韬光养晦,积蓄力量,最终定鼎天下,平定四方。
本文引用地址://www.cazqn.com/article/202005/412945.htm再后来,中苏交恶时期,毛主席也振聋发聩地提出“深挖洞、广积粮、不称霸”的号召。
两朝太祖都是百年一遇的政治家、军事家,英雄所见略同,他们深知战略储备的力量,晓得唯有建设深厚的国家储备,才不至于阵亡于暗暗长夜而等不来那终将来临的天光。
就拿现在来说吧,我辈吃瓜群众有福气,可以好整以暇地看美国各个州的州长和特朗普在推特上骂来骂去地打嘴炮,其实这还不是因为美国联邦政府储备的医疗物资消耗殆尽,没有应急的储备造成的?
这些储备平时躺在仓库里睡大觉,还要付出长期维护和定期更换的高昂成本,但是它们在关键时刻能救命,疫情连三月,呼吸机抵万金呐!当需求高峰期来时,这些平时没啥用的储备可以赢得宝贵的时间,挽救脆弱的生命!
不过,立国不到三百年的美国人哪有这种历史感悟?
通过建立“空间”纵深,以应对时间密集型的突发需求,拿空间换时间,这就是“储备”的意义。
在程序员的世界里,这种储备叫做“缓冲”。今天,笔者就跟大家分享一个多年前发生在自己身上的案例,一个因为没有使用储备导致“数据丢失”的故事。
一
那正是天寒地冻的时节。
窗外狂风席卷,人影难觅,只有一面冷飕飕的月亮像瑶台的镜子,远远地挂在云端。那天,甚是高远,似穹庐,笼盖在一座小楼的上方。
那楼里只亮着一盏灯,亮灯的房间里只有一个人。
天高云淡,这个房间很孤单,这个人也很孤单。
这个人,就是在下!
月黑风高夜,正是捉虫时,没错,别看在下枯坐已久,但脑袋却在转个不停,在下正在对着电脑屏幕找bug!
当时,项目组正在做一款蓝牙娱乐设备,概而言之,这款设备插上U盘能放歌,接上蓝牙能免提,连上手机还能用音频流播放手机里的音乐。
现在说来这些都不算啥,但是在十余年前,那可算是个新概念。
这个设备的开发采用了双处理器方案,概而言之,一颗主控处理器处理人机接口,主要功能是以按键和显示屏的方式管理播放列表、通话和音乐播放,还有一颗蓝牙单芯片处理和手机的蓝牙通信,主要功能是把来电请求、通话状态发给主控处理器,同时接收来自主控的接听/挂断电话指令、音乐控制指令等。
主控处理器是个32位的单片机,蓝牙芯片选用CSR集成了蓝牙基带的单芯片,两者通过SCI接口进行通信。
在下不才,在里面负责蓝牙单芯片的开发。
二
如前所述,这两颗单片机以SCI接口进行通信。为了更顺口一些,还是说串口吧,只不过,大多数人印象中的串口是RS232,它主要用于设备间的通信,而笔者这里是同一个设备电路中的通信,没有走RS232电平,直接走TTL电平。
有串口通信就有通信协议,为了减轻主控开发人员的负担,在下自告奋勇地承担了协议的制定工作,却不成想,这倒成了我后来“背锅”的原因。
刚开始,我和负责主控芯片软件开发的李工一边喝着茶水磨牙拌嘴,一边“你打你的,我打我的”地加班加点,偶有串口联调通信,也是一切顺利,万事大吉,直到突如其来的数据丢失把这种岁月静好打成了满地狼藉。
那是将要起风的一天傍晚,同事们都各自归家,游戏人间烟火去了,独独剩下我和李工还在苦逼地写代码。
北岛说:如果你是一条船,漂泊就是你的命运,可别靠岸。领导说:如果你是一个工程师,加班就是你的命运,可别想着早下班。
想着那些早下班的同事,我也想到一句话:哪里有什么岁月静好,只不过我和李工在替你们负重前行!
办公室里万籁俱静,静的有些出奇,李工在一旁眉头紧锁,间或捏着下巴颏子向我投来深情的一瞥,直让人起鸡皮疙瘩。我在一旁也打起了嘀咕,“这厮有事?”
果然,李工带着斟酌的语气开口了,“天雷君,你定的串口通信协议莫不是有问题?感觉丢数据呢!”
原来,从今天下午起,李工做通话管理那部分程序时,有时候得不到正确顺序的数据。比方说,手机来电话了,用户直接在手机上接通了,蓝牙芯片这边本来会按顺序发过来“来电请求、接通等待、接通通话”,可是有的时候,没有“来电请求”就直接把“接通等待”这个报文发过来了。
而通话管理程序实际上是一个状态机,按照这几条报文跳转通话状态,现在报文次序不对,状态机自然就乱套了。
问题是显然的,原因似乎也是呼之欲出的。按李工的说法,是蓝牙链路的射频通信干扰了串口通信,导致报文里的数据出错,按照李工的提议,应该修改串口通信协议,每条报文应该连发两次,这样才能保证出错的几率大大降低。
那时我还年轻,惯于把别人甩的锅自觉地戴在自己头上。于是,我默默地收起内心的骄傲,采纳了他的意见。
不曾想,待我把报文发送改成连发两次后,问题出现的几率似乎更高了!!
于是,李工给我判了刑,要求大改通信协议,然后就拂袖回家了。
三
我尝遍世间冷暖炎凉,但依然愿在薄情的世界里深情地活着——这才是生活。
李工走后,偌大的办公室只剩下我一个人了。
月亮渐渐爬上树梢,寒风在窗外咆哮,月亮很孤单,我也很孤单。
我孤独地看着李工留给我的代码,在这萧杀的寂静里,捕捉着不知藏身何处的bug。
是的,“严于律人、宽以待己”的我可没觉得自己有哪里不对,‘通信协议有什么好改的?’我一边在鼻尖哼着气,一边看李工写的代码。
在李工的程序里,是通过中断接收串口发送的字节,然后在一个单独的任务解析报文的,解析出一条完整的报文后,再根据报文的含义向其它相应的任务里发消息。
我看了看李工为串口报文解析任务设定的优先级,居然是最低的!
其实,当时我也不知道该怎么设置任务的优先级,但是,联想到之前李工指控我通信协议有问题的情景,我就是觉得:怎么能够把这么‘重要’的任务设置成最低的优先级呢?
我一边在鼻孔哼着气,一边改了任务的优先级。三下五除二,再调试一把,还别说,果然好多了,测了好几遍,没问题!
既如此,我释然了。根本不是通信协议的事儿,而是报文一股脑地发过来时,主控这边处理报文不及时,导致“来电请求”报文还没解析完时,其中的数据就被破坏了。
于是,我恍然了。任务优先级设置不同,会导致这么明显的差异,这实际上也给我敲响了警钟:任务优先级不要随便动!
可是,我又再度默然了。我这么贸然地改了优先级,是不是可能会有很多其它功能出现莫名其妙的故障呢?于是,我一个激灵,默默地把优先级改了回去。
四
岁月如水,拨动着墙上的钟表指针,也拨动着我的心弦。
看着眼前剩下的半杯水,我生起了哲学的深思:“人生,过一天少一天,可是我们并不急着把这一辈子过完;水杯,喝一口少一口,可是我们并不想着一口气喝光。寿命、水杯都是一种缓冲型的容器,让我们可以安步当车,不疾不徐。”
那串口接收岂非也是如此?只要给它一个足够的缓冲,即便短时期内来了好几条报文,也不妨碍我们一条一条地处理了!
开了窍的我,带着兴奋的心情,在李工的代码里实现了一个环形缓冲器。拿出512个字节,开个数组,建立两个索引,分别标记读取位置和写入位置,这些索引到了512后自动归零。刚开始时,自然是写入索引大于读取索引,数据就这样鱼贯而入鱼贯而出。
慢慢地,只要写入索引的回零次数不大于(读取索引回零次数+1),即使出现了数据堆积,只要假以时间,也能准确无误地把数据消费完。而一旦出现写入索引回零次数大于读取索引回零次数+1,就表示数据出现了溢出,此时调大缓冲区的大小即可。
问题就这么顺利地解决了,自从用上了缓冲,后面的通信便没有再破过功。
至于李工,他看完被我改造过的代码,再次给我投来深情的一瞥。我回以嫣然一笑,对着他帅气的脸庞吐出两句诗:桃花潭水深千尺,不及我跟李工情呐!
评论