Aufgabe 1: Petri-Netze Geben Sie für die beiden angegebenen Petri-Netze jeweils an, ob sie deadlock-frei sind und begründen Sie Ihre Antwort. a) b) Aufgabe 2: Socket-Kommunikation Gegeben sei ein Client, der mit Hilfe der Socket-Kommunikation mit UDPProtokoll eine mit 0 beendete Folge von int-Zahlen an den Port 7777 eines Servers schickt. Schreiben Sie den UDP-Server, der in einer Schleife alle Zahlen (bis zum Wert 0) einliest, dabei deren Summe berechnet und dann das Ergebnis an den Client zurückschickt. include- Anweisungen können Sie dabei weglassen und auf das Abfangen von Fehlersituationen können Sie verzichten. 1 Aufgabe 3: RPC-Programmierung in C++ In dieser Aufgabe geht es um ein Client-Server System, bei dem der Server dem Client folgende Operationen in RNS-Arithmetik zur Verfügung stellt. base: assign: add: mult: incr: Festlegung der RNS-Basis durch einen Vektor (m0,m1,…,m9) Zuweisung an einen Akkumulator: accu=(X0,X1,…,x9) RNS-Addition: accu+=(X0,X1,…,x9) RNS-Multiplikation: accu*=(X0,X1,…,X9) RNS-Incrementieren: accu+=1 Für die RNS-Zahldarstellung soll folgender Typ feld verwendet werden. const rns_dimension=10; struct feld { int v[rns_dimension]; }; Um eine echte Nebenläufigkeit zwischen Client und Server zu erreichen, sollen die RPC-Prozeduren für die obigen Operationen keine Ergebnisse an den Client zurückliefern, sondern sie sollen sich nach der Parameterübergabe sofort beenden und die eigentliche Abarbeitung einem parallel laufenden Berechnungsthread überlassen. Zur Rückübertragung der erzielten Ergebnisses soll eine RPC-Funktion get benutzt werden, die kein Argument besitzt, die Beendigung der bisher veranlassten Berechnungen abwartet und dann den Inhalt des Akkumulators akku zurückgibt. Zur Illustration ist in Tabelle 1 ein Ausschnitt aus einem Client-Programm angegeben. Die darin aufgerufenen RPC-Prozeduren start und stop dienen lediglich dazu, die im Server benötigte Threads und Semaphoren zu erzeugen, bzw. zu löschen. In den Tabellen 2 und 3 ist die Server-Implementierung ausschnittsweise angegeben. Die RPC-Routinen für base, assign, add, mult und incr tragen mit der Hilfsprozedur command_to_queue (und einem Thread queue_thread) jeweils den Operator command und gegebenenfalls das Argument parameter in eine Warteschlange queue der Länge 20 ein. Diese Warteschlage wird durch ein zyklisch verwendetes Feld implementiert. Die Verfügbarkeit freier Einträge wird über einen Semaphore empty_queue_cells verwaltet und die Anzahl bereits gefüllter Einträge wird durch den Semaphore full_queue_cells dargestellt. Für die Abarbeitung der in der Warteschlange gespeicherten Kommandos ist der Thread computation_thread zuständig. Er wartet in einer Schleife auf die Verfügbarkeit eines queue-Elements, führt dann die entsprechende Berechnung durch und gibt das queue-Element wieder frei. 2 int main (int arge, char* argvD) {CLIENT* cl=clnt_create(argv[l], RNSPROG, RNSVERS, feld feld feld feld "TCP"); m={l7, 19, 23, 29, 31, 37, 41, 43, 47, 53}; // RNS-Basis a={10, 10, 12, 12, 8, 3, 0, 0, 0, 0}; b={13, 14, 15, 16, 17, 18, 19, 20, 21, 22}; c={ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; feld* y; void* result; result= start_l(NULL, cl); result= base_l(fcni, cl); // Server aktivieren // RNS-Basis festlegen // Rechnung: y = (a+b)*c +1 in RNS-Arithmetik result= assign_1(&a, cl); result= add_1(&b, cl); result= mult_1(&c, cl); result= incr_1(NULL,cl); y= get_1(NULL,cl); cout << "y="; // Ergebnisausgabe for (int i=0; i< rns_dimension; i++) cout << (*y).v[i] << “ “; cout << endl; result= stop_1(NULL, cl); // Server deaktivieren } Tabelle 1: Ausschnitt aus einem Client-Program, zum RNS-Server a) Schreiben Sie die Datei rns.x für das Client-Server-System. b) Beschreiben Sie das Verhalten des in Tabelle 2 und 3 dargestellten Servers durch ein Petri-Netz. Das Petri-Netz sollte "Client-Anfragen", RPC-Routinen, die Threads queue_thread und computation_thread, sowie alle verwendeten Semaphoren beinhalten. c) Geben Sie eine Implementierung der Server-Routine start_1_svc an, die sämtliche benötigen Semaphoren initialisiert und die Threads star tet. d) Geben Sie eine Implementierung der Server-Routine stop_1_svc an, die invers zu start_1_svc arbeitet. e) Schreiben Sie die Server-Routine get_1_svc, die den Inhalt der glo balen Variablen accu an den Client zurückliefert. Dazu muss zunächst auf die Abarbeitung aller Befehle aus der queue gewartet werden. 3 // RNS-Server ... #indude "rns.h" typedef void* zeiger; int base[ras.dimension]; // Basis-Vektor für RNS-Arithmetik feld accu; // RNS-Arbeitsregister // Aufzählungstyp für Kommandos, die im Hintergrund bearbeitet werden enum cmd {base.cmd, assign_cmd, add_cmd, mult.cmd, incr_cmd, decr.cmd}; struct commands {cmd c; int v[rns_dimension];}; const int queue_size=20; commands queue [queue_size]; int read_cmd=0; int write_cmd=O; // // // // Größe der queue für Kommandos Puffer für Kommandos Index zum Lesen aus queue Index zum Schreiben in queue cmd command; int parameter[rns.dimension]; int ok=0; // Threads pthread_t computation_thread; pthread_t queue_thread; // Berechnungsthread // Kommandos in queue eintragen // Semaphoren sem_t insert_cmd, insert_cmd_done; sem_t empty_queue_cells, full_queue_cells; void command_to_queue(cmd c, int* v) {command=c; if (v) for (int i=0; i<rns_dimension; parameter [i]= v[i]; sem_post(&insert_cmd); sem_wait(&insert_cmd_done); } // Hilfsprozedur i++) Tabelle 2: Globale Deklarationen und Prozedur command_to_queue des RNS-Servers 4 // RPC-Prozeduren void* base_1_svc(feld *p, svc_req* cl) {command_to_queue(base_cmd,(*p).v); return &ok; } void* assign_1_svc(feld *p, svc_req* cl) {command_to_queue(assign_cmd,(*p).v); return &ok; } void* add_1_svc(feld *p, svc_req* cl) {command_to_queue(add_cmd,(*p).v); return &ok; } void* mult_1_svc(feld *p, svc_req* cl) {command_to_queue(mult_cmd,(*p).v); return &ok; } void* incr_1_svc(void *p, svc_req* cl) {command_to_queue(incr_cmd,NULL); return &ok; } // Vom Thread "command_thread" auszuführende Prozedur void* f_computation(void* p) {while(1) {sem_wait(&full_queue_cells); // Warten auf Kommando in queue[read_cmd] cmd c=queue[read_cmd].c; // Auszuführendes Kommando if (c==base_cmd) for (int i=0; i<rns_dimension; i++) base[i] =queue[read_cmd].v[i]; else ... read_cmd=(read_cmd+1)%queue_size; // Element in queue wieder freigeben sem_post(&empty_queue_cells); } } // Vom Thread "queue_thread" auszuführende Prozedur void* f_command_to_queue(void* p) {while(1) {sem_wait(&insert_cmd); // Warten auf Vorliegen eines Kommandos sem_wait(&empty_queue_cells); // Warten auf freies Feld in queue queue[write_cmd].c=command; // Kommando in queue eintragen for (int i=0; i<rns_dimension; i++) // Argument in queue eintragen queue[write_cmd].v[i]=parameter[i]; write_cmd=(write_cmd+l)*/.queue_size; sem_post(&insert_cmd_done) ; // Eintrag wurde vorgenommen sem_post(&full_queue_cells); // neues Kommando in queue } } Tabelle 3: Programmteile des RNS-Servers 5 Aufgabe 4: Threads Um eine herkömmliche for-Schleife der Art for (int i=0; i<10; i++) y[i]= f(x[i]); mit einer rechenzeitintensiven Funktion int f (int) nebenläufig statt se quentiell durchzuführen, kann man wie im folgenden dargestellt, 10 separate Berechnungsthreads verwenden: void* fthread(void* x) { (int local_x=(int) (*x); int y=f(local_x); pthread_exit(&y); } ... main() {... pthread p[10]; int param; int* result; for (int i=0; i<10; i++) { param=x[i]; pthread_create(&(pthread[i]), NULL, fthread, ftparam); } for (int i=0; i<10; i++) { pthread_join(pthread[i], (void**)(&result)); pthread_detach(pthread[i]); y[i]= *result; } ... a) Erklären Sie kurz, warum man auch auf einem Rechner mit nur einer CPU eine Chance hat, dass die pseudo-parallele Ausführung schneller ist, als die sequentielle Schleifenabarbeitung. b) Nennen Sie einige Gründe, warum die pseudo-parallele Ausführung eventuell langsamer ist, als die sequentielle Ausführung der for-Schleife. c) Im obigen nebenläufigen Programm gibt es Fehler, die zu falschen Er gebnissen führen können. Erklären Sie kurz, was falsch gemacht wurde. 6 Aufgabe 5: RNS-Arithmetik Für die folgenden Prägen soll jeweils die RNS-Basis (5,9,11) verwendet werden. a) Welcher Zahlbereich kann mit der angegebenen RNS-Basis dargestellt werden? b) Stellen Sie die Werte 10 und 20 in RNS-Notation dar und berechnen Sie dann im RNS-System das Produkt (10 • 20). Sie brauchen das Ergebnis nicht zurücktransformieren. c) Die Zahl 2 hat die RNS-Darstellung (2,2,2). Um im RNS-Zahlsystem durch 2 zu dividieren, braucht man ein "inverses Element" (x1,x2,x3), so dass die RNS-Multiplikation ((x1,x2,x3)-(2,2,2)) das Ergebnis (1,1,1) liefert. Ermitteln Sie dieses "inverse Element". Demonstrieren Sie die Richtigkeit anschließend anhand der Division 50/2 in RNS-Arithmetik. Aufgabe 6: Allgemeines a) Der fork-Befehl kann in C und C++ benutzt werden, um Prozesse zu erzeugen. Welche Bedeutung hat sein Rückgabewert? b) Was ist bei der Socket-Kommunikation der wesentliche Unterschied zwi schen dem UDP- und dem TCP-Protokoll c) Wann spricht man bei der nebenläufigen Programmierung von "kriti schen Bereichen"? d) Welche Bedeutung hat das Kommando rmiregistry bei der RMIProgrammierung? e) Welche Bedeutung hat das Kommando rpcgen bei der RPC-Programmierung? Aufgabe 7: JAVA-RMI Tabelle 4 zeigt einen in JAVA geschriebenen RMI-Server. Seine Schnittstellenbeschreibung stellt nur eine Methode int getO zur Verfügung und lau tet: import java.rmi.Remote; import java.rmi.RemoteException; public interface DelphiInterface extends Remote {int getO throvs RemoteException; } Geben Sie an, welche Ausgabe der in Tabelle 5 dargestellte Client erzeugt. 7 import import import import import java.rmi.RemoteException; java.rmi.Server.UnicastRemoteObject; java.rmi.RMISe curityManager; java.rmi.Naming; Delphiinterface; public class DelphiServer extends UnicastRemoteObject implements DelphiInterface { int a,b; DelphiServer() throws RemoteException { super(); a=1; b=1; } public int get() throws RemoteException { int c=a+b; a=b; b=c; return c; } public static void main(String args[]) { try { System.setSecurityManager(new RMISecurityManager()); DelphiServer server=new DelphiServer(); Naming.rebind("Delphi-Server", server); } catch (java.net.MalformedURLException me) { System.out .println("Malformed URL :" + me.toString()); } catch (RemoteException re) { System.out.println("RemoteException :" + re.toString()); } } } Tabelle 4: JAVA-Implementeirung eines RMI-Servers 8 import import import import import import java.rmi.Remote; java.rmi.RemoteException; java.rmi.RMISecurityManager; java.rmi.Naming; java.rmi.NotBoundException; java.net.MalformedURLException; import DelphiInterface; public class DelphiClient /* extends Remote */ { public static void main(String argv[]) { if (System.getSecurityManager()=null) System.setSecurityManager(new RMISecurityManager()); try { String url= "//localhost/Delphi-Server"; DelphiInterface Delphi1= (DelphiInterface)Naming.lookup(url); for (int i=1; i<5; i++) System.out.println(Delphi1.get()); DelphiInterface Delphi2= (DelphiInterface)Naming.lookup(url); for (int i=1; i<5; i++) System.out.println(Delphi2.get()); } catch (java.rmi.NotBoundException exe) { System.out.println("ERROR NotBoundException()" + exc.toString()); } catch (java.net.MalformedURLException exe) { System.out.println("ERROR MalformedURLException " + exc.toString()); } catch (java.rmi.RemoteException exe) { System.out.println("ERROR RemoteException " + exc.toString()); } } } Tabelle 5: JAVA Client-Implementierung 9