Le système TinyOS, ses librairies, et ses applications sont écrites en nesC, un nouveau langage pour le développement d'applications orientées composants. Le langage nesC est principalement dédié aux systèmes embarqués comme les réseaux de capteurs. nesC a une syntaxe proche du langage C. mais supporte le modèle concurrent de TinyOS ainsi que des mécanismes pour la structuration, le nommage et l'assemblage de composants logiciels en des systèmes réseaux embarqués fiables. L'objectif principal est de permettre aux concepteurs d'applications de construire des composants qui peuvent être composés rapidement en des systèmes complets, concurrent, tout en permettant une vérification profonde à la compilation.
TinyOS définie un nombre important de concepts qui sont exprimés dans nesC. Premièrement, les applications nesC sont construites à partir de composants ayant des interfaces bidirectionnelles bien définies. Deuxièmement, nesC définie un modèle de concurrence basé sur les notions de tâches, les "handlers" d'événements matériels, et la détection des conditions d'accès concurrent aux données au moment de la compilation.
Une application nesC consiste en un ou plusieurs composants assemblés pour former un exécutable.
Un composant offre et utilise des interfaces. Ces interfaces sont l'unique point d'accès au composant et sont bidirectionnelles. Une interface définie un ensemble de fonctions appelées commandes (qui doivent êtes implémentées par le composant qui offre cette interface), et un autre ensemble de fonctions appelées événements (qui doivent êtres implémentées par le composant utilisant l'interface). Pour qu'un composant puisse appeler les commandes d'une interface il doit implémenter les événement définis dans l'interface. Un même composant pourrait offrir et utiliser plusieurs interfaces différentes ou plusieurs instances d'une même interface.
Il existe deux types de composants dans nesC: modules et configurations. Les modules définissent le code de l'application en implémentant une ou plusieurs interfaces. Les configurations sont utilisées pour assembler d'autres composants, en reliant les interfaces utilisées par des composants aux interfaces offertes par d'autres composants. ceci est appelé "wiring" (câblage). Toute application nesC est décrite par une configuration qui assemble les composants de l'application.
nesC utilise l'extension de fichier ".nc" pour tous les fichiers sources -- interfaces, modules, et configurations.
TinyOS execute seulement un programme consistant d'un ensemble de composants requis par l'application. Il existe deux chemins d'exécution : les tâches, et les "handlers" d'événements matériels.
Les tâches sont des fonctions dont l'exécution est différée. Une fois lancée, une tâche s'exécute jusqu'à sa fin et n'est pas et ne peut pas interrompre une autre tâche.
Les "handlers" d'événements matériels sont exécutés en réponse à une interruption matérielle et s'exécutent aussi jusqu'à leurs fins, mais peuvent interrompre l'exécution d'une tâche ou d'un autre "handler".
Les commandes et les événements qui s'exécutent dans un "handler" d'événement matériel doivent être déclarés avec le mot clé "async".
Comme les tâches et les "handlers" sont préemptibles par d'autres codes asynchrones, les programmes nesC son susceptibles à certaines conditions de concurrences d'accès aux données.
Pour éviter ces conditions d'accès concurrent, il faut soit accéder aux données partagées exclusivement à travers des tâches, ou en ayant tous les accès aux données partagées dans des instructions "atomic".
Le compilateur signale les accès potentiellement concurrents aux données partagées au programmeur. Il se peut que le compilateur signale des faux positifs Dans ce cas, une variable peut être déclarée avec le mot clé norace.
Le mot clé norace doit être utilisé avec une extrême précaution.