现在的位置: 首页 > 综合 > 正文

nesC 1.1 语言参考手册(2)

2013年09月05日 ⁄ 综合 ⁄ 共 11498字 ⁄ 字号 评论关闭
7 配置
配置是通过连接、配线,建立与其他组件的连接:
configuration-implementation:
implementation { component-listopt connection-list }
组件列表列出用来建立这一个结构的组件,连接列表指明各组件之间,以及与结构说明之间是怎样装配在一起的。在这一节的其余部分中,我们调用来自结构的外部的规格元素
, 和来自结构的内在的成份之一的规格元素。
7.1 包含组件
组件列表列出用来建立这一个结构的组件。在结构里面这些组件可随意的重命名,使用共同外形规格元素,或简单的改变组件结构从而避免名称冲突。(以避免必须改变配线)为组件选择的名字属于成份的实现域。
component-list:
components
component-list components
components:
components component-line ;
component-line:
renamed-identifier
component-line , renamed-identifier
renamed-identifier:
identifier
identifier as identifier
如果二个组件使用as给出相同的名字,则会发生编译时间错误(举例来说., components
X, Y as X)。
只有一个个别的例子:如果组件 K 被用于二不同的结构 ( 或甚至两次用于相同的结构里面), 在程序中仍然只有 K(及它的变量) 的唯一实例。
7.2 配线
配线用于连接规格元素 (接口,指令,事件)。本节和下一节(第 7.3 节) 定义配线的语法和编译-时间规则。第 7.4 节详细说明程序配线声明是如何指出在每个调用和信号表达中哪个函数被调用。
connection-list:
connection
connection-list connection
connection:
endpoint = endpoint
endpoint -> endpoint
endpoint <- endpoint
endpoint:
identifier-path
identifier-path [ argument-expression-list ]
identifier-path:
identifier
identifier-path . identifier
配线陈述连接二个端点。每个端点的标识符路径指明一个规格要素。自变量表达式列表可选的指出接口参数值。如果端点的规格要素是参数化的,而端点又没有参数值,那么我们说该端点是参数化的。如果一个端点有参数值,而下面的任一事件成立时,就会产生一个编译时间错误:
     叁数值不全是常量表达式.
     端点的规格元素不是参数化的.
     参数个数比规格要素中限定的叁数个数多 ( 或少)
      叁数值不在规格元素限定的叁数类型范围中。
如果端点的标识符路径不是以下三种形式之一,就会产生一个编译时间错误:
      X, 此处X命名一种外部的规格元素.
       K.X,此处K 是组件列表中的一个组件,而X是K 的规格元素。
K ,此处K是组件列表中的一些组件名。这种形式用在固定的连接中,将在第 7.3 节中讨论。注意,当叁数值指定时这种形式不能够使用。
nesC 有三种配线陈述:
       endpoint1=endpoint2:( 赋值配线) 任何连接包括一外部规格元素。这些有效地使两规格元素相等。设S1是endpoint1的规格要素,S2是endpoint2的规格要素。下面两个条件之一必须满足,否则就会产生编译时间错误:
– S1 是内部的, S2 是外部的 (反之亦然) ,并且 S1 和 S2都是被提供或都是被使用
– S1 和 S2 都是外部的,而且一个被提供,而另一个被使用.
endpoint1->endpoint2:( 联编配线) 一个连接包括二种内在的规格元素。.联编配线总是连结一由endpoint1指定的使用规格元素到一endpoint2指定的提供规格元素。如果这两个条件不能满足, 就会发生编译-时间错误.。
     endpoint1<- endpoint2 与endpoint2 -> endpoint1是等价的。
在配线的所有三种类型中,两被指定的规格元素必须是一致的,就是说., 它们必须都是指令,或都是事件, 或都是接口实例. 同时, 如果它们是指令(或事件),则它们必须有相同的函数名 如果他们是接口实例,它们必须有相同的接口类型。他们一定是有相同的接口类型的. 如果这些条件不能满足, 就会发生编译-时间错误.。
如果一个端点是参数化的,则另一个必须也是而且必须有相同的叁数类型;否则就会发生编译-时间错误.。
相同的规格元素可以被多次连接,举例来说.,:
configuration C {
provides interface X;
} implementation {
components C1, C2;
X = C1.X;
X = C2.X;
}
在这个例子中,当接口X中的命令被调用时,多次的配线将会导致接口X的事件的多重信号 ("扇入"),以及多个函数的执行("扇-出")。注意,当二个结构独立地联结相同接口的时候,多重配线也能发生,举例来说.:
configuration C { }                configuration D { }
implementation {                  implementation {
components C1, C2;                components C3, C2;
C1.Y -> C2.Y;                    C3.Y -> C2.Y;
}                                }
所有的外部规格元素必须配线,否则发生编译-时间错误. 可是,内部的规格元素可以不连接 (它们可能在另外一个结构中配线,或者如果模块有适当的默认事件或指令实现,他们可以不配线).
7.3 隐含连接
隐含连接可以写成K1 <- K2.X 或K1.X <- K2 (=和->是等价的). 该用法通过规格元素K1 (不妨K2)来引用规格元素 Y,因此K1.Y <- K2.X (不妨 K1.X <- K2.Y )形成一个合法联结。如果能正确的引用Y,则连接建立,否则发生编译-时间错误。举例来说:
module M1 {                          module M2 {
provides interface StdControl;             uses interface StdControl as SC;
} ...                                  } ...
configuration C { }
implementation {
interface X {                           module M {
command int f();                        provides interface X as P;
event void g(int x);                      uses interface X as U;
}                                         provides command void h();
} implementation { ... }
configuration C {
provides interface X;
provides command void h2();
}
implementation {
components M;
X = M.P;
M.U -> M.P;
h2 = M.h;
}
图 1: 简单的配线例子
components M1, M2;
M2.SC -> M1;
}
M2.SC -> M1 这一行与M2.SC -> M1.StdControl. 是等价的。
7.4 配线语义
我们首先撇开参数化接口讨论配线语义. 7.4.1 节将讨论参数化接口。最后,第 7.4.2 节叙述整体上而言,程序配线声明上的要求。我们将会用到图1中的简单程序作为我们运行的例子。
我们根据中间函数定义配线的意义。每个组件的每个指令或事件都有中间函数. 举例来说,在图 1 中,模块M 有中间函数 IM.P.f , IM.P.g , IM.U.f , IM.U.g , IM.h. 在例子中,我们以其组件,任意接口实例名,及函数名为基础命名中间函数。中间函数不是使用就是提供。每个中间函数接受与组件说明中相应指令或事件相同的自变量。中间函数体I是调用(执行系列)其它中间函数的列表。I 通过程序配线说明连接到其它中间函数 。I接受的自变量不变的经过被调用的中间函数.I 返回结果列表,(列表元素类型是相应指令或事件返回给I的结果类型),列表通过连接调用中间函数返回结果构成。返回空值的中间函数适合不相
连接的指令或事件;返回两个或以上值的中间函数适合“扇出”。
nesC允许在没有直接中间函数的情况下编译,所以本节中描述的行为没有运行开销,实际的函数调用需要参数化的指令或事件。
中间函数和结构 一个结构的配线说明指定中间函数体。我们首先扩展配线说明到中间函数而不限于规格元素,并取消配线说明中= 和->的区别。我们用 I1<-> I2 表示中间函数I1 和I2之间的连结。举例来说,图 1中的结构C 叙述了下列中间函数连接:
IC.X.f<-> IM.P.f     IM.U.f<-> IM.P.f     IC.h2<->IM.h
IC.X.g<-> IM.P.g     IM.U.g<->IM.P.g
在结构 C 的连接I1 <-> I2中,二个中间函数之一是被调用的,另一个是调用者。如果下列任一条件成立(我们使用内部或外部的用辞作规格说明并不妨碍结构 C包含连接),则I1(同样地,I2)是被调用的:
       如果 I1 符合一件被提供指令或事件的内部规格元素.
       如果 I1 符合一件被使用指令或事件的外部规格元素.
    如果 I1 符合一个接口实例X 的指令,而X是内部的且被提供或外部的且被使用的规格元素.
     如果 I1 符合一个接口实例X 的事件,而X是外部的且被提供或内部的且被使用的规格元素.
如果这些情况没有一个成立,则I1 调用者。7.2 节的配线规则确保一个连接 I1<->I2 不会同时连接二个调用者或二个被调用者。图1的结构 C 中,IC.X.f , IC.h2 , IM.P.g,IM.U.f 是调用者而 IC.X.g , IM.P.f , IM.U.g,IM.h 是被调用者。如此C的连接说明IC.X.f 调用IM.P.f,IM.P.g调用IC.X.g,等等。
中间函数和模块 模块中的C代码调用中间函数,或被中间函数调用。
模块M中提供指令或事件a的中间函数I 包含一个单独调用以运行M中的a。其结果是
一个单独的调用返回列表。表达式call a(e1, . . . , en)性质如下:
     自变量e1, . . . , en 被赋值为v1, . . . , vn.。
      a对应的中间函数被以自变量v1, . . . , vn调用,返回结果列表L.
      如果 L=(w)( 一个独立列表),调用的返回结果就是 w.
如果 L=(w1,w2, . . . ,wm) (二或更多的元素),调用的结果仰赖于a的返回类型t。如果t=void,则结果是void。否则,t 一定有一联合函数c( 第 10.3节演示联合函数是如何联合类型的),否则发生编译-时间错误。联合函数接受类型t 的两个值并且返回一个类型t的结果。该调用的结果是c(w1, c(w2, . . . , c(wm&S722;1,wm))) ( 注意L中元素次序是任意的).
list of int IM.P.f() {              list of void IM.P.g(int x) {  
return list(M.P.f());              list of int r1 = IC.X.g(x);
}                               list of int r1 = IM.U.g(x);
return list concat(r1, r2);
}
list of int IM.U.f() {             list of void IM.U.g(int x) {
 return IM.P.f();                  return list(M.U.g(x));
}                           }
list of int IC.X.f() {              list of void IC.X.g(int x) {
 return IM.P.f();                    return empty list;
}                            }
list of void IC.h2() {              list of void IM.h() {
return IM.h();                    return list(M.h());
}                            }
图 2: 图1的中间函数
         如果 L 为空则默认以v1, . . . , vn,为自变量调用执行a,并返回该调用结果。第 7.4.2 节表明如果L为空且a没有默认实现则会发生一编译- 时间错误。
信号表达式的规则是一样的。
中间函数举例 图 2使用类C的语法演示了图 1中组件产生的中间函数,其中list(x)产生一个包含X的独立列表,空列表是表示含0个元素的列表的常量,连接列表如锁链般连接两个列表。对M.P.f, M.U.g, M.h的调用,调用模块M中实现的指令和事件(未给出)。
7.4.1 配线和参数化函数
如果组件K的一条指令或事件a带有类型t1, . . . ,tn的接口叁数,则对每一数组(v1 : t1, . . . , vn :
tn)存在一个中间函数Ia,v1,...,vn 。
在模块中,如果中间函数Iv1,...,vn符合参数化的提供指令(或事件)a,则Iv1,...,vn中对a的实现的调用将传递v1, . . . , vn作为a的接口参数。
下面是对表达式call _[e01, . . . , e0m](e1, . . . , en)讨论:
       自变量e1, . . . , en被赋值为 v1, . . . , vn。
       自变量e01, . . . , e0m被赋值为v01 , . . . , v0m。
       v0i 对应ti 类型, 这里t i 是a的第i个接口叁数的类型。
       a对应的中间函数Iv01 ,...,v0m被以参数v1, . . . , vn,调用,返回列表L。
       如果 L 有一个或更多的元素, 在非参数化的情形下产生调用结果
      如果 L 为空,a的默认实现会被以自变量v1, . . . , vn,,以接口参数值v01 , . . . , v0m调用,且返回该调用的结果。7.4.2节表明如果L为空且a没有默认实现,则会产生编译-时间错误。
信号表达式的规则是一样的.
配线说明中的一个端点关系到一参数化规格元素时,有二种情形:
      端点指定叁数值 v1, . . . , vn。若端点符合指令或事件 a1, . . . ,am ,则相应的中间函数为Ia1,v1,...,vn,. . . , Iam,v1,...,vn且配线方式不变。
       端点未指定叁数值. 在这情况下,配线说明的两个端点都对应相同接口参数类型t1, . . . ,tn.的参数化规格元素。如果一个端点对应指令或事件 a1, . . . , am 而另一端点对应指令或事件 β1, . . . , βm,则对所有的1<=i<= m和所有的数组(w1 : t1, . . . ,wn : tn)有连接Iai,w1,...,wn<-> Iβi,w1,...,wn (就是说., 端点是为所有对应的叁数值连接的).。
7.4.2 应用级的需求
一个应用的配线说明必须满足两个需求, 否则就会发生编译时间错误:
       没有只包含中间函数的无限循环.
      在应用模块中的每个call a ( 或signal a)表达式中:
–如果调用是非参数化的:如果调用返回空的结果列表,则a一定有默认实现 (结果列表中元素个数只仰赖于配线)。
–如果调用是参数化的:如果a的接口叁数的任何替代值都返回空结果列表,则a必定有默认的实现 (给定参数值数组的返回结果列表中元素数目只仰赖于配线)。
注意这种情况不考虑用来在调用点叙述接口参数值的表达。
该调用的特色是包含在几个指令执行间的运行时选择——这是中间函数唯一的一处运行时开销
8 nesC 的协作
nesC采用由一旦运行直至完成作业(代表性的实时运算)和硬件异步触发中断控制构成的运行模型。编译器依靠用户提供的事件句柄和原语特征来识别中断源 (见10.3节)。nesC调度程序能以任意次序运行作业,但是必须服从一旦运行直至完成规则 (标准的TinyOS调度程序遵从FIFO(先进先出)策略).因为作业不能独占且是一旦运行直至完成的,所以它们是原子的互不妨碍的,但能够被中断。
由于这种并行运行模型,在程序共享的状态下特殊数据竞争,导致nesC 程序状态是不稳定的。比如,它的全局和模块内变量 (nesC不含动态存储配置). 为避免竞争,要么只在作业内部访问共享状态,要么只在原子的声明内部访问。编译时,nesC 编译器会报告潜在的数据竞争。
形式上, nesC 程序代码分为二个部份:
同步码 sync (SC):仅仅在作业内部可达的编码 (函数,指令,事件,作业)
异步码 async (AC):至少一个中断源可达的代码.
虽然非抢占消除作业之间的数据竞争, 但是在SC 和 AC,以及AC 和 AC 之间仍然有潜在的竞争。通常,任何从 AC可达的共享状态更新都是一个潜在的数据竞争. nesC 运行的基本常量是:
无竞争常量:任何共享状态更新要么仅同步码可达,要么仅发生在原子陈述内部. 只要所有对函数f 的调用是在原子陈述内部的,我们就认为对f 的调用是在原子陈述内部的。
这可能引入一种编译器不能够发现的竞争情况,但它一定是跨越多个原子陈述或作业的,并且是使用中间存储变量的。
nesC 可能报告实际上不会发生的数据竞争,举例来说., 如果所有的通路都被其他变量上的守卫保护。在这种情况下,为避免多于的消息,程序会用注释存储类型说明注释一个变量v,从而忽略所有关于v的数据竞争警告。注释关键字应谨慎使用。
 
对任何异步码的且没有声明异步的指令或事件,nesC 会报告编译- 时间错误。
这确保那些不安全的代码不会在中断时无意中被调用。
9 nesC 应用程序
一个 nesC应用程序有三个部份。:一连串的 C 声明和定义,一组接口类型,和一组组件。nesC 应用程序命名环境构造如下:
   最外层的全局命名环境,包含三个命名域: 一个 C 变量,一个用于C声明和定义的C 标签命名域,和一个用于组件和接口类型的组件和接口类型命名域。
    通常,C声明和定义可以在全局命名环境内部引入自己的嵌套命名域(用于函数声明和定义的函数内部代码段,等等)。
    每个接口类型引入一个命名域,用于保存接口的指令或事件。这种命名域是嵌套于全局命名环境的,所以指令和事件定义能影响全局命名环境中的C类型和标签定义。
     每个组件引入二个新命名域。规格命名域,嵌套于全局命名环境,包含一变量命名域用于存放组件规格元素。实现命名域, 嵌套于规格命名域,包含一个变量和一个标签命名域。
对于结构,作用范围变量命名域包含组件用以引用其包含组件的名字 (7.1节). 对于模块,作用范围保存作业,以及模块体中的C声明和定义。这些声明,及其它可能引入自己的嵌套在作用范围内的命名域 (比如函数体,代码段等等). 由于这种命名域的嵌套结构,模块中的代码可以访问全局命名环境中的C声明和定义,但是不能访问其他组件中的任何声明或定义.。
构成一个nesC应用程序的C声明和定义,接口类型和组件由一个随选的装载程序决定。nesC 编译器的输入是一个单独的组件K。nesC 编译器首先装载C文件 (第 9.1 节),然后装载组件K(9.2节)。程序所有代码的装载是装载这两个文件的过程的一部分。nesC 编译器假定所有对函数,指令及事件的调用不以自然的属性 (第 10.3 节) 都发生被装载的代码中(例如., 没有对非自然的函数 " 看不见的 " 调用)。
在装载文件预处理的时候,nesC 定义NESC 符号,用于识别nesC 语言和编译器版本的数字 XYZ。对于nesC 1.1, XYZ 至少为110。
装载C 文件,nesC组件及接口类型的过程包括定位对应的资源文件。文件定位的机制不是本参考手册中所要讨论的。要详细了解通用编译器是如何作业的,请阅读《the ncc man page.》
9.1 装载 C文件X
如果 X 已经被装载,就不用再做什么。否则, 就要定位并预处理文件 X.h。C宏定义 ( 由 # define和 #undef) 的改变会影响到所有的后面的文件预处理。来自被预处理的文件X.h的 C声明和定义会进入C全局命名环境,因此对所有的后来的 C文件加工,接口类型和组件是有影响的。
5举例来说,现在的 nesC 编译器使用这些信息除去无法访问的代码.
6NESC 符号不被在 nesC 的较早版本中定义.
9.2 装载组件K
如果K已经被装载,就不用再做什么。否则, 就要定位并预处理文件 X.nc。对C宏定义( 由 # define和 #undef)的变化被忽略。使用下面的语法分析预处理文件:
nesC-file:
includes-listopt interface
includes-listopt module
includes-listopt configuration
includes-list:
includes
includes-list includes
includes:
includes identifier-list ;
如果 X.nc没有定义模块K 或结构 K,将报告编译-时间错误。否则,所有的包含列表指定的C文件都将被装载 (9.1节)。然后,组件说明中用到的所有接口类型都将被装载(9.3节)。接着,处理组件说明(第5节). 如果 K 是一个结构, K 指定的 (第 7.1 节) 的所有组件被
装载 (9.2节)。最后,K的实现被处理 (第6节和第7节)。
9.3 载入接口类型I
如果I已经被装载,就不用再做什么。否则, 就要定位并预处理文件 X.nc。对C宏定义( 由 # define和 #undef)的变化被忽略。预处理文件同上面的nesC-文件一样分析。如果 X.nc没有定义接口I,将报告编译-时间错误。否则,所有的包含列表指定的C文件都将被装载 (9.1节)。接着,处理I的定义(第4节).。
作为组件或接口包含C 文件的例子,接口类型Bar可能包含用于定义Bar中使用的类型的C文件BarTypes.h:
Bar.nc:                                  BarTypes.h:
includes BarTypes;                        typedef struct {
interface Bar {                                int x;
command result_t bar(BarType arg1);           double y;
}                                      } BarType;
接口Bar的定义能参考Bar类型, 同样任何使用和提供接口Bar组件也能(装载任何这些组件说明或实现之前,都要先装载接口Bar,自然还有BarTypes.h)
10 多样性
10.1没有自变量的函数的C声明的旧风格
没有自变量的 nesC函数使用()声明, 而不是 (void)。后者的用法将报告编译-时间错误.。
旧式的C声明(用())和函数定义(在自变量之后指定参数列表)在接口和组件中是不允许的(会引起编译-时间错误)。
注意这些变化都不用于C文件(以便现有的.h 文件能被不变的使用).
10.2 // 注释
nesC 允许C,接口类型和组件文件中的 //注释 。
10.3 属性
nesC使用gcc的属性语法声明函数的一些属性,变量及类型。这些属性可以放置在声明(在声明符之后) 或函数定义.(在叁数列表之后)上。 x 的属性是全部x 的声明和定义.上的所有属性的集合。
nesC 的属性语法是:
init-declarator-list: also
init-declarator attributes
init-declarator-list , init-declarator attributes
function-definition: also
declaration-specifiersopt declarator attributesdeclaration-listoptcompound-statement
attributes:
attribute
attributes attribute
attribute:
attribute ( ( attribute-list ) )
attribute-list:
single-attribute
attribute-list , single-attribute
single-attribute:
identifier
identifier ( argument-expression-list )
gcc不允许函数定义中参数列表后面的属性.
nesC 支持五种属性:
     C:这一属性用于在一个模块的顶层作为C 声明或定义 d( 它被所有其他声明忽略). 这指明d应该出现在全局范围,而不是模块的组件作用域。这允许在C代码中使用(举例来说,如果它是一个函数,则可被调用)d。
       自然的: 这一个属性可用于任何函数 f (在模块或 C代码中)。.这指出对f的调用在源代码中是不可见的。典型地,函数自然地被中断源 ,和C主函数调用。第9节讨论 nesC 编译器在编译期间如何使用自然的属性。
       事件句柄: 这一个属性可用于任何函数 f (在模块或 C代码中)。它指出f 是一个中断处理函数, 自动被硬件调用。这意味着f 既是自然的又是异步码 (AC)。
     原子的事件句柄: 这一个属性可用于任何函数 f (在模块或 C代码中)。它指出f 是一个中断处理函数, 自动被硬件调用,屏蔽中断的运行。这意味着f 既是自然的又是异步码 (AC)。而且,f 运行时好像被封装进一个原子的陈述。
    联合 (fnname): 这一属性为类型定义声明中一个类型指定联合函数。联合函数指定该如何联合调用一指令或事件而"扇出“返回的多个结果。举例来说:
typedef uint8_t result_t __attribute__((combine(rcombine)));
result_t rcombine(result_t r1, result_t r2)
{
return r1 == FAIL ? FAIL : r2;
}
当联合指令(或事件)返回类型是t 时,叙述逻辑-相似的行为。详细的语义见第 7.4 节。
如果类型 t 的联合函数c没有类型t c(t,t),就会发生编译时间错误。
使用属性的例子:在文件 RealMain.td 中:
module RealMain { ... }
implementation {
int main(int argc, char **argv) __attribute__((C, spontaneous)) {
}
}
这个例子表明主函数实际上应该出现在 C全局命名空间 (C),所以连接器能找它。它还表明即使在程序任何地方都没有函数调用主函数,主函数同样能够被调用(自然的)。
10.4 编译-时间常量函数
nesC有新类型的常量表达式:常量函数。常量函数在语言里面定义的函数,编译时当作一个常数.
nesC 现在有二种常量函数:
      unsigned int unique(char *identifier)
返回值:如果程序包含n个有相同标示字符串的对unique的调用,每个调用返回一个0~n-1之间的无符号整数。例如有10个unique("abc"),则每一个unique("abc")的结果是一个1~9之间的数字,而且,不会相重复。故10个unique("abc")其实就是进行了一个从0到9的编号。
使用unique的目的是为了把一个唯一整数传递给参数化接口的实例,以便一个组件只要提供一个参数化接口就能唯一地识别连接到那个接口的各种不同组件。
      unsigned int uniqueCount(char *identifier)
返回值:如果程序包含 n个有相同标示字符串的对unique的调用,每个调用都返回n(既unique在程序中出现的个数)。
使用uniqueCount的目的是为了度量数组(或其他的数据结构)的大小,这些数组使用unique返回的数来进行索引的。
举例来说, 一个定时器服务是通过一个参数化接口来识别它的客户的(每个独立的定时器由此而来),使用unique可以使用uniqueCount来分配正确个数的定时器数据结构。

抱歉!评论已关闭.