分类:
2009-06-09 15:03:50
-------------------------------------------------
本文系本站原创,欢迎转载!
为了清晰的讲述djyos给嵌入式软件开发带来的跨越。
我们先将嵌入式软件开发的人员分为两种:
一是开发api模块的,一般多数是驱动,这一部分和硬件很相关,且往往是实现一个功能,比如串口读写,读温度模块,读转速模块,为了下文讲述方便,我将这些人员称为系统程序员。
二是开发应用的人,使用API的人,可以不了解硬件,只是根据需求利用这些api来实现这些需求,比如,当接收到一个a字符时,去读温度,这个需求就是使用串口读api函数,判断后,然后调用温度读api,这个过程他是不需要了解这些功能具体是如何实现的,这些人员我先称之为应用程序员。
同时为了简单明了,我们以一个例子来说djyos事件调度机制给嵌入式软件开发带来的跨越式变化。
有这样一个系统:
有一个键盘输入,温度传感器,电动机转速传感器,无线接收设备。
这个系统的要求是
1.当有键盘输入时,则读取温度值及电机转速,这个要求必须尽快完成。
2.无线接收设备接收数据,同时需要将无线接收到得数据要尽可能的处理更新。
1有实时性要求,2没有,只需尽可能就行了。
我们来说下这个实例的应用开发的各种实现:
一:.首先是裸机系统实现方式(无操作系统):
1. 键盘输入,因为实时性要求,大家肯定会用一个中断程序实现,读取温度就是一个api:get_temperature,。读取速度值及处理也为api,分别是get_speed;
2 由于这个没有实时性要求,就是用一般程序实现get_wireless_data,deal_wireless_data,
于是裸机程序的总架构如下:
int main(){
While(){
get_wireless_data();//时间长
deal_wireless_data();//时间长
}
}
Key_deal() Interrupt 0{
get_temperature();
get_speed();
};
用裸机程序和用操作系统的区别是什么呢?
主要是裸机程序不能并发执行,裸机程序中,当一个操作停止时,不能转去调用其他的程序。而在操作系统中,上面的get_adc和get_speed是可以实现并发运行的,因为他们是独立的,且大部分时间是在等待中度过,只要将他们分到不同的task,然后get_adc函数等待的时候能够去执行get_speed,那就可以达到宏观的并行了。同样对于get_wireless_data,deal_wireless_data,因get_wireless_data需要比较长的时间,且大部分时间是在等待设备发数据过来,这个时候其实是可以执行deal_wireless_data的,这个有点像流水线的感觉,可以同时处理get,deal两个操作。
2.下面就说一下,上面程序在RTOS中如何改造,我们以ucos为例。
正如上面所说的,能够并行的要分到不同的task中,以让他们并发执行。
对于get_wireless_data, deal_wireless_data好办,在main中分别创建一个对应的task就可以了,因为这些是必须要执行的,即
OSTaskCreate (get_wireless_data(void *)0, &Task0Stk[Task0StkLengh - 1], Task4Prio);
OSTaskCreate (deal_wireless_data,(void *)0, &Task1Stk[Task1StkLengh - 1], Task5Prio);
但是对于get_temperature, get_speed就不好办了,它是属于事件激发型的,也就是只有在有键盘输入时才需要执行,如果按照裸机程序的思路应该要这样改造。
Deal_key(){
OSTaskCreate (get_temperature,(void *)0, &Task2Stk[Task2StkLengh - 1], Task0Prio);
OSTaskCreate (get_speed,(void *)0, &Task3Stk[Task3StkLengh - 1], Task0Prio);
}
但是由于这个是每次有键盘输入都要创建task,开销比较大,一般采取一开始就创建对应的task,但是需要这些task一开始就是阻塞的,只有当deal_key task发送消息给他们,他们才开始执行。
于是改造为
main{
OSTaskCreate (get_wireless_data(void *)0, &Task0Stk[Task0StkLengh - 1], Task4Prio);
OSTaskCreate (deal_wireless_data,(void *)0, &Task1Stk[Task1StkLengh - 1], Task5Prio);
OSTaskCreate (get_temperature,(void *)0, &Task2tk[Task2StkLengh - 1], Task1Prio);
OSTaskCreate (get_speed,(void *)0, &Task3Stk[Task3StkLengh - 1], Task0Prio);
}
Key_deal(){
OSMboxPost(get_temperature_box)//这个邮箱get_speed函数要实现
OSMboxPost(get_speed_box);//这个邮箱get_speed函数要实现
}
从上可以看出,ucos实现方式,应用程序员首先需要分解指定任务,这个就需要应用程序员了解底层,了解api实现细节,同时由于应用程序员需要实现task之间的通信,这样就需要应用程序员对task的通信机制要了解,像消息队列和邮箱,比如消息队列,应用程序员就必须知道相关api的task使用的邮箱是什么,且这个通常由api通过OSMboxCreate实现,然后OSMboxAccept,这些都将应用程序员和系统程序员的耦合性加强了。同时这些任务的堆栈及优先级等东西完全由应用程序员决定是不合理,比如一般只有api的开发人员即系统程序员才对其api非常熟悉,也只有他知道这个api需要用多大stack,同时有些api的执行(task)必须是要很高优先级的,我们可以将这些优先级的定义权先交给系统程序员,而不是完全交给对api实现及硬件不了解的开发程序员,当然如果应用程序员觉得自己确实要自己定义优先级,也可以,但是至少要给系统程序员定义优先级的机会,在传统RTOS中都没有提供这些机制,那在传统RTOS像ucos中,这些矛盾如何解决呢。没有办法,只有嵌入式软件开发人员身兼多职,既是系统程序员,也是应用程序员,这也是为什么嵌入式软件开发需要比较高的门槛。
但是djyos改变了这一尴尬的处境,使得嵌入式程序员分职变得可能,即可以独立出一个应用程序员,这样门槛就降低了,这也将极大地提高嵌入式软件的开发速度。下面就来看下上面的例子在djyos中的实现。
三:DJYOS的实现方式:
既然要讲djyos,那就先来认识djyos的调度系统。
Djyos是以事件为调度单位的,每个事件属于一个事件类型,每个事件类型拥有虚拟机池,可以看成是线程池,当一个事件类型的事件发送了,就从中分配一个虚拟机给这个事件执行。事件分为mark事件和非mark类型事件,mark类型事件只是通知执行,不会产生多个执行线程。同时事件可以被同步,也可以和中断同步。事件类型注册时并没有分配资源,只有在事件弹出时才分配相关资源,有效的利用了资源。同时为了保证实时事件类型的实时性,实时事件类型注册时就分配了一定资源。
下面开始真正讲djyos,djyos可以很容易的解决上面提到那些问题,且上述裸机程序,进行很小的规范修改,就可以平稳的移植到djyos操作系统的平台上来。
比如上面裸机程序的改造如下:
get_wireless_data(); 对应为 pop_event(DJYOS_EVENT_GET_WIRELESS_DATA);
deal_wireless_data();//对应为pop_event(DJYOS_EVENT_DEAL_WIRELESS_DATA);
}
最后实现就是。
int main(){
djy_evtt_pop (DJYOS_EVENT_GET_WIRELESS_DATA);
djy_evtt_pop (DJYOS_EVENT_DEAL_WIRELESS_DATA);
}
Key_deal() {
djy_evtt_pop (DJYOS_EVENT_GET_TEMPERATURE);
djy_evtt_pop (DJYOS_EVENT_GET_SPEED)
};
上面的DJYOS_EVENT_GET_WIRELESS_DATA是数值型变量,是event_type,事件类型的id。
是不是和一般的裸机程序没有区别,只是原来的api调用,变成djy_evtt_pop对应的event_type即弹出事件而已.当然需要多出一个操作,那就是注册这个api代表的事件类型,比如上面的get_wireless_data这个api,就是系统程序员在写完这个函数的代码后,需要添加如下代码
DJYOS_EVENT_GET_WIRELESS_DATA=djy_evtt_regist(true,true,prios,1,get_wireless_data,8192,"get wireless data");
这样就相当于注册了一个事件类型,其他人使用这个api只需用pop_event(event_id)即可.
但是请注意,这个注册操作是系统程序员应该做到,所以在djyos里,应用程序员和系统程序员有了独立的分工,他们之间仅有的一点联系是接口,这个就是event(事件),系统程序员注册事件,应用程序员使用事件,同时由于DJYOS定义了一些标准的事件,这样应用程序员就和系统程序员没有丝毫联系了,因为他们都知道标准的事件类型数值。
也许你会说,那这个和直接调用api有什么区别。
区别是:
1. djy_evtt_pop弹出的事件是可以并发执行的执行体,它们执行时是一个独立的线程,正因为这样
djy_evtt_pop (DJYOS_EVENT_GET_TEMPERATURE);
djy_evtt_pop (DJYOS_EVENT_GET_SPEED)
这两个操作弹出的事件的执行体get_temperature和get_speed是可以并发执行的,这个是属于操作系统调度的范围了,所以其实这也是操作系统程序和裸机程序的差别。
2. 由于事件类型和执行体api是动态绑定的,这样当事件类型对应的执行体api改变了,应用程序不需要改变。
比如有一天,我的系统没有无线接收设备了,身边只有有线设备,于是数据必须通过有线设备读取,这个时候应用程序不用改,还是通过
djy_evtt_pop (DJYOS_EVENT_GET_WIRELESS_DATA);
但是系统程序员要改,要将
DJYOS_EVENT_GET_WIRELESS_DATA=djy_evtt_regist(true,true,prios,1,get_wireless_data,8192,"get wireless data");改为
DJYOS_EVENT_GET_WIRELESS_DATA=djy_evtt_regist(true,true,prios,1,get_wireness_data,8192,"get wireless data");
get_wireness_data是系统程序员需要解决的,和应用程序员无关。
这个其实也是djyos和其他RTOS的一个差别。
int main(){
id=djy_get_evtt_id("get wireless data");
if(id!=cn_invalid_id)
djy_evtt_pop(id);
id=djy_get_evtt_id("deal wireless data");
if(id!=cn_invalid_id)
djy_evtt_pop(id);
Key_deal() {
id=djy_get_evtt_id("get temperature");
if(id!=cn_invalid_id)
djy_evtt_pop(id);
id=djy_get_evtt_id("get speed");
if(id!=cn_invalid_id)
djy_evtt_pop(id);
};这种方式和上面的实现的一个差别就是不再使用系统程序员注册api事件类型得到的id,这样应用程序员不再使用任何系统程序员拥有的变量,资源。从而达到完全隔离,同时应用程序可以独立编译。
从上可以看出djyos给软件开发行业带来的跨越:
1. 嵌入式软件开发可以有明显的分工,即系统模块开发,和应用开发,且相互独立,这样系统模块和应用程序开发可以并发进行,极大缩短开发周期。
2. 系统模块和应用程序完全隔离,这样系统模块的修改不会影响应用程序,极大提高了系统的可维护性,和扩展性。
3. 应用程序开发可以与硬件无关,也可与底层机制实现无关,只要你会c/c++,你就可以开发嵌入式程序,这必将极大促进嵌入式软件产业的蓬勃发展。
附:
上面有些代码为了阅读方便,没有完全按照djyos里的api接口书写。
Djyos的调度机制考虑了更多问题,这里只是提到了一个方面,还有djyos的泛设备模型等机制,都是为了方便程序的开发,同时也是为了程序的可维护性和扩展性。我们djyos的原则就是九九加一,99%的便捷性和1%的可能性,有关djyos的详细资料可以到下载. 同时可以访问blog.csdn.net/djyos