第三章 TinyOS编程语言nesC

第四节 模块

    2.通过接口函数传递参数

    组件间交互的唯一方式是通过函数调用,这里的函数通常是指接口函数。通过接口函数在组件之间传递参数的方式有两种:赋值和引用(指针)。第一种方式是将数据复制到栈。在这种方式下,被调用函数可以自由存放和修改数据。第二种方式是在调用函数和被调用函数之间共享指针变量。在这种方式下,为了避免内存数据损坏和内存空间泄露,两个组件都要谨慎管理对数据的访问,因此应该尽量减少数据共享情况的出现。

    减少数据共享的一种简单的方法是在模块变量中不存放指针。在一些抽象数据组件中就使用了这种方法。抽象数据组件主要包括通用模块和带有指针参数命令的通用接口。将数据封装在组件中,使用接口中提供的命令来访问这些数据。由于使用指针作为参数的命令或者事件的持续时间是有限的,因此数据共享的时间也很短暂,减少了数据共享带来的问题,但仍然会出现数据不一致的问题。

    这种方法在分阶段调用中不适用。考虑下面的接口Send中的函数send和sendDone。

    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, uint8_t len);

    }

    接口Send也是一个分阶段接口。发送分组的Send接口中具有4个命令和1个事件。Send接口的使用者调用Send.send( )命令,即调用下层组件发送分组,当下层组件发送完分组后,会调用该接口中的sendDone( )事件通知上层组件。更具体地说,为了发送一个数据分组,接口Send的使用者调用命令Send.send( ),返回值是SUCCESS,表明已经将指向数据分组的指针(msg)传递给了接口的提供者(被调用者)。被调用者将在一个变量中存储该指针,改变状态并直接返回。如果接口Send的使用者在将数据分组传递给接口的提供者后修改了数据包,则数据分组可能会被损坏。例如,接口提供者中的调用函数在计算完整个数据分组的校验和后发送该分组,此后又修改了该数据分组中某些字段的值,导致数据分组的校验和不再与该分组匹配,在这种情况下,接收该数据分组的节点会认为收到一个错误的分组而丢弃它,造成了资源的浪费。

    为了避免上述问题,在TinyOS中提出了“所有权”规则:任何时间存放变量的内存块应归唯一的某个模块所有。在上述例子中,接口Send的使用者调用了命令Send.send( ),实际上就将指针msg所指的内存块的所有权从调用者(接口的使用者)交给了被调用者(接口的提供者),因此,在该分组真正发送完毕之前,即收到Send.sendDone( )事件之前,接口Send的使用者不是该内存块的所有者,所以不要对该内存块进行操作,这样可以避免共享指针带来的损坏内存数据的问题。为了充分利用内存,避免内存泄露问题,还应在Send.sendDone( )事件中将相应的内存块指针作为参数传递回接口的使用者,将内存块的所有权返还给它原来的所有者,使得原来的所有者可以继续使用该内存块。

    3.4.2  任务

    在模块实现中有一类推后调用的函数形式,被称为任务(task)。任务可以使某个计算逻辑推后一段时间执行。任务相对于其他任务具有原子性,在运行过程中,不必担心其他任务破坏自己的数据。任务应尽量短,否则会影响其他任务的执行,降低系统响应的灵敏性。

    定义任务(定义函数)和提交任务(调用函数)都在模块实现部分完成。可以使用两种方式来定义任务和提交任务。对于同一个任务,两种方式不能混用。

    1.第一种定义任务和提交任务的方式

    //定义任务                           //提交任务

    task void 任务名( ){                   post 任务名( );

    …

    }

    task是定义任务时使用的关键字,表明后面的函数是一个任务,任务没有返回值,没有参数。

    post是提交任务时使用的关键字。执行该语句,实际上是将任务名所指的任务挂入任务队列后立即返回。任务提交成功返回SUCCESS,否则返回EBUSY。

    2.第二种定义任务和提交任务的方式

    在TinyOS中提供了一个任务接口TaskBasic,该接口中包括一个提交任务的异步命令和一个通知运行任务的事件。使用该接口来定义任务和提交任务与第一种方式是等价的。

    interface TaskBasic{

      async command error_t postTask(); //提交任务

      event void runTask();  //运行任务

    }

    当组件用关键字“task”定义一个任务时,实际上就表示它使用了接口TaskBasic的一个实例。任务的主体就是事件“runTask”,当组件使用关键字“post”时,实际上是调用了命令postTask。每个任务接口都有唯一的标识作为参数来与调度器组件连接,而这个参数是通过以“TinySchedulerC.TaskBasic”字符串为实参的编译函数unique( )来获得的。编译函数unique( )将在第五章中介绍。