Κατανεµηµένα Συστήµατα Ι Χειµερινό Εξάµηνο 2007-2008 Περίληψη Ο στόχος του εργαστηρίου είναι η υλοποίηση κατανεµηµένων αλγόριθµων κατασκευής επικαλυπτικών δένδρων µε την γλώσσα προγραµµατισµού nesc και την χρήση Active Messages στο περιβάλλον TinyOS. Μελέτη της συµπεριφοράς των αλγόριθµων µε την χρήση του TOSSIM σε γενικά δίκτυα. Υλοποίηση Αλγόριθµου Κατασκευής Επικαλυπτικού ένδρου Ενα επικαλυπτικό δέντρο T (G) ενός δικτύου G περιέχει όλες τις διεργασίες του δικτύου (κορυφές) και ορισµένα (ίσως όλα) κανάλια επικοινωνίας (ακµές). Η κατασκευή ενός επικαλυπτικού δέντρου προυποθέτει την επιλογή µιας διεργασίας u 0 που ϑα είναι η ϱίζα του δέντρου T (G). Οταν η κατασκευή ολοκληρωθεί, όλες οι διεργασίες πρέπει κατ ελάχιστον να γνωρίζουν την διεργασία γονέα τους στο δέντρο. Σε ορισµένες εφαρµογές είναι απαραίτητο κάθε διεργασία να γνωρίζει επιπλέον στοιχεία όπως το ύψος της στο δέντρο. Υπάρχουν πολλοί διαφορετικοί αλγόριθµοι κατασκευής επικαλυπτικών δέντρων οι οποίοι απευθύνονται σε ορισµένους τύπους συστηµάτων που χρησιµοποιούν συγκεκριµένες ιδιότητες του συστήµατος για να εκτελεστούν. Παρ όλα αυτά, όλοι οι αλγόριθµοι µπορούν να περιγραφούν µε τον ίδιο τρόπο όταν ϑέλουµε να ορίσουµε την λειτουργικότητα που προσφέρουν στα υψηλότερα επίπεδα ενός συστή- µατος. Η γλώσσα nesc προσφέρει ένα τρόπο περιγραφής της εσωτερικής λειτουργίας των αλγόριθµων µε γενικό τρόπο. Με την χρήση των interfaces µπορούµε να περιγράψουµε τις λειτουργίες των αλγό- ϱιθµων εκλογής αρχηγού µε γενικό, αφαιρετικό τρόπο. Ορίζουµε το interface SpanningTreeControl ως εξής : command result_t init(uint16_t deviceid) αρχικοποιεί τις εσωτερικές µεταβλητές του αλγόριθµου. Η παράµετρος deviceid υποδηλώνει την ταυτότητα που ϑα χρησιµοποιήσει η διεργασία κατα την κατασκευή του επικαλυπτικού δέντρου. Το command επιστρέφει πάντα SUCCESS. command result_t start() ξεκινά τη διαδικασία κατασκευής του επικαλυπτικού δένδρου. Επιστρέφει πάντα SUCCESS. command uint16_t getparentid() επιστρέφει την ταυτότητα του κόµβου-γονέα στο δένδρο, αλλιώς UNKNOWN_PARENT. Για την ϱίζα του δέντρου επιστρέφει την ταυτότητα της διεργασίας. command uint8_t getstatus() επιστρέφει JOINED αν η διεργασία έχει συνδεθεί στο δέντρο, αλλιώς NOT_JOINED. Σελ. 1 από 10
command result_t getheight() επιστρέφει το ύψος του κόµβου στο επικαλυπτικό δένδρο (0 αν είναι η ϱίζα, αυξάνεται όσο πλησιάζουµε προς τα ϕύλλα). event result_t joined(uint16_t parentid, uint16_t height) όταν ο κόµβος συνδεθεί στο επικαλυπτικό δέντρο, δηµιουργείται ένα event όπου η παράµετρος parentid δηλώνει την ταυτότητα του κόµβου-γονέα στο δέντρο και η παράµετρος height δηλώνει το ύψος του κόµβου στο δένδρο. Το event επιστρέφει πάντα SUCCESS. Ο κώδικας που αποτυπώνει το interface SpanningTreeControl στη γλώσσα nesc ϐρίσκεται στη σελίδα του µαθήµατος. Τοποθετήστε αυτό το αρχείο στον κατάλογο της εφαρµογής σας. Μια εφαρµογή που ϑέλει να δηµιουργήσει ένα επικαλυπτικό δένδρο (π.χ. για τη συλλογή πληρο- ϕορίας από όλους τους κόµβους) ϑα χρησιµοποιήσει το παραπάνω interface χωρίς να έχει ιδιαίτερη σηµασία ποια ϑα είναι η τελική υλοποίηση των µεθόδων (δηλ. πως ϑα υλοποιήσουµε τον αλγόριθµος κατασκευής του δέντρου). Υλοποιούµε την εφαρµογή lab2app, στην οποία η συσκευή µε ταυτότητα 0 ϑέλει να δηµιουργήσει ένα επικαλυπτικό δένδρο στο δίκτυο. Για να µπορεί ο χειριστής του συστήµατος να διαγνώσει την κατάσταση που ϐρίσκονται οι συσκευές κατά την λειτουργία τους, η εφαρµογή χρησιµοποιεί τα Led των συσκευών ως εξής : Το κόκκινο led ανάβει στη ϱίζα όταν αρχίσει να εκτελείται ο αλγόριθµος. Οταν ολοκληρωθεί η εκτέλεση και τερµατίσει ο αλγόριθµος, το πράσινο Led ανάβει. Οσο κατασκευάζεται το επικαλυπτικό δένδρο, στους υπόλοιπους κόµβους, το κίτρινο Led είναι αναµµένο. Οταν η διεργασία συνδεθεί στο δέντρο, το πράσινο Led ανάβει. Εφόσον έχουµε να κάνουµε µε ένα ασύγχρονο σύστηµα, υπάρχει περίπτωση οι συσκευές να µη ξεκινήσουν ταυτόχρονα αλλά να υπάρχει µια χρονική καθυστέρηση. Για να εξασφαλίσουµε ότι όλες οι συσκευές του συστήµατος είναι ενεργοποιηµένες και συµµετέχουν στην κατασκευή του επικαλυπτικού δεντρου χρησιµοποιούµε ένα Timer για να καθυστερήσουµε την εκτέλεση του αλγόριθµου. Η εκτέλεση του αλγόριθµου κατασκευής επικαλυπτικού δένδρου και η ενηµέρωση της ολοκλήρωσης του γίνεται µε την υλοποίηση ενός task (το οποίο όµως καλεί τη SpanningTreeControl.start() µόνο στον κόµβο 0) και ένα event handler. Σύµφωνα µε τα παραπάνω, ορίζουµε το module lab2appm ως εξής : module lab2appm { provides { interface StdControl ; uses { interface SpanningTreeControl ; interface Leds ; interface Timer ; implementation { // I n i t i a l i z e the component. command result_t StdControl. i n i t ( ) { c a l l Leds. i n i t ( ) ; Σελ. 2 από 10
c a l l SpanningTreeControl. i n i t (TOS_LOCAL_ADDRESS) ; // Start things up set the timer to f i r e once a f t e r 3000ms command result_t StdControl. start ( ) { return c a l l Timer. start (TIMER_ONE_SHOT, 3000); // Halt execution o f the application disables the clock component. command result_t StdControl. stop ( ) { return c a l l Timer. stop ( ) ; task void starttreeconstruction ( ) { // Start the t r e e construction process i f (TOS_LOCAL_ADDRESS == 0) { c a l l SpanningTreeControl. start ( ) ; c a l l Leds. redon ( ) ; dbg (DBG_TEMP, " Tree construction process started.\n " ) ; else { c a l l Leds. yellowon ( ) ; event r e s u l t_ t SpanningTreeControl. joined ( uint16_t parentid, uint8_ t height ) { dbg (DBG_TEMP, " Node %d joined the t r e e under Node %d with height %d.\n", TOS_LOCAL_ADDRESS, parentid, height ) ; c a l l Leds. yellowoff ( ) ; c a l l Leds. greenon ( ) ; // Start the whole process event result_t Timer. f i r e d ( ) { post starttreeconstruction ( ) ; Σελ. 3 από 10
Παρατηρήστε ότι κατά την λειτουργία του συστήµατος υπάρχει περίπτωση να προκύψουν (α) σφάλ- µατα τερµατισµού στις συσκευές του συστήµατος (π.χ. στην ϱίζα του δέντρου), (ϐ) παροδικά σφάλµατα όπου οι συσκευές λόγω επανεκκίνησης µπορεί να χάσουν την ταυτότητα της διεργασίας γονέα και το ύψος τους ή (γ) προσθήκες νέων συσκευών που δεν είναι συνδεδεµένες στο δέντρο. Ενας εύκολος τρόπος να αντιµετωπίσουµε αυτές τις περιπτώσεις, είναι να εκτελούµε περιοδικά τον αλγόριθµο κατασκευής του δέντρου αλλάζοντας την κλήση της Timer. Υλοποίηση του αλγόριθµου κατασκευής επικαλυπτικού δένδρου SpanningTree Ο αλγόριθµος SpanningTree κατασκευάζει ένα επικαλυπτικό δέντρο µε ϱίζα µια προκαθορισµένη διεργασία σε γενικά δίκτυα όπου οι διεργασίες δεν γνωρίζουν το σύνολο των διεργασιών (n). Πρόκειται για έναν αλγόριθµο που απαιτεί O (diam(g)) γύρους και ανταλλάσσει O (m) µηνύµατα. Η ϐασική έκδοση του αλγόριθµου δεν προσφέρει όλες τις πληροφορίες που περιγράφει το interface SpanningTreeControl. Για αυτό τον λόγο κάνουµε τις ακόλουθες παραδοχές : 1. Το command result_t getheight() επιστρέφει πάντα 0. 2. Το event joined(parentid, height) επιστρέφει την ταυτότητα της διεργασίας γονέα και ύψος 0. Λόγω της ασύγχρονης λειτουργίας του συστήµατος, η σωστή εκτέλεση του αλγόριθµου SpanningTree απαιτεί την ύπαρξη µιας ουράς για τα εξερχόµενα µηνύµατα. Για αυτό τον λόγο χρησιµοποιούµε το component QueuedSend που προσφέρει το ίδιο interface SendMsg µε το component GenericComm αλλά τοποθετεί τα µηνύµατα σε µια ουρά. Τα αρχεία που υλοποιούν το component QueuedSend είναι στον ϕάκελο /opt/tinyos-1.x/tos/lib/queue. Κατά την µεταγλώτισση της εφαρµογής µας, για να συµπεριληφθεί σωστά το component QueuedSend πρέπει να εισάγουµε στο Makefile την εξής γραµµή : PFLAGS= -I%T/lib/Queue Η υλοποίηση του αλγόριθµου AsynchSpanningTree, σε υψηλό επίπεδο, αποτελείται από το module AsynchSpanningTreeM που περιέχει την λογική της διαδικασίας, το component GenericComm που χρησιµοποιείται για την παραλαβή Active Messages και το component QueuedSend που χρησιµοποιείται για την αποστολή Active Messages. Το διάγραµµα διασύνδεσης απεικονίζεται γραφικά στην Εικ. 1. includes SpanningTreeMsg ; configuration AsynchSpanningTree { provides interface StdControl ; provides interface SpanningTreeControl ; implementation { components AsynchSpanningTreeM, QueuedSend, GenericComm; AsynchSpanningTreeM. SendMsg > QueuedSend. SendMsg[AM_SPANNINGTREEMSG] ; AsynchSpanningTreeM. ReceiveMsg > GenericComm. ReceiveMsg [AM_SPANNINGTREEMSG] ; StdControl = GenericComm; StdControl = QueuedSend; Σελ. 4 από 10
Σχήµα 1: Το διάγραµµα διασύνδεσης του AsynchSpanningTree SpanningTreeControl = AsynchSpanningTreeM ; Το αρχείο SpanningTreeMsg.h ορίζει την δοµή των µηνυµάτων του αλγόριθµου και τις απαραίτητες σταθερές. Η δοµή του µηνύµατος SpanningTreeMsg είναι πολύ απλή : Το πεδίο id χρησιµοποιείται για την αποθήκευση της ταυτότητας της διεργασίας που πρόκειται να στείλει το µήνυµα αναζήτησης. typedef struct SpanningTreeMsg { uint16_t id ; SpanningTreeMsg ; enum { AM_SPANNINGTREEMSG = 15, UNKNOWN_PARENT = 65534, JOINED = 1, NOT_JOINED = 0 ; Το module AsynchLCRM χρησιµοποιεί τα interface SendMsg και ReceiveMsg για την αποστολή και παραλαβή µηνυµάτων και προσφέρει το interface SpanningTreeControl. module AsynchSpanningTreeM { provides { interface SpanningTreeControl ; uses { interface SendMsg; interface ReceiveMsg ; Οι διεργασίες διατηρούν (α) µια µεταβλητή uint16_t m_id µε την ταυτότητα της διεργασίας, (ϐ) µια µεταβλητή uint8_t m_joined µε την κατάσταση µαρκαρισµένη/µη-µαρκαρισµένη, (γ) µια µεταβλητή uint16_t m_parentid µε την ταυτότητα της διεργασίας γονέας στο δέντρο, και (δ) µια µεταβλητή TOS_Msg m_msg για την αποστολή µηνυµάτων. implementation { uint16_t m_id; uint16_t m_parentid ; Σελ. 5 από 10
uint8_ t m_joined ; TOS_Msg m_msg;... Η αρχικοποίηση του αλγόριθµου γίνεται ως εξής : // I n i t i a l i z e the spanning tree construction process. async command result_t SpanningTreeControl. i n i t ( uint16_t deviceid ) { atomic { m_joined = NOT_JOINED; m_id = deviceid ; m_leaderid = UNKNOWN_PARENT; dbg (DBG_BOOT, " AsynchSpanningTree : i n i t i a l i z e d.\n " ) ; και οι δύο συναρτήσεις get υλοποιούνται απλά : // Get current parent ID. async command uint16_t SpanningTreeControl. getparentid ( ) { return m_parentid ; // Get current status. async command uint8_ t SpanningTreeControl. getstatus ( ) { return m_joined ; Η υλοποίηση της start ελέγχει κατά πόσο έχει ήδη ολοκληρωθεί η διαδικασία, δηλ. αν η parentid δεν έχει τιµή UNKNOWN_PARENT αλλά το ID της διεργασίας γονέας. Σε περίπτωση που δεν έχει ολοκληρωθεί η διαδικασία, η διεργασία στέλνει ένα µήνυµα αναζήτησης µε την ταυτότητα της. Παρατη- ϱήστε ότι η χρήση της καθολικής µεταβλητής γίνεται µε το πρόθεµα atomic. // Start the spanning t r e e construction process. async command result_t SpanningTreeControl. start ( ) { uint16_t parentid ; atomic parentid = m_parentid ; // Check i f already finished i f ( parentid! = UNKNOWN_PARENT) return FAIL ; dbg (DBG_USR1, " AsynchSpanningTree : started\n " ) ; // Send f i r s t search message Σελ. 6 από 10
post sendsearchmessage ( ) ; Τέλος, το interface SpanningTreeControl ορίζει και την χρήση του event joined. Η δηµιουργία νέων event γίνεται από το task reportjoined. Παρατηρήστε τον τρόπο δηµιουργίας νέων event µε το πρόθεµα signal. Παρατηρήστε ότι η χρήση των καθολικών µεταβλητών γίνεται µε το πρόθεµα atomic και την αντιγραφή τους σε τοπικές µεταβλητές. / Generate the event o f the completion o f the spanning t r e e construction process / task void reportjoined ( ) { uint16_t parentid ; atomic parentid = m_parentid ; dbg (DBG_BOOT, " AsynchSpanningTree : joined.\n " ) ; signal SpanningTreeControl. joined ( parentid, 0 ) ; Το module AsynchSpanningTree στέλνει µηνύµατα αναζήτησης σε όλες τις εξερχόµενες γειτονικές διεργασίες στις εξής περιπτώσεις : Στην εκκίνηση του αλγόριθµου µε την χρήση του task sendsearchmessage. Το µήνυµα περιέχει την ταυτότητα της διεργασίας ϱίζα (id =0). Οταν µια διεργασία λάβει ένα µήνυµα αναζήτησης και δεν έχει ακόµα ενταχθεί στο επικαλυπτικό δέντρο, ϑέτει την διεργασία από όπου έλαβε το µήνυµα ως γονέα της στο δέντρο και στέλνει ένα νέο µήνυµα αναζήτησης µε την χρήση του task sendsearchmessage. Το µήνυµα περιέχει την ταυτότητα της διεργασίας (id = m_id). // Send a search message to a l l neighboring processes task void sendsearchmessage ( ) { // Access message body SpanningTreeMsg msgdata = ( SpanningTreeMsg ) m_msg. data ; // Set message contents atomic msgdata >id = m_id; // Try to send the message c a l l SendMsg. send (TOS_BCAST_ADDR, s i z e o f ( SpanningTreeMsg ), &m_msg) ; dbg (DBG_TEMP, " AsynchSpanningTree : Sending \tspanningtreemsg(%d)\n", msgdata >id ) ; Το interface SendMsg ορίζει και την χρήση του event senddone. Υλοποιούµε έναν απλό event handler που κάνει µια κλήση στο task reportjoined (και ο αλγόριθµος τερµατίζει). Σελ. 7 από 10
// Respond to the <code>sendmsg. senddone</code> event event result_t SendMsg. senddone ( TOS_MsgPtr msg, bool success ) { // Access message body SpanningTreeMsg msgdata = ( SpanningTreeMsg ) msg >data ; dbg (DBG_TEMP, " Sent \tspanningtreemsg(%d)\n", msgdata >id ) ; // Report that process joined the t r e e post reportjoined ( ) ; Η παραλαβή των µηνυµάτων γίνεται µέσω του interface ReceiveMsg και τη διαχείριση του event ReceiveMsg. Η συνάρτηση ελέγχει τον τύπο του µηνύµατος και πράττει ανάλογα. // Process a message received event TOS_MsgPtr ReceiveMsg. r e c e i v e ( TOS_MsgPtr recv_packet ) { // Access message body SpanningTreeMsg msgdata = ( SpanningTreeMsg ) recv_packet >data ; uint16_t joined ; atomic joined = m_joined ; dbg (DBG_TEMP, " AsynchSpanningTree : Received \tspanningtreemsg(%d)\n", msgdata >id ) ; // Check i f we have already joined the t r e e i f ( joined == JOINED) { // we have already joined the t r e e // ignore message else { // join the tree under the ID of the process // that sent t h i s search message atomic { m_parentid = msgdata >id ; joined = JOINED; // Send new search message post sendsearchmessage ( ) ; return recv_packet ; Σελ. 8 από 10
Σχήµα 2: Το διάγραµµα διασύνδεσης της εφαρµογής lab2app ιασύνδεση της εφαρµογής lab2app µε τον αλγόριθµου AsynchSpanningTree Η διασύνδεση της εφαρµογής lab2app µε τον αλγόριθµου AsynchSpanningTree, σε υψηλό επίπεδο, αποτελείται από το module lab2appm που περιέχει την λογική της εφαρµογής, το component AsynchSpanningTree που υλοποιεί έναν αλγόριθµο κατασκευής επικαλυπτικού δέντρου, το component TimerC που χρησιµοποιείται για την εκκίνηση της διαδικασίας και το component LedsC για την έξοδο της κατάστασης της συσκευής. Το διάγραµµα διασύνδεσης απεικονίζεται γραφικά στην Εικ. 1. configuration lab2app { implementation { components Main, lab2appm, AsynchSpanningTree, LedsC, TimerC ; Main. StdControl > lab1appm. StdControl ; Main. StdControl > AsynchSpanningTree. StdControl ; lab2appm. SpanningTreeControl > AsynchSpanningTree. SpanningTreeControl ; lab2appm. Leds > LedsC ; lab2appm. Timer > TimerC. Timer [ unique ( " Timer " ) ] ; Εκτελέστε τον αλγόριθµο µε την χρήση των δύο τοπολογιών γενικών δικτύων που προσφέρονται στην σελίδα του µαθήµατος για 8 και 16 διεργασίες. Παρατηρείστε τα µηνύµατα εξόδου. Υλοποίηση του αλγόριθµου κατασκευής επικαλυπτικού δένδρου SpanningTreeHeight Τροποποιείστε τον αλγόριθµο SpanningTree έτσι ώστε οι διεργασίες να γνωρίζουν το ύψος τους από την ϱίζα του δέντρου. Οι αλλαγές που ϑα κάνετε συνοψίζονται ως εξής : 1. Οι διεργασίες ϑα διατηρούν µια µεταβλητή για το ύψος τους στο δέντρο. 2. Το µήνυµα αναζήτησης περιέχει και το ύψος της διεργασίας. 3. Η παραλαβή των µηνυµάτων µέσω του event ReceiveMsg ϑέτει την τιµή της µεταβλητής. Υλοποίηση του αλγόριθµου αναζήτησης κατα εύρος AsynchBFS Υλοποιείστε τον αλγόριθµο AsynchBFS έτσι ώστε η κατασκευή του επικαλυπτικού δέντρου να ικανοποιεί τις συνθήκες της αναζήτησης κατά εύρος : οι διεργασίες που ϐρίσκονται σε απόσταση d από την ϱίζα του δέντρου στο G, ϐρίσκονται στο επίπεδο d στο δέντρο T (G). Σελ. 9 από 10
Βασιζόµενοι στον αλγόριθµο SpanningTreeHeight, οι αλλαγές εστιάζουν στην περιοδική επαναληψη του µηνύµατος αναζήτησης (π.χ. κάθε 3000 sec). Κάθε ϕορά που λαµβάνει ένα νέο µήνυµα αναζήτησης, εξετάζει το ύψος και αλλάζει τη µεταβλητής m_parentid αν το µήνυµα αναζήτησης περιέχει µια γειτονική διεργασία µε µικρότερο ύψος απο τον γονέα. Εργαστηριακές Ασκήσεις 1. Εκτελέστε την εφαρµογή lab2app µε τον αλγόριθµο SpanningTree για τα γενικά δίκτυα 8 και 16 κόµβων. Πόσα µηνύµατα ανταλλάσουν οι διεργασίες ; Ποιό είναι το ύψος του δέντρου ; 2. Υλοποιείστε τον αλγόριθµο κατασκευής επικαλυπτικού δέντρου SpanningTreeHeight. 3. Εκτελέστε την εφαρµογή lab2app µε τον αλγόριθµο SpanningTreeHeight για τα γενικά δίκτυα 8 και 16 κόµβων. Πόσα µηνύµατα ανταλλάσουν οι διεργασίες ; Ποιό είναι το ύψος του δέντρου ; 4. Υλοποείστε τον αλγόριθµο αναζήτησης κατα εύρος AsyncBFS. 5. Εκτελέστε την εφαρµογή lab2app µε τον αλγόριθµο AsyncBFS για τα γενικά δίκτυα 8 και 16 κόµβων. Πόσα µηνύµατα ανταλλάσουν οι διεργασίες ; Ποιό είναι το ύψος του δέντρου ; Σελ. 10 από 10