Беспроводные сенсорные сети.

Часть 3. Средства программирования

М.В.Сергиевский, С.Н.Сыроежкин

Операционная система TinyOS

Язык программирования NesC

 

Беспроводные сенсорные сети (БСС) состоят из миниатюрных вычислителей — мотов, снабженных сенсорами (температуры, освещенности, давления и т.д.) и приемопередающими радио­устройствами. Поскольку размер мота должен быть небольшим, его питание осуществляется от маломощной батареи. Моты применяются для сбора и первичной обработки сенсорных данных, которые они передают друг другу. Структура сенсорной сети формируется автоматически таким образом, чтобы в конечном счете все сенсорные данные были получены шлюзом, имеющим соединение с корпоративной сетью. Как уже отмечалось (см. КомпьютерПресс № 4’2008), современный технологический уровень развития БСС дает возможность перейти к практическому использованию сенсорных сетей, но для этого необходимо уметь разрабатывать программное обеспечение для мотов. В настоящее время для этой цели имеются специальные средства и даже технологии, к которым в первую очередь относятся операционная система TinyOS и язык программирования NesC.

TinyOS — это операционная система класса Open Source, разработанная специально для использования в БСС. Более того, TinyOS — первая ОС для БСС. Сама TinyOS написана на языке NesC, который разрабатывался именно для программирования приложений, ориентированных на работу в среде TinyOS, что позволило сделать работу программ, написанных на NesC, максимально эффективной. В связи с этим есть смысл сначала остановиться на принципах организации работы TinyOS, а затем перейти к конкретным инструментальным средствам языка NesC.

Операционная система TinyOS

TinyOS имеет компонентную архитектуру, при правильной компоновке обеспечивающую минимальный размер кода, что очень важно для сенсорных устройств, которые имеют строгие ограничения по объему памяти. Библиотека компонентов TinyOS включает сетевые протоколы, драйверы сенсоров и утилиты получения и сбора информации, которые могут быть усовершенствованы в клиентских приложениях. Реализованная в TinyOS событийная модель дает возможность управлять питанием на низком уровне, что позволяет экономить энергопотреб­ление. TinyOS перенесена более чем на дюжину аппаратных платформ и многочисленные сенсорные устройства.

TinyOS заметно отличается от ОС общего назначения, таких как UNIX, Windows и пр. Например, приложения для БСС не являются интерактивными в том же смысле, что и приложения для обычных ПК. Это объясняется тем, что TinyOS не нуждается во встроенной поддержке пользовательского интерфейса. К тому же ограничения в ресурсах памяти, с одной стороны, и аппаратная поддержка распределения памяти — с другой, делают такие механизмы, как виртуальная память, ненужными или даже невозможными в реализации.

При разработке TinyOS основное внимание было уделено обеспечению малого энергопотребления и возможности использования для программирования языка c довольно высоким уровнем абстракции. В результате была создана ОС с простой, но весьма развитой компонентной архитектурой. Специфика этой архитектуры заключается в обеспечении развитых и надежных механизмов параллельного выполнения задач в условиях крайне ограниченных ресурсов. Описанные выше причины привели разработчиков TinyOS к выбору модели, основанной на событиях.

Архитектура TinyOS включает две главные функциональные составляющие: планировщик задач и компонент. Понятие «компонент» в TinyOS несколько отличается от общепринятого. Так, интерфейс компонента TinyOS состоит из двух частей: верхней (upper), предоставляемой этим компонентом как провайдером, и нижней (lower), требуемой для его функционирования. Обе части содержат описания команд и событий.

Компонент имеет два основных элемента: набор команд и набор обработчиков событий. Кроме того, в каждом компоненте объявляются события, о которых он сигнализирует, и команды, используемые данным компонентом. Эти объявления применяются при компоновке для формирования конфигурации системы, настроенной на определенный класс приложений. Процесс компоновки распределяет компоненты по уровням, где каждый более высокий уровень посылает команды нижележащему уровню, а тот, в свою очередь, обращается к более высокому уровню с помощью сигналов о событиях. Аппаратное обес­печение является самым нижним уровнем в иерархии компонентов.

Использование статического распределения памяти позволяет определять требования к памяти на этапе компиляции и избегать накладных расходов, связанных с динамическим распределением памяти. Кроме того, такой подход позволяет сокращать время выполнения благодаря статическому размещению переменных во время компиляции вмес­то доступа к ним по указателям во время выполнения.

Команды являются неблокируемыми запросами к компонентам нижележащего уровня. Чтобы не возникало продолжительных задержек, время отклика вызванной команды не должно превышать заданного интервала времени, при этом команда должна вернуть флаг, указывающий, успешно она завершилась или нет. Отметим, что команды не могут подавать сигналы о событиях.

Обработчики событий прямо или косвенно имеют дело с аппаратными событиями. Самый нижний уровень компонентов содержит обработчики, непосредственно связанные с аппаратными прерываниями. Аппаратное событие инициирует процесс обработки, который распространяется вверх по уровням через события и может вернуться вниз с помощью команд.

Как и в остальных ОС, в TinyOS существуют атомарные потоки (task), которые выполняются вплоть до своего завершения, хотя и могут быть вытеснены событиями. С компонентом может быть связано более одного потока. Потоки могут вызывать команды нижележащего уровня, сигнализировать о событиях более высокому уровню и планировать другие потоки внутри компонента. Семантика потока «выполнение вплоть до завершения» позволяет иметь один стек, который выделяется выполняющемуся в данный момент потоку, что очень важно в системах с ограниченной памятью. Потоки дают возможность имитировать параллельную обработку внутри каждого компонента, так как они выполняются асинхронно по отношению к событиям. Однако потоки не должны блокироваться или простаивать в ожидании, поскольку в таких случаях они будут препятствовать развитию обработки в других компонентах.

Язык программирования NesC

NesC (network embedded system C) — это компонентно-ориентированный язык программирования, построенный на базе C. Основной структурной единицей программы на NesC является компонент, который через интерфейсы взаимодействует с другими компонентами. Интерфейсы в NesC двунаправленны, они определяют способы взаимодействия между двумя типами компонентов: пользователями и провайдерами. Интерфейсы могут содержать объявления команд и событий. Реализации команд должны быть описаны в компоненте-провайдере, а реализации обработчиков объявленных в интерфейсе событий — в компоненте-пользователе:

interface SendMsg {

command result_t send(uint16_t address, uint8_t length, TOS_MsgPtr msg);

event result_t sendDone(TOS_MsgPtr msg, result_t success);

}

В приведенном примере представлена часть стандартного интерфейса SendMsg для отправки сообщения в сеть. Компонент-провайдер этого интерфейса предоставляет команду send, которой может воспользоваться компонент-пользователь для отправки сообщения, а после отправления сообщения компонент-провайдер вернет компоненту-пользователю сигнал о событии sendDone, значением параметра которого будет результат операции отправки сообщения (см. рисунок).

 

В NesC существует два типа компонентов: модули и конфигураторы. Модули описывают действующие механизмы компонента: команды, которые он предоставляет, и обработчики событий. Обычно разработка модулей не вызывает больших сложностей, если разработчик знаком с программированием на стандартном языке С. Конфигураторы описывают связи между интерфейсами других компонентов, а также могут предоставлять свои интерфейсы (экспортировать интерфейсы). Несмотря на то что при реализации конфигуратора используется весьма простой язык, содержащий всего три оператора: «->», «<-», «=», — написание конфигуратора является довольно нетривиальной задачей, поскольку далеко не всегда можно сразу продумать эффективные связи между интерфейсами компонентов.

Описание любого компонента в NesC, будь то конфигуратор или модуль, состоит из двух секций: интерфейсной и секции реализации. Интерфейсная секция компонента содержит объявления команд, событий и интерфейсов (в дальнейшем команды, события и интерфейсы мы будем называть одним общим термином — функции), которые использует или предоставляет компонент. Если компонент использует функцию, на это указывает зарезервированное слово uses, если предоставляет — provides. Эта секция также может быть пустой, что свойственно конфигураторам, или содержать объявления только используемых или только предоставляемых функций. Взаимодействие с другими компонентами осуществляется через объявленные функции.

Приведем пример описания модуля, поставляемого вместе с TinyOS:

module BlinkM {

// интерфейсная секция

provides {

   interface StdControl;

}

uses {

   interface Timer;

   interface Leds;

}

implementation {

// секция реализации

 command result_t StdControl.init() {

   call Leds.init();

   return SUCCESS;

} command result_t StdControl.start() {

   return call Timer.start(TIMER_REPEAT, 1000);

}

command result_t StdControl.stop() {

   return call Timer.stop();

}

event result_t Timer.fired()

{

   call Leds.redToggle();

   return SUCCESS;

}

}

}

Компонент-модуль BlinckM предоставляет один интерфейс StdControl и использует два интерфейса: Timer и Leds. Стоит отметить, что используемые интерфейсы Timer и Leds фактически являются только объявлениями некоторых команд и/или событий, реализация которых должна быть описана в других компонентах, которые предоставляют интерфейсы Timer и Leds. Другими словами, компонент-провайдер и компонент-пользователь для предоставления первым второму каких-либо функций должны иметь одинаковые объявления разделяемых функций.

Теперь, когда мы разобрались, как объявлять функции компонентов, рассмотрим секцию реализации модуля, то есть тексты команд и обработчиков событий. Конечно, мы не сможем рассмотреть все особенности языка NesC, поэтому остановимся лишь на общей структуре секции реализации модуля. В ней обязательно должны быть тексты всех предоставляемых модулем команд (они объявлены в интерфейсной секции модуля после зарезервированного слова provides). Также обычно требуется реализация обработчиков событий, которые модуль принимает от компонентов-провайдеров. Выше приведена реализация трех команд — StdControl.init(), StdControl.start() и StdControl.stop(), которые объявлены в интерфейсе StdControl, а также описан обработчик события — срабатывания таймера. Таймер будет запускаться циклически с периодом в 1000 мс при вызове команды StdControl.start(). Результатом обработки этого события будет включение красного индикатора сенсорного устройства.

Однако объявлений в секции связи модуля явно недостаточно для связывания компонентов, поскольку объявления не указывают компоненту-пользователю, какой компонент для той или иной функции будет провайдером. Для того чтобы пользователь смог найти нужный компонент-провайдер, применяются конфигураторы, связывающие пользователей с провайдерами. Приведем пример конфигуратора для модуля BlinkM:

configuration BlinkС{

//интерфейсная секция

}

implementation {

// реализация

components Main, BlinkM, TimerC, LedsC;

   Main.StdControl -> TimerC.StdControl;

Main.StdControl -> BlinkM.StdControl;

   BlinkM.Timer -> TimerC.Timer[unique(“Timer”)];

BlinkM.Leds -> LedsC.Leds;

}

Как видно из примера, в компоненте-конфигураторе BlinkС отсутствует интерфейсная секция, а значит, сам конфигуратор BlinkС не предоставляет и не использует никаких функций, а лишь связывает другие компоненты. В секции реализации присутствуют четыре компонента. Синтаксис оператора связи «->» следующий:

<компонет-пользователь>.<имя_интерфейса> -> <компонент_провайдер>[.<имя_нтерфейса>];

Если провайдер предоставляет только один интерфейс, то название интерфейса у провайдера можно опустить.

Часто при программировании необходимо использовать потоки, называемые в NesC задачами. Принцип работы задач описан выше; добавим лишь, что синтаксически задачи — это функции типа void без аргументов. Задачи рекомендуется использовать в случае, если необходимо сигнализировать о событии или реализовать псевдопараллелизм вычислений. Приведем пример объявления:

task void readDoneTask();

и вызова задачи:

post readDoneTask();

Итак, приложение для TinyOS представляет собой набор компонентов, каждый размером примерно 200 байт, и интерфейсов для межкомпонентного взаимодействия. Для каждого конкретного приложения формируется свой набор компонентов. Полученное приложение на этапе компиляции для конечной платформы (iris, mica2, telos, и т.д.) интегрируется с ядром системы в один выполняемый файл, который и загружается на сенсорный узел.

Язык программирования NesC обладает большим количеством стандартных компонентов и интерфейсов, посредством которых можно писать серьезные приложения для сенсорных узлов. Компилируются написанные приложения при помощи специальных программ — кросскомпиляторов на обычных ПК. Естественно, что для БСС разрабатываются распределенные приложения и для полноценного тестирования нужна сеть, содержащая большое количество узлов, а это сильно затрудняет процесс тестирования и отладки. Выходом из такого положения является использование программ-эмуляторов.

 

В начало В начало

КомпьютерПресс 8'2008

Наш канал на Youtube

1999 1 2 3 4 5 6 7 8 9 10 11 12
2000 1 2 3 4 5 6 7 8 9 10 11 12
2001 1 2 3 4 5 6 7 8 9 10 11 12
2002 1 2 3 4 5 6 7 8 9 10 11 12
2003 1 2 3 4 5 6 7 8 9 10 11 12
2004 1 2 3 4 5 6 7 8 9 10 11 12
2005 1 2 3 4 5 6 7 8 9 10 11 12
2006 1 2 3 4 5 6 7 8 9 10 11 12
2007 1 2 3 4 5 6 7 8 9 10 11 12
2008 1 2 3 4 5 6 7 8 9 10 11 12
2009 1 2 3 4 5 6 7 8 9 10 11 12
2010 1 2 3 4 5 6 7 8 9 10 11 12
2011 1 2 3 4 5 6 7 8 9 10 11 12
2012 1 2 3 4 5 6 7 8 9 10 11 12
2013 1 2 3 4 5 6 7 8 9 10 11 12
Популярные статьи
КомпьютерПресс использует