第三章-nesC编程语言

上传人:su****e 文档编号:243328547 上传时间:2024-09-21 格式:PPT 页数:84 大小:456.50KB
返回 下载 相关 举报
第三章-nesC编程语言_第1页
第1页 / 共84页
第三章-nesC编程语言_第2页
第2页 / 共84页
第三章-nesC编程语言_第3页
第3页 / 共84页
点击查看更多>>
资源描述
单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,第三章,nesC,编程语言,第三章,nesC,编程语言,nesC,语言简介,nesC,语言规范,基于,nesC,语言的应用程序,nesC,程序运行模型,编程约定,可视化组件关系,1,、,nesC,语言简介,TinyOS,最初是用汇编和,C,语言编写的,后来改用支持组件化编程的,nesC,语言。,该语言把,组件化,/,模块化思想,和,基于事件驱动的执行模型,结合起来。,TinyOS,操作系统、库、及其应用程序都是用,nesC,语言编写的。,TinyOS,是一种面向传感器网络的新型操作系统,它最初是用汇编和,C,语言编写的。,但在应用过程中,发现,C,语言不能有效方便地支持面向传感器网络的应用和操作系统的开发,相关工作人员为此对,C,语言进行了一定的扩展,开发出,nesC,语言。,nesC,不但支持,TinyOS,的并发模型,还同时具有结构化机制,命名机制,能够跟其它软件组件连接成一个健壮的网络嵌入式系统。,其主要目标是让应用程序开发人员能够方便地建立起完整的、并发式系统的组件,并可以在编译时作出全面的检查。,TinyOS,定义了很多的重要的概念,这些概念都体现在,nesC,语言上了。,nesC,:,使用,C,作为其基础语言,支持所有的,C,语言词法和语法,其独有的特色如下:,增加了,组件,(,component,),和,接口,(,interface,),的关键字定义;,定义了接口及如何使用接口表达组件之间关系的方法;,目前只支持组件的静态连接,不能实现动态连接和配置。,nesC,应用程序都是由组件组成的,这些组件之间的连接是通过定义良好的、具有双向性质的接口。,1,、结构和内容的分离:程序由组件构成,它们装配在一起,(“,配线,/,连接 ”,),构成完整程序。,2,、根据接口的设置说明组件功能。接口可以由组件提供或使用。被提供的接口表现它为使用者提供的功能,被使用的接口表现使用者完成它的作业所需要的功能。,3,、接口有双向性:它们描述一组接口供给者,(,指令,),提供的函数和一组被接口的使用者,(,事件,),实现的函数。,4,、组件通过接口彼此静态地相连。这增加运行时效率,而且允许更好的程序静态分析。,5,、,nesC,基于由编译器生成完整程序代码的需求设计。这考虑到较好的代码重用和分析。这方面的一例子是,nesC,的编译,-,时间数据竞争监视器。,6,、,nesC,的协作模型基于一旦开始直至完成作业,并且中断源可以彼此打断作业。,nesC,编译器标记由中断源引起的潜在的数据竞争。,2,、,nesC,语言规范,概述,TinyOS,操作系统、库和程序服务程序是用,nesC,写的。,nesC,是一种用于开发组件式结构程序的语言。,nesC,是一种,C,语法风格的语言,但是支持,TinyOS,的并发模型,以及组织、命名和连接组件成为嵌入式网络系统的机制。,nesC,应用程序由有良好定义的双向接口的组件构建;,nesC,把组件化,/,模块化的思想和,TinyOS,基于事件驱动的执行模型结合起来。,规范,nesC,应用程序由一个或多个组件连接而成。,一个组件可以提供或使用接口:,组件中,command,接口由组件本身实现;,组件中,event,接口由调用者实现;,接口是双向的,调用,command,接口必须实现其,event,接口。,nesC,编程基本概念,接口(,interface,),模块(,module,),组件(,configuration,、,components,),命令(,command,)、事件(,event,)、任务(,task,),2.1,接口,一个组件可以提供接口(,provides,),也可以使用接口(,uses,)。提供的接口描述了该组件提供给上一层调用者的功能,而使用的接口则表示该组件本身工作时需要的功能。,接口是一组相关函数的集合,它是双向的并且是组件间的唯一访问点。接口声明了两种函数:,命令(,command,):接口的提供者必须实现它们;,事件(,event,):接口的使用者必须实现它们 。,接口是双向的:提供或使用。,接口指定了一组命令(,command,),其职能由接口的提供者实现。还指定了一组事件(,event,),其职能由该接口的使用者实现。,也就是说,提供了接口的组件,必须实现,该接口的命令函数;而使用了某接口的组件,必须实现,该接口的事件函数。,如果一个组件调用了(,call,)的一个接口命令,,必须实现该接口的事件。,一个组件可以使用(,use,)或提供(,provide,)多个接口或者同一接口的多个实例。,如何定义接口:,接口放在一个单独的文件中*,.,nc,接口的名称应与文件名对应例如,interface1,的接口则必须对应于文件名,interface1.nc,接口定义描述了一系列函数原型(,command,和,event,),SendMsg.nc,:,interface,SendMsg,command,result_t,send,(uint16_t address, uint8_t,length,message_t,*,msg,);,event,result_t,sendDone,(message_t,*,msg,result_t,success);,SendMsg,接口类型提供者必须实现发送,指令,而使用者必须实现,sendDone,事件,.,provides interface A1; /*,提供接口 *,/,uses interface A1; /*,使用接口 *,/,provides interface A1,as,A2; /*,接口别名 *,/,uses interface A1,as,A2;,接口的特点:,Provides,未必一定有组件使用,但,uses,一定要有人提供,否则编译会提示出错。在动态组件配置语言中,uses,也可以动态配置。,接口可以连接多个同样的接口,叫做多扇入,/,扇出。,一个,module,可以同时提供一组相同的接口,又称参数化接口,表明该,Module,可提供多份同类资源,能够同时给多个组件分享。,interface Send ,command error_t,send(message_t,*,msg, uint8_t,len,);,command error_t,cancel(message_t,*,msg,);,event void,sendDone(message_t,*,msg, error_t error);,command uint8_t,maxPayloadLength,();,command void*,getPayload(message_t,*,msg,);,command,:命令关键字,interface,:接口关键字,event,:事件关键字,2.2,组件,一个,nesc,编写的程序由一个或多个组件构成或连接而成。,组件分两种:,Module,组件(模块):实现某种逻辑功能;,Configuration,组件(配件):将各个组件连接起来成为一个整体。,一个组件可以提供接口(,interface,),也可以使用接口。提供的接口描述了该组件提供给上一层调用者的功能,而使用的接口则表示了该组件本身工作时需要的功能。,一个组件由两部分组成:一个是,规范说明,,包含要用接口的名字;另一部分是它们的,具体实现,。,组件特征:,组件内变量、函数可以自由访问,但组件之间不能访问和调用。,组件规格列出该组件提供或使用的规格元素,(,接口请求,指令或事件,),。,每种规格元素有一个名字,(,接口实例名,命令名或事件名,).,这些名字属于总组件,-,规格作用域的变量命名空间。,一个组件说明中可以有多个使用和提供指令。多个使用和提供规格元素可以通过包含在, and,中而组合在一个指令中。,举例来说,下面两个说明是一样的:,接口实例声明的完整语法是,interface X as Y,明确地指明,Y,作为接口的名字。,interface X,是,interface X as X.,的一个速记,.,指令或事件能通过包括一个声明了指令或事件及存储类型的标准的,C,函数而作为规格元素直接地被包含:,作为接口实例,如果没有指定接口叁数,指令,(,事件,),就是简单的指令,(,简单的事件,),如果接口参数是指定的,就是参数化指令,(,参数事件,),。接口参数被放置在一般的函数参数列表之前,举例来说,注意接口参数只在组件说明里面指令或事件上被允许,而不允许在接口类型里面,.,在组件,K,的规格中提供的一个指令,(,事件,) F,是,K,的提供指令,(,事件,) F;,同样地,一个被用于组件,K,的规格的指令,(,事件,),是,K,的使用指令,(,事件,) F,。,组件,K,的提供接口实例,X,的指令,F,是,K,的提供指令,X.F,;组件,K,的使用接口实例,X,的指令,F,是,K,的使用指令,X.F,。,K,的提供接口实例,X,中的事件,F,是,K,的使用事件,X.F; K,的使用接口实例,X,中的事件,F,是,K,的提供事件,X.F (,注意事件的使用和提供根据接口双向属性的颠倒,),。,当使用,/,提供区别关系不大时,我们常常只简单的提到, K,的指令或事件,a,。,K,的指令或事件,a,可能是参数化的或简单的,取决于其通信的规格元素的参数化或简单状态,.,组件模型,Component M1,provides ,interface P1;,interface P2;,uses ,interface U1;,interface U2;,implementation ,/,实现部分,组件的实现(,implementation,)作用域,对于模块来说该部分是程序功能的代码实现部分,它实现了所提供的接口中的命令和使用的接口中的事件;对于配件来说该部分用于将接口的使用组件和提供组件之间连接起来组成一个程序。,组件(模块或配件)的规范(,specification,)作用域。组件根据功能需要可以声明所使用和提供的接口,也可以不提供(或)使用任何接口。,component,代表,module,或,configuration,组件标识符(,identifier,),P1,:接口标识符(名字),模块,M1,必须实现它所提供接口的命令(,command,)。,U1,:接口标识符,模块,M1,必须实现它所使用的接口中事件(,event,)。,组件有两种:配件和模块。,模块(,module,):提供一个或多个接口的实现。,配件(,configuration,):把其他的组件装配起来,连接组件使用的接口到其提供者。,每个,nesC,应用程序都,必须有且只有一个,顶层配件(,top-level configuration,)连接内部组件。,之所以区别设计模块与配件,是为了让系统设计者在构建应用程序的时候可以脱离现有的实现。例如:设计者可以提供配件,只是简单地把一个或多个模块连接起来,而不涉及其中具体的工作。同样地,另一个开发者负责提供一组模块库,这些模块可以普遍使用到众多应用中。,2.3,模块及其组成,模块是主要用,C,语言实现的组件和规范,module-implementation:,implementation translation-unit ,编译基本单位是一连串的,C,声明和定义,模块编译基本单位的顶层声明属于模块的组件说明域。这些声明的范围是模糊的而且可以是,:,任意的标准,C,声明或定义,一种作业声明或定义,指令或事件实现,.,模块实现说明,编译基本单位必须实现模块的所有的提供指令,(,事件,)a,。 一个模块能调用它的任一指令和它的任一事件的信号。,简单指令或事件,a,由带有存储类型指令或事件的,C,函数定义的语法实现,(,注意允许在函数名中直接定义的扩展,),。另外,语法关键字必须被包含如果它被包含在,a,的声明中。,带有接口参数,P,的参数指令或事件,a,,由带有存储类型指令或事件的函数定义的,C,文法实现,这时,函数的普通参数列表要以,P,作为前缀,并带上方括号 。这些接口参数声明,P,属于,a,的函数参数作用域而且和普通的函数参数有相同的作用域,调用命令和事件信号,一个简单的指令,a,使用,call a(.),调用,一件简单的事件使用,signal a(.),发送讯号。,一个参数指令,a(,个别地,一件事件,),有,n,个接口参数,类型为,t1 , . . . ,tn,由接口参数表达式,e1 , . . . ,en,。调用如下:,call ae1, . . . , en(.),。,接口参数表达式,ei,必须分配类型,ti,;,实际的接口参数值是,ei,影射到,ti,.,指令和事件的执行是立即的,也就是,调用和发送信号行为和函数调用是同样地。,一个模块能为一使用指令或事件,a,指定默认的调用或信号实现。,提供指令或事件的默认实现会引起编译,-,时间错误。,如果,a,未与任何指令或事件实现联系,默认的实现将被执行。,任务,任务是一个独立的控制实体,由一个返回空存储类型的函数定义。,任务也能预先声明,,举例来说,., task void,myTask,(),;,任务通过前缀,post,提交,举例来说, post,myTask,(),。,提交操作将任务挂入队列,迅速返回;,如果提交成功则返回,1,,否则返回,0,。,post,表达式的类型是,unsigned char,。,原子,atomic-stmt:,atomic statement,原子通常是运行的最小单位,确保运行时,没有其它的运算同时发生。,它用于更新并发的数据结构的互斥变量,等等。,原子的区段应该很短。,禁止原子代码内调用命令或触发事件,控制只能正常地 流入或流出原子的陈述,:,任何的,goto, break,或,continue,,跳转入或出一原子代码都是错误的,2.4,配件及其组成,配件通过连接一系列其他组件来实现一个组件规范,主要功能是实现组件间的相互访问方式。,包含的组件,连接,/,绑定,用于连接规格元素,(,接口,指令,事件,),。,绑定连接二个端点。,每个端点的标识符路径指明一个规格要素。,自变量表达式列表可选的指出接口参数值。,如果端点的规格要素是参数化的,而端点又没有参数值,那么我们说该端点是参数化的。,如果一个端点有参数值,而下面的任一事件成立时,就会产生一个编译时间错误:,􀁺 参数值不全是常量表达式,.,􀁺 端点的规格元素不是参数化的,.,􀁺 参数个数比规格要素中限定的叁数个数多,(,或少,),􀁺 叁数值不在规格元素限定的叁数类型范围中。,如果端点的标识符路径不是以下三种形式之一,就会产生一个编译时间错误:,􀁺,X,:此处,X,命名一种外部的规格元素,.,􀁺,K.X,,此处,K,是组件列表中的一个组件,而,X,是,K,的规格元素。,K,:此处,K,是组件列表中的一些组件名。,nesC,有三种绑定语句,endpoint1=endpoint2:(,赋值绑定,),任何连接包括一外部规格元素。,这些有效地使两规格元素相等。设,S1,是,endpoint1,的规格要素,,S2,是,endpoint2,的规格要素。下面两个条件之一必须满足,否则就会产生编译时间错误:, S1,是内部的, S2,是外部的,(,反之亦然,),,并且,S1,和,S2,都是被提供或都是被使用, S1,和,S2,都是外部的,而且一个被提供,而另一个被使用,endpoint1-endpoint2:(,联编绑定,),一个包括二种内在的规格元素的绑定。,联编绑定总是连结一由,endpoint1,指定的使用规格元素到一,endpoint2,指定的提供规格元素。,如果这两个条件不能满足,就会发生编译,-,时间错误。,endpoint1 endpoint1,是等价的。,在绑定的所有三种类型中,两被指定的规格元素必须是一致的,就是说,.,它们必须都是指令,或都是事件,或都是接口实例。,如果它们是指令,(,或事件,),则它们必须有相同的函数名。,如果他们是接口实例,它们必须有相同的接口类型。,如果这些条件不能满足,就会发生编译,-,时间错误,.,。,如果一个端点是参数化的,则另一个必须也是而且必须有相同的参数类型,;,否则就会发生编译,-,时间错误。,所有的外部规格元素必须绑定,否则发生编译,-,时间错误,内部的规格元素可以不绑定。,隐式绑定,隐式绑定可以写成,K1 - K2.X,或,K1.X ,是等价的,).,该用法通过规格元素,K1,来引用规格元素,Y,,因此,K1.Y - K2.X,形成一个合法绑定。,无参数化绑定的语句,在配件,C,的连接,I1 I2,中,二个中间函数之一是被调用的,另一个是调用者。,如果下列任一条件成立,则,I1(,同样地,I2),是被调用的:,如果,I1,符合一个被提供命令或事件的内部规格元素,.,如果,I1,符合一个被使用命令或事件的外部规格元素,.,如果,I1,符合一个接口实例,X,的命令,而,X,是内部、被提供的或是一个外部的且被使用的规格元素,.,如果,I1,符合一个接口实例,X,的事件,而,X,是外部、被提供的或是内部的且被使用的规格元素,.,如果这些情况没有一个成立,则,I1,是调用者。,表达式,call a(e1, . . . , en),实现过程如下:,􀁺 自变量,e1, . . . , en,被赋值为,v1, . . . ,vn,.,。,􀁺,a,对应的中间函数被自变量,v1, . . . ,vn,调用,返回结果列表,L.,􀁺 如果,L=(w)(,一个独立列表,),调用的返回结果就是,w.,参数化函数绑定的语句,如果组件,K,的一条指令或事件,a,带有类型,t1, . . . ,tn,的接口参数,则对每一数组,(v1 : t1, . . . ,vn,:,tn,),存在一个中间函数,Ia,v1,.,vn,。,在模块中,如果中间函数,I (v1,.,vn,),符合参数化的提供指令(或事件),a,,则,Iv1,.,vn,中对,a,的实现的调用将传递,v1, . . . ,vn,作为,a,的接口参数。,表达式,call ae01, . . . , e0m(e1, . . . , en),实现:,􀁺 自变量,e1, . . . , en,被赋值为,v1, . . . ,vn,。,􀁺 自变量,e1, . . . ,em,被赋值为,v1 , . . . ,vm,。,􀁺,vi,对应,ti,类型,这里,t i,是,a,的第,i,个接口参数的类型。,􀁺,a,对应的中间函数,I(v1 ,.,vm,),被参数,v1, . . . ,vn,调用,返回列表,L,。,􀁺 如果,L,有一个或更多的元素,调用结果和无参数的情形一样产生,􀁺 如果,L,为空,则用接口参数,v1, . . . ,vn,和参数,v1 , . . . ,vm,调用,a,的默认实现,且返回该调用的结果。,如果,L,为空且,a,没有默认实现,则会产生编译,-,时间错误。,绑定语句的一个端点指向一参数化规格元素时,有二种情形:,􀁺 端点指定参数值,v1, . . . ,vn,。,若端点对应指令或事件,a1, . . . ,am,,则相应的中间函数为,I(a1,v1,.,vn,),. . ., I(am,v1,.,vn,),且绑定方式不变。,􀁺 端点不定义参数值,.,在这情况下绑定端点都对应参数化规格元素,且有相同接口参数类型,t1, . . . ,tn,.,。,如果一个端点对应命令或事件,a1, . . . , am,而另一端点对应命令或事件,1, . . . ,m,则对所有的,1=i,MainC.Boot,;,/,BlinkC.Boot,-,MainC.Boot,BlinkC.Timer0 - Timer0;,/BlinkC.Timer0 - Timer0.Timer0,BlinkC.Timer1,-,Timer1;,/ -,是连接的意思,BlinkC.Timer2,-,Timer2;,/ -,是一种包含两个内部规范元素的连接,BlinkC.Leds,- LedsC;,/,BlinkC.Leds,-,LedsC.Leds,/,也就是把负责实现应用部分的模块,BlinkC,与系统的组件库连接起来,/,记住,,BlinkAppC,和,BlinkC,组件是不一样的。更确切的说,,BlinkAppC,是由,Blinkc,组件连同,mainc,,,ledsc,和,3,个,timer,定时器一起组成的。,BlinkC.nc,module,BlinkC (), uses interface Timer as Timer0;,/,定义使用到的接口, /Timer1,、,Timer2,的定义同上,uses interface Leds;,uses interface Boot;,/BlinkC,可以调用这些它使用的接口的任何命令,,但必须实现这些接口的所有事件,event,implementation,event,void,Boot.booted,(), call Timer0.startPeriodic( 250 );,/250ms,周期性触发,call Timer1.startPeriodic( 500 );,call Timer2.startPeriodic( 1000 );,event void,Timer0.fired(),dbg(“BlinkC,”, “Timer 0 fired %,s.n,”,sim_time_string,();,call Leds.led0Toggle();,/led0,灯切换灭,-,亮状态, /Timer1,、,Timer2,的,fired(),事件函数同上,接口连接,由于大多数的节点平台没有基于硬件的内存保护措施,也没有将用户地址空间和系统地址空间分离开,,只有一个所有组件都能共享的地址空间。,最好的办法,就是保持内存尽可能少的共享。,组件声明的任何状态变量都是,私有的,:没有任何其他组件可以对它进行命名或者直接访问它。,两个组件直接交互的唯一方式是通过,接口,。,nesC,使用 箭头,来绑定一个接口到另一个接口,但一定要是同一类接口。,例如,A - B,意为,A,连接到,B,。,A,是接口的,使用者(,user,),,而,B,是接口的,提供者(,provider,),。完整的 表达式应该为:,A. a - B. b,这意味着, 组件,A,的接口,a,连接到 组件,B,的接口,b,。,当一个组件使用或者提供同一个接口的多个不同实例时,设置别名就非常有必要了。如,BlinkC,中的,timer0, timer1, timer2,。,当一个组件只含有一个接口的时候,就可以省略接口的名字了。如,BlinAppC,中,Blinkc.leds, ,ledsC,。 就省略了,LedsC,组件中包含的接口,leds,。其等同于:,Blinkc.leds, ,ledsC.leds,。,由于,BlinkC,组件中仅仅含有一个,leds,的接口实例,那也同样等同于:,Blinkc, ,ledsC.leds,。,同样地,,TimerMilliC,组件只提供了单一的,timer,接口实例,也不必包含在下面的连接里:,BlinkC.Timer0 - Timer0,连接的箭头是 可以对称倒反的。如,Timer0 Timer0,为了方便阅读,大多数连接的箭头还是 从左到右的。,MainC.nc,#include ,hardware.h,configuration MainC ,provides interface Boot;,/,提供接口,Boot,uses interface Init as SoftwareInit;,implementation ,components,PlatformC, RealMainP, TinySchedulerC;,RealMainP.Scheduler,- TinySchedulerC;,RealMainP.PlatformInit,-,PlatformC,;,SoftwareInit,=,RealMainP.SoftwareInit,;,Boot,=,RealMainP;,/RealMainP,使用,SoftwareInit,接口,提供,Boot,接口,Blink,这里的,“,=,”,是一种包含一个外部规范元素的连接。这种连接可以,有效地使两个规范元素等价,,如,S1=S2,,但是这种等价必须满足下面的条件之一:,S1,内部,,S2,外部(或者相反),且,S1,和,S2,是同时被提供的或同时被使用的。,S1,和,S2,都是外部的,而且一个是被提供,一个是被使用。,LedsC,& TimerMilliC,configuration LedsC ,provides,interface Leds;,implementation .,generic configuration TimerMilliC() ,provides,interface Timer;,implementation ,Blink,BlinkSingle,实例,修改,Blink,应用的实现方式,采用一个定时器,一个状态变量来决定点亮或关闭哪个,LED,灯。,原,Blink,程序中无状态变量,现在稍作修改。使用一个定时器和一个状态变量将实现和,Blink,一样的效果。,节省了,CPU,资源和内存。,BlinkSingle,实例,另存,Blink,为,BlinkSingle,:,注释掉,timer1,timer2,timer1,timer2,所有相关;,增加一个单字节状态变量,uint8_t,;,在,timer0,触发事件里根据状态变量采取不同动作。,编译程序并下载到节点,修改,BlinkC,组件,删除,编译程序并下载到节点,修改,BlinkC,组件,相关代码,module BlinkC safe(),uses interface Timer as Timer0; /,删除其他,timer1,timer2,uses interface Leds;,uses interface Boot;,implementation,uint8_t counter = 0;,event void,Boot.booted,(),call Timer0.startPeriodic( 250 );,event void Timer0.fired(),counter+;,if(counter&0x01),call Leds.led0On(); ,else call Leds.led0Off();,if(counter&0x02),call Leds.led1On();,elsecall,Leds.led1Off();,if(counter&0x04),call Leds.led2On();,elsecall,Leds.led2Off();,BlinkSingle,实例,变量类型,为了跨平台使用,变量的类型和标准,C,语言的,int,、,long,和,char,不一样。,TinyOS,代码使用的是更清楚直接的类型,直接声明字节大小。事实上,这些能映射到基本的,C,类型,在不同的平台上是不同的映射。,大多数平台支持浮点数运算。,(double,可能不行,),移植,TinyOS1.x,代码到,2.x,4,、,nesC,程序运行模型,nesC,采用由一旦运行直至完成任务(代表性的实时运算)和硬件异步触发中断控制构成的运行模型。,nesC,调度程序能以任意次序运行任务,但是必须服从一旦运行直至完成规则,(,标准的,TinyOS,调度程序遵从,FIFO,(先进先出)策略,).,任务是一旦运行直至完成的,所以它们是原子的互不妨碍的,但能够被中断。,这种并行运行模型,在程序共享的状态下特殊数据竞争,导致,nesC,程序状态是不稳定的。,nesC,程序代码分为二个部分:,同步码,(SC),:仅仅在作业内部可达的编码,(,函数,指令,事件,作业,),异步码,(AC),:至少一个中断源可达的代码,.,虽然非抢占消除作业之间的数据竞争,但是在,SC,和,AC,,以及,AC,和,AC,之间仍然有潜在的竞争。通常,任何从,AC,可达的共享状态更新都是一个潜在的数据竞争,.,nesC,提出了原子性的代码执行方式,以解决冲突问题。,任务,到目前为止,所有代码都是,同步(,synchronous,,,sync,),的。它运行在,单一的前后执行顺序,,没有任何形式的抢占。,也就是说,当同步代码开始运行后,直到完成前它不会释放,CPU,给其他的同步代码。,但是,如果一段同步代码运行时间很长, 它会阻止其他同步代码运行,从而,不利于系统的反应,。,在大多数情况下,因为同步代码是非抢占的,这种编程方式行之有效。但是,这种做法,并不适合,大规模计算。,当一个组件需要做什么且此时还有宽裕的时间,最好给,TinyOS,延迟计算的能力,即处理完之前已在等待的事情后再执行。,任务是一个函数,组件告诉,TinyOS,稍后再运行而不是立即运行。,任务是,TinyOS,系统提供的一种特殊的机制,类同于线程。,task,一般为一个函数,无参数,无返回值。,task,可以在一般的,TinyOS,程序中发出,而,task,的执行是由,TinyOS,系统内核来实现的。并且,task,的执行是不影响调用者的,将会在发出,task,后的某一个时刻被调度运行。,任务之间的运行是原子性(就像原子一样是不可分割的基本微粒,不可被中断的)。,任务的代码,应该尽量简单点,:避免其他的执行操作突然接管并修改其中的数据。,中断会突然中断当前的执行过程,并抢占运行 。,task,的特点:,无参数、无返回值,系统会按特定的顺序调度这些,task,task,间不能抢占,但是可以被中断所抢占,在,task,未执行时,发出多少个,task,,都将只运行第一次这个,task,系统执行完一个,task,后才会去执行其它,task,,所以,task,一般要求短小,不至于影响其它,task,任务在模块的实现里声明句法如下:,task void taskname(),taskname,是设定的任务名称。 任务必须,空返回,,并不能带有任何参数。,派遣一个任务去(稍后)执行,句法:,post,taskname,();,一个组件可以在命令、事件或者任务里派遣一个任务。,一个任务可以安全地调用命令和触发事件。,task,的定义:一般也是放在,module,中的,module M1implementationtask void task1()void f1()post task1();,提交操作(,post,)将任务放入内部的,先进先出(,FIFO,)的任务队列,,当一个任务被执行时,它必须一直运行到结束,才能让下一个任务运行。,一个任务的运行周期不该太长久。一系列长作业应分成几个独立的小任务。,任务之间不能相互抢占顺序。,硬件中断可以抢断任务,(但至今还没见过这种情况)。,任务的提交操作返回一个,error_t,,其值可以是,SUCCESS,或者,FAIL,。 当且仅当任务已经等待运行(,已经成功提交,但是还未运行,)才会提交失败,FAIL,。,event void Timer0.fired() ,uint32_t i;,call Leds.led0Toggle();,for (i = 0; i 400001; i+) ,uint32_t i;,task void,computeTask,(),uint32_t start = i;,for (;i = 400000) ,i = 0;, else ,post,computeTask,();,内部函数,只供组件内部使用的函数。,一个组件调用另一个组件里的函数的唯一方式是通过接口的命令和事件。,有时会碰到这样的情况:一个组件想要一个只能供自己内部使用的函数。,组件可以定义标准的,C,语言函数,而其他组件则不能取这个名字,因此也不能直接调用。,内部函数就相当于,C,语言里的函数:调用时,不需要关键字,call,或,signal,来修饰。,内部函数,可以调用命令或触发事件。,分阶段作业,在一个,阻塞系统(,blocking,),中,当程序调用一个长作业,这个调用直到作业完成后才会返回:即该程序阻塞了。,在一个,分阶段(,split- phase,),系统里,当程序调用一个长作业时,,调用立即返回,,并要求,其完成时触发一个回调。,即分阶段作业。,分阶段作业分成,调用(,invocation,),和,完成(,completion,),两个独立的执行阶段。,当程序调用一个长作业时,调用立即得到返回,并要求其完成后触发一个回调。,分阶段调用在执行时不会占用堆栈内存,可以保持系统的相应性,减少了堆栈的使用,可以保持系统的响应:绝不会当应用需要执行动作时,出现所有线程都被阻塞调用占用的情况;,往往能减少堆栈的利用。,Blocking,Split-Phase,if (send() = SUCCESS) ,sendCount,+;,/ start phase,send();,/completion phase,void,sendDone(error_t,err) ,if (err = SUCCESS) ,sendCount,+;,同步与异步,关键字“,async,”,修饰的命令函数或事件函数都是异步的。,中断处理程序都是异步的,不能调用同步函数。,在中断处理程序中,执行同步函数的唯一方式是通过发布任务。,异步,async,可以在任务之外抢断运行的函数,须用,“,async,”,关键字,标明:它们相对于任务是异步运行的。,默认情况下,命令和事件函数都是同步的:如果它们不是同步的,就得用,“,async,”,关键字注明(在接口的定义文件里)。,所有的,中断处理程序都是异步,的,不能在它们的调用代码里包含同步的函数。,在中断处理程序中,执行同步函数的唯一方式是,发布任务,。一个任务的发布是一个异步的操作,而任务的运行却是同步的。,竞争冲突,问题:如果任务带来了延迟,为什么要使用它呢?为什么不使所有函数都是异步的?原因就是:,竞争冲突。,即抢占执行的程序会修改当前计算过程中的变量,导致系统进入一种不一致的状态。,多指令周期的指令被打断其后果更加严重。,bool,state;,async,command,bool,toggle(),if(state,= 0 ) ,State=1;Return 1;,if(state,= 1) ,State=0; Return 0;,从,state=0,开始:,Toggle();,State=1;,interruptToggle();State=0;Return 0;,Return 1;,假设,如果左边的消息发送代码是异步的,就有可能在条件语句“,if(!state,)”,和作业“,state = TRUE”,之间有另一个组件插进来也试图发送。但两个同时发送就会出现问题。,编程提示:尽量保持代码同步。只有该代码的时间选择非常重要时,或调用该代码的上级代码的时间选择也很重要时,代码才是异步的。,Command,result_t,SendMsg.send,If(!state,) ,State = TRUE;/Send a packetReturn SUCCESS;,Return FAIL;,原子性,atomic,中断带来的问题,意味着程序需要有一种方式能够在执行一小段代码时不会被其他程序抢占。,NesC,语言用,atomic,(中文意思为原子)语句提供了这种功能。,atomic,语句块保证了这些变量的读取与写入都是原子性的。,原子性代码,保证一小段代码不会被其他程序抢占,atomic,代码块会浪费,CPU,资源,应尽量减少,atomic,代码块使用,,atomic,代码块应尽量简短,atomic,代码块通常用于转换一个组件里的状态变量,注意,,这并不意味着,atomic,语句块不会被抢占。,即使是,atomic,语句块,两个不共用变量的代码片段可以相互抢占对方。,在这个例子里,,incrementC,(理论上来讲)可以侵犯,incrementA,不可冒犯的原子性。,但,incrementA,不能抢占它自身,,incrementC,也一样。,command,bool,incrementA,(),atomic ,a+; b=a+1;,command,bool,incrementC,(),atomic ,c+; d=c+1;,变量的保护,NesC,会检查变量有没有被合理的保护,如果没有就发出警告。,那什么时候对变量提供,atomic,区域保护?,如果是从异步函数访问变量,那它必须得到保护。,nesC,编译器的分析具有,流动敏感性,。这意味着,如果函数没有包含在一个,atomic,代码块里,但它总是在,atomic,的代码块里被调用,那编译器就不会发出警告。,Atomic,的注意,当你可以自由地播撒,atomic,代码块来避免数据竞争冲突时,你仍应该小心的处理。,一方面,一个,atomic,代码块,浪费,CPU,资源,,所以要尽量减少,atomic,代码块。,另一方面,,atomic,代码块,越短,,对中断的延迟则越少,从而,提高系统的并发性。,atomic,代码块,运行多久,是一个棘手的问题,尤其是当一个组件必须调用另一个组件的时候。,atomic,代码块运行时长例子,例如,,Atmega128,单片机上,SPI,总线的实现组件有一个资源仲裁者在管理对总线的访问权。仲裁者允许不同的用户请求资源(这里指总线),并在它们被授权时通知它们。,当,SPI,没有用户时,,SPI,就把它自己关掉,但如果不调用仲裁者(或复制仲裁者的状态变量),它就不知道什么时候没有用户。,在这种情况下,,isUse,(),命令的调用被期望是非常短的(可能就是读取一个状态变量)。,如果有人连接一个仲裁者,其,isUse,(),命令要耗时,1ms,,那就会出现问题了。最好尽量减少此类情况。,修改状态变量,atomic,代码块最基本的使用是在一个组件里,转换状态变量,。通常,一个状态的转换可以分成两个阶段,,第,1,个阶段是修改成一个新的状态;,第,2,个阶段是执行某些操作。,如果状态变量是用,async,函数访问的,那就需要使状态的转换原子化(即使用,atomic,代码块)。,但不需要把整个代码段都放在,atomic,块里,因为发送一个信息包耗时很长,这会导致整个系统错过中断。,If(!state,) ,State = TRUE;/Send a packetreturn SUCCESS;,return FAIL;,uint8_t,oldState,;,atomic ,oldState,= state;,state = TRUE; ,if (!,oldState,) ,/send a packet,return SUCCESS;,else return FAIL; ,编程提示:保持,atomic,代码块简短,尽量少用。在,atomic,代码块里调用到外部组件时要小心处理。,无线模块的开启过程,打开电压调节器,获得,SPI,总线的访问权,同过,SPI,总线发布一个命令,开启无线模块的振荡器,将无线模块设置为振荡模式,5,、编程约定,通用约定,不要使用不常见的缩写,缩写的首字母应当大写,保持缩写单词的上下文一致性,所有代码都应当有说明文档,在文档说明顶部加入,author,标签,软件包,对相关文件采用前缀标示,在一个软件包中,区分公共组件和私有组件,语法约定,nesC,语言约定,命名约定,软件包约定,预处理约定,C,语言约定,其他语言约定,TinyOS,约定,错误返回值,组件间的指针传递,接口连接的注解约定,6,、可视化组件关系,精心设计的,TinyOS,系统通常有很多层的配置,每一层都简单地提取了抽象概念,以很少的可执行代码来实现复杂的配置。,要想到达底层的模块,或者操纵各个层,使用文本编辑器是很费力的。幸好,,TinyOS,和,nesC,有一个名为,nesdoc,的辅助工具, 它可以自动地从源代码产生说明文档。除了注释之外,,nesdoc,还可以显示配件的结构与组成。,nesdoc,的使用方法:在应用程序的目录下输入“,make platform docs,”,,比如“,make micaz docs,”,生成的文档保存在,tinyos-2.x/doc/nesdoc,目录中。,图形化文档说明,以下是,Blink,程序的图形说明,单一的矩形框表示模块;,双层的矩形框表示配件;,虚线表示其为通用的普通的,generic,(相当于类);,线条表示连接。,其中,MainC,组件如右图所示:,带阴影的椭圆表示接口;,MainC,提供接口,Boot,,使用接口,SoftwareInit,。,MainC,的详细情况在这里不作进一步阐述。但从组件中,可以看出它的功能:控制调度、初始化硬件平台和初始化软件组件。,
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 图纸专区 > 课件教案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!