免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 7402 | 回复: 1

djyos对嵌入式软件可靠性的理解 [复制链接]

论坛徽章:
0
发表于 2012-08-20 19:57 |显示全部楼层
许多嵌入式操作系统,在宣传自己的优异性时,不外乎上下文切换时间多么短、占用系统资源多么少、功能模块多么多、线程间通信手段多么丰富。谈到可移植性时,不外乎说自己的C语言率多么高,移植需要修改的代码行多么少,等等!这些方面,都非常重要,但并不全面,主要是从操作系统本身出发看问题。嵌入式操作系统的核心要素是可靠性、实时性和可移植性。要从嵌入式产品的整体效果出发,例如可靠性,不能光考虑操作系统的可靠性,更多地,应考虑由操作系统和应用程序构成的整个产品的可靠性。可移植性也一样,关注操作系统本身的可移植性重要,更应该关注应用程序可移植性。
1.     正确理解可靠性
谈到操作系统的可靠性,许多人会把焦点集中在操作系统本身是否可靠稳定,在运行中是否会出错等。这完全是一厢情愿、闭门造车的想法,就像一个汽车设计工程师,大谈特谈其设计的汽车本身如何可靠,开多少年都不会坏,却闭口不谈乘客是否安全。
在这里,我先给可靠性一个定义:
嵌入式操作系统的可靠性,表现为应用程序在该操作系统支持下可靠运行的能力。
注意,是用户产品可靠,这对通用操作系统和嵌入式操作系统的要求,是有区别的。通用操作系统中,运行许多应用程序,一个应用程序挂了,操作系统要保证其他应用程序不受影响,故操作系统本身可靠性非常重要。而嵌入式系统呢?嵌入式系统只有一个应用程序在跑,唯一的应用程序挂了,即使操作系统本身正常,又有什么意义呢?因此,嵌入式系统的可靠性,更多地强调支持应用程序可靠,这也是嵌入式操作系统的一个设计难点。
影响用户产品的可靠性因素,可以分解为:
1、  操作系统可靠。
2、  应用程序可靠。
3、  应用程序要正确地使用操作系统
以上三点,无论哪一个出现问题,都将导致用户的产品缺陷。
操作系统作为众多用户、众多产品的公共模块,理应得到更多的考核、验证、反馈、修正的机会,更加容易保证其正确性。
而用户的应用程序则不然,它的正确性,主要靠研发企业的测试来保证,用户的验证和反馈也很有限,是容易出问题的地方。产品缺陷,主要来自两个方面:
1、  应用程序本身业务逻辑存在bug
2、  应用程序没有正确地使用操作系统。
正是由于应用程序容易出问题,所以应该特别注意,操作系统作为基础软件,责无旁贷,绝对不能仅仅着眼于自己是否可靠。再强调一句,嵌入式系统中,如果应用程序挂了,操作系统再坚挺,都毫无意义。
对于操作系统来说,降低自身缺陷固然重要,但如果能在帮助应用程序减少错误方面做点工作,对提高用户应用程序可靠性,贡献更大。djyos主要从以下两个方面,帮助应用程序减少缺陷:
1、  降低程序员不正确地使用操作系统的机会。
2、  帮助用户减轻系统中各模块的耦合强度,以降解系统复杂度,减少应用程序本身存在的bug。这是系统工程师好项目经理最应该关注的。
对操作系统自身的可靠性,我们知道,操作系统调度器本身就是一个比较复杂的程序,直接控制计算机的方方面面。仔细测试能最大程度地排除bug,保证OS自身的可靠性,这是毋庸置疑的。但我要说,通过架构设计,使操作系统更加简洁,对减少bug更有意义。越简洁的东西,越可靠,这是永恒不变的真理。
1、  通过架构设计,降低临界区保护的复杂性,是djyos简洁可靠的典型案例。
2、  事件(djyos是事件调度)只有两种状态:就绪态和阻塞态,降解调度器的复杂性。
2.     简洁的调度器,提高操作系统可靠性
我们知道,越简洁的东西,越可靠,这是永恒不变的真理。作为操作系统的核心——调度器,更加如此。但简洁,必须以良好的系统架构设计为依据,不能以牺牲功能和性能为代价,更不能因此而降低用户使用的方便性。如果降低了使用方便性,不但会降低用户的使用体验,更重要的是,可能导致应用程序更多的缺陷,进而大大降低用户产品的可靠性,这与“简洁以提高可靠性”的目标,是背道而驰的。djyos的调度器,虽然更加简洁,但实现了更加强大的功能,同时更加易学易用。
传统操作系统中,线程是调度对象,也是程序员编程控制的对象。线程是计算机的实际运行单位,操作系统需要许多状态来获知线程的执行情况,才能有效进行调度。有些操作系统,状态多得让人眼花缭乱,而状态多了,状态机就会变得复杂,代码也随之变得复杂。而djyos是以事件为调度目标的,事件是现实世界的抽象,即人们需要计算机处理的事务,事务只有3中状态:
1、  处理事务的条件未成熟,处于阻塞态;
2、  事务本身准备好,等待计算机处理,属就绪态;
3、  事务正在被计算机处理,属运行态。
既然事务只有3种状态,那么,djyos的事件,自然也只有三种状态,状态少了,调度器自然更加简洁,代码也因此更加简单、更容易保证其可靠性。djyos能实现更加简洁的调度器,完全源于现实世界的特点,丝毫没有因此而降低系统的功能和性能。
Djyos的简洁,还体现在临界资源的保护上,我们知道,操作系统为应用程序提供服务,其本身有着大量的资源是临界资源,在操作时需要保护。
传统操作系统的中断系统设计并不合理,它使得操作系统的临界资源面临两级保护:关调度和关中断,且使应用程序容易出错。关于这点,本系列文章有专文论述,在此不赘述。
我们知道,在RTOS的实现中,临界资源保护方面,要极其小心,一不小心就会出错。而且,这种错误,是属于上帝扔骰子的游戏,刚好碰上并发访问,就出错,没碰上,就正常。出错了,破坏了数据,但如果不立即访问被破坏的数据,也表现不出来,只有天知道知道什么时候bug会找上们来。传统操作系统用关中断和关调度两级控制,在实现时,时时刻刻都要小心,每一个需要保护的地方,都要考虑关中断还是关调度,更甚的是,应用程序也一样,程序员小心翼翼地选择关中断还是关调度。这对有经验的程序员,也许没什么难的,但是,代码不总是由有经验的程序员写的。因此,临界资源保护方面,也是应该尽可能地简化。
djyos得益于中断系统的架构设计,使djyos的临界资源保护更加简洁。Djyos中,中断被分为异步信号和实时中断。异步信号表示不是十分紧急的中断,实时中断用于响应非常紧急的中断。实时中断不允许使用任何系统服务,而异步信号则允许使用所有系统服务。保护临界资源不再分两级,开关调度和开关异步信号是等同的,这使操作系统实现变得简单,应用程序也变得简单,不需要纠结究竟要关调度还是要关中断。同时,更加简单的系统,实现了更加强大的功能:
1、  异步信号中断服务函数允许使用所有系统服务,更加自由且不容易犯错。
2、  实时中断可达到裸跑的实时性。
3.     明确易用的中断编程,增加应用程序可靠性
本文不详细讲解djyos的中断系统实现原理,有兴趣的,可参考相关文档。
传统RTOS,中断处理在ISR中进行,有经验的程序员,只在ISR中处理少量必须立即完成的工作,然后,一般会通过信号量或者消息通知一个线程,由线程接着完成后续工作。但也有些初级程序员,在ISR中完成冗长的工作。传统RTOS,对ISR例程会有较大限制,许多操作系统服务是不允许在ISR中使用的,一不小心就会中招。文档完善的操作系统,会有一个长长的清单,说明哪些服务不允许在ISR中使用,哪些是允许的。文档不完善的呢?你只有自己看着办了。墨菲定律告诉我们,就算你文档很完善,只要存在ISR中误用系统服务的可能,就一定会有人这样做。
这样做的后果是什么呢?遗憾的是,没有人能准确描述,上帝也只能说“不可预知后果”;
这样的bug会在什么时候爆发呢?只有上帝知道;你不知道中断什么时候发生,只要不发生在“不该发生的时间”,系统就平安无事,天知道什么时候会中断会发生在“不该发生的时间”。
嵌入式产品中,什么问题是最可怕的呢?调试和测试不出来,会到达用户手上,且不可预知后果的问题,是最可怕的!那些运行数月,内存莫名其妙地被误改的问题,很可能跟它有关。
ISR中误用系统服务就属于这种“上帝才知道答案”的问题。
然而,墨菲先生对djyos却无可奈何,djyos把中断分为两类,一类叫异步信号,对应于并非紧急的中断,例如键盘,异步信号的响应函数中,允许调用全部系统服务;另一类叫实时中断,对应需要紧急处理的中断事件,在实时中断的处理函数中,则禁止使用所有的系统服务。看官,这里没有模棱两可的地方,要么就全部允许,要么就都不允许。
对于普通的中断(异步信号),是允许使用全部系统调用的,只要你养成良好编程习惯,检查系统调用的返回值,就不会出错。程序员根本没有机会犯在ISR中误用系统服务这种错误。
对于实时中断,则完全禁止使用系统服务,最明确的东西,也就是最不容易搞错的。不像传统操作系统,有些系统服务可用,有些不可用,很模糊,程序员很容易混淆。
3.1.   Djyos中断编程方法3.1.1.    实时中断编程模型
1、  编写ISR服务程序,像普通C函数一样编写即可。
u32 __uart1_int(ufast_t uart_int_line)
{
}
2、  使用下列语句序列设置中断:
    int_isr_connect(cn_int_line_uart1,__uart1_int);
    int_setto_real(cn_int_line_uart1);
    int_echo_line(cn_int_line_uart1);
    int_restore_real_line(cn_int_line_uart1);
第一句,把ISR函数跟中断线联系起来。
第二句,把相应中断设置成实时中断。
第三句,假响应一下,以免硬件的初始化状态影响。
第四句,使能中断
上述初始化过程,来自一个实际应用,该应用的mcu使用lpc1225,主频40Mhzflash速度20Mhz,实际运行速度在20Mhz~40Mhz之间。但要实现2.5Mbps baud的串口通信,如果不能再接收中断发生5uS内响应中断的话,必然丢数据。因此,该应用使用了djyos的实时中断机制,实测下来,中断响应时间小于1.5uS,效果很理想。
3.1.2.    异步信号的事件模式编程
这是djyos推荐的编程模型。
1、  编写事件处理函数。
void uart1_event(void)
{
         While1
         {
                //do something
                djy_wait_evtt_pop(djy_my_evtt_id(),1,cn_timeout_forever);
}
}
2、  登记事件类型,用于异步信号处理的事件,一般设为关联型事件。
uart1_evtt = djy_evtt_regist(enum_correlative,100,0,1, uart1_event,0x1000,NULL);
3、  按照下列指令序列初始化中断号:
    int_evtt_connect(cn_int_line_USART1,uart1_evtt);
    int_setto_asyn_signal(cn_int_line_USART1);
    int_echo_line(cn_int_line_USART1);
    int_restore_asyn_line(cn_int_line_USART1);
第一句,把事件类型与中断号联系起来。
第二句,把中断号设为异步信号。
第三句,假响应一下,以免硬件的初始化状态影响。
第四句,使能中断。
此后,只要发生uart中断,就会自动弹出uart1_evtt类型的事件。第一次弹出事件后,uart1_event将被执行,处理完相应的事务后,将会在djy_wait_evtt_pop( )函数处阻塞,直到下一次中断的到来。应用程序完全不需要写ISR程序,由于uart1_event是普通的事件处理函数,它跟普通事件处理函数一样,可以得到所有OS服务。
3.1.3.    异步信号的ISR模式编程
这种编程模型,与传统RTOS相似,不同的是,djyosISR可以调用全部系统服务,唯一的限制是,不能发生实际阻塞。比如,可以在ISR中调用malloc,但如果内存不足,在线程中调用的话,将会被阻塞,在ISR中调用的话,将返回NULL,不会被阻塞。只要养成良好的编程习惯,检查系统调用的返回值,就不会出错。
1、  编写ISR服务程序,像普通C函数一样编写即可。
u32 __uart1_int(ufast_t uart_int_line)
{
}
2、  使用下列语句序列设置中断:
    int_isr_connect(cn_int_line_uart1,__uart1_int);
    int_setto_asyn_signal(cn_int_line_uart1);
    int_echo_line(cn_int_line_uart1);
    int_restore_asyn_signal_line(cn_int_line_uart1);
第一句,把ISR函数跟中断线联系起来。
第二句,把相应中断设置成异步信号中断。
第三句,假响应一下,以免硬件的初始化状态影响。
第四句,使能中断
这个过程,跟实时中断非常相似,就是函数名不一样而已,但实现过程是有很大差别的。这种方式下,__uart1_int函数中是可以使用全部系统服务的。
3.1.4.    异步信号的ISR和事件混合模式编程
1、  编写事件处理函数。
void uart1_event(void)
{
         While1
         {
                //do something
                djy_wait_evtt_pop(djy_my_evtt_id(),1,cn_timeout_forever);
}
}
2、  登记事件类型,用于异步信号处理的事件,一般设为关联型事件。
uart1_evtt = djy_evtt_regist(enum_correlative,100,0,1, uart1_event,0x1000,NULL);
3、  编写ISR服务程序,像普通C函数一样编写即可。
u32 __uart1_int(ufast_t uart_int_line)
{
}
4、  按照下列指令序列初始化中断号:
    int_evtt_connect(cn_int_line_USART1,uart1_evtt);
    int_isr_connect(cn_int_line_uart1,__uart1_int);
    int_setto_asyn_signal(cn_int_line_USART1);
    int_echo_line(cn_int_line_USART1);
    int_restore_asyn_line(cn_int_line_USART1);
第一句,把事件类型与中断号联系起来。
第二句,把ISR函数与中断号联系起来。
第三句,把中断号设为异步信号。
第四句,假响应一下,以免硬件的初始化状态影响。
第五句,使能中断。
此后,只要发生uart中断,就会先调用__uart1_int函数,然后自动弹出uart1_evtt类型的事件。可以在__uart1_int( )函数中处理比较紧急的事务,比如copy硬件缓冲区,把其他事情留给uart1_event( )做。第一次弹出事件后,uart1_event将被执行,处理完相应的事务后,将会在djy_wait_evtt_pop( )函数处阻塞,直到下一次中断的到来。
这种玩法,功能有点类似于linux的中断上半部和下半部的功能,但比linux更简洁易用。
4.     降低模块间耦合,提高应用程序可靠性
另一个会严重降低应用程序可靠性的地方,就是模块间互相耦合,搅在一起。搅在一起的代码,就像一盘意大利面条,又杂又乱,如果代码写成这样的话,可靠性要高,上帝都会笑。
那么,避免意大利面条式的编码,全是应用程序的责任吗?OS作为基础软件,就一点责任都没有吗?这显然是不对的,只是大多数OS开发者没有意识到这点罢了,他们只讲究OS自身的高汇聚低耦合,忘记了OS的基本职责。消防部门的职责不仅仅是救火,更重要的是防火;OS的职责不仅仅是把出错的应用程序杀死,更重要的是帮助用户设计正确的应用程序。前面讲到,djyos的中断设计,可以消灭用户错误地调用系统服务的错误,但这只是帮助用户设计正确程序的初级阶段,帮助的只是程序员;那么,帮助用户,甚至是迫使用户编写高内聚、低耦合的模块,降解模块间的相互纠缠的关系,才是高级阶段,帮助的是系统工程师。
许多操作系统,描述自己功能强大时,总是喜滋滋地宣称自己的api多么丰富,程序员想干什么都可以支持。其实,api不是越多越好,甚至有些api是有害的,比如线程间(对应djyos的事件)互相直接控制的api,包括但不限于:
线程无条件挂起;
直接唤醒别的线程;
设置别的线程的优先级;
杀死别的线程;
等等……
这些功能,咋一看上去,是帮助程序员控制程序,但实质上是有害的,以“线程无条件挂起——直接唤醒别的线程”这对冤家来说吧。
写过C代码的都知道,goto是很受忌讳的语句,甚至有人根本不用,而提倡用ifforwhile来实现程序转移。那么,大家有没有想过,我们为什么这么忌讳goto呢?为什么偏爱ifforwhile呢?因为goto是很粗暴、不讲道理的转移,而ifforwhile呢,它清楚地说明了转移的原因和条件,有理有据!我们对函数内的goto耿耿于怀的同时,却无视长期存在的、组件之间、线程之间的大GOTO,甚至有些操作系统还为提供了丰富的大GOTO为卖点,说什么为应用程序提供丰富的服务。
什么是大GOTO呢?操作系统环境下编程,一个线程往往代表一个独立模块。绝大多数操作系统提供了无条件的线程休眠、暂停和唤醒功能,线程执行到任何时候,都可以无缘无故地倒地就睡,cpu将转而执行另一个就绪线程,就像用GOTO跳转到另一个线程一样,究竟跳到哪个线程呢?不知道!小goto还有个标号让你知道跳到哪里,大GOTO则连跳到哪里读不知道。线程睡着以后,自己是不会唤醒自己的了,但其他任何线程都可以无条件地把它唤醒,这又涉嫌模块间互相操作。因此,无条件的休眠、暂停和唤醒,使程序的执行过程跨模块地搅在一起,是模块间失去独立性,如果两个线程是由两个团队独立开发的,则属于两个团队间交叉跳转。
Djyos不提供模块间互操作功能,以降低模块间的耦合,这样,可能会给用户编写模块内的代码带来些许不便,但能使每个模块独立化。要知道,登10次泰山的难度,也比不上登一次珠峰;同样,把十个简单系统做可靠的难度,低于把一个复杂系统做可靠。Djyos刻意帮助用户降解可能存在的模块间耦合,不以“帮助用户少写几行代码”为目标。djyos不提供任何类似的操作,所有事件都是自己控制自己的行为,没有任何事件处理函数间交叉控制的可能。djyos并不是不让事件暂停、休眠和阻塞,只是像ifforwhile一样,要求程序员说明理由,为此,djyos提供了丰富的同步功能,程序员使用这些同步函数,明确告诉操作系统,自己停下来的理由,以及明确的唤醒条件,由操作系统检测到这些条件成立后,唤醒自己。这样,应用程序每个事件都自己控制自己行为,从而彻底避免了应用程序模块之间直接的交叉控制。也就是说,djyos中不存在跨事件(对应其他操作系统的线程)的大GOTO
一个线程,直接控制别的线程的优先级,也是很致命的,一个线程的优先级,属于其核心内部数据,在面向对象的思想中,是最应该隐藏的信息。而直接被别的模块控制,还谈什么模块独立性。Djyosdjy_set_prio函数,只能设置正在执行的事件(对应其他操作系统的线程)自己的优先级。
如何管理数据,是降低模块间耦合性的重要一环,有经验的系统工程师,拿到项目后,首先想到的,是如何组织和管理数据的问题。广义上,应用程序需要的一切数据都是资源,DJYOS操作系统系统提供一个简单易用的资源管理模块,用于管理用户比较关注的、对程序运行意义重大的资源,并提供了丰富的资源管理api函数。在DJYOS资源管理模块的协助下,应用程序管理数据将变得非常简单。更主要的是,如果用户坚持用资源管理器管理所有数据,将使不同的软件模块实现细节上获得惊人的一致性。一个有一定规模的软件,必然要划分为若干模块才能顺利开发,如果不加约束,各模块可能各自设计自己喜欢的方法管理各自的数据。这种不一致性将增加软件的复杂度,增加阅读和修改软件的难度,而且,这样不可避免地会增加软件规模,增加编程和测试的工作量。若要在开发团队内和团队间人员调配,新人将花较多的时间熟悉新模块,而如果各模块代码有一致性,就会降低人员调配时交接工作的难度。

论坛徽章:
0
发表于 2012-08-24 23:00 |显示全部楼层
顶一个lol
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP