QoS-orientierte Kommunikation über Ethernet für
Transcription
QoS-orientierte Kommunikation über Ethernet für
Fachhochschule Wiesbaden Fachbereich Informatik Diplomarbeit zur Erlangung des akademischen Grades Diplom-Informatiker (FH) QoS-orientierte Kommunikation über Ethernet für verteilte, Linux-basierte Automatisierungsanwendungen vorgelegt von: Marco Kosinski am: 25.04.2005 Referent: Korreferent: Prof. Dr. Reinhold Kröger Prof. Dr. Martin Gergeleit II Erklärung Hiermit erkläre ich an Eides statt, daß ich die vorliegende Diplomarbeit selbständig und nur unter Verwendung der angegebenen Hilfsmittel und Literaturquellen verfaßt habe. Wiesbaden, 25.04.2005 Marco Kosinski Hiermit erkläre ich mein Einverständnis mit den im Folgenden aufgeführten Verarbeitungsformen dieser Diplomarbeit: Verarbeitungsform Einstellung der Arbeit in die Bibliothek der FHW Veröffentlichung des Titels der Arbeit im Internet Veröffentlichung der Arbeit im Internet Wiesbaden, 25.04.2005 ja nein √ √ √ Marco Kosinski i ii Inhaltsverzeichnis 1 Einführung 1 2 Grundlagen 5 2.1 2.2 2.3 Quality of Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.1.1 Klassifizierung . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1.2 Paket Scheduling, Traffic Shaping und Policing . . . . . . . . . 10 2.1.2.1 Strict Priority Queueing . . . . . . . . . . . . . . . . 11 2.1.2.2 Weighted Round Robin Queueing . . . . . . . . . . . 12 2.1.2.3 Token Bucket Algorithmus . . . . . . . . . . . . . . . 13 Ethernet im Echtzeitbetrieb . . . . . . . . . . . . . . . . . . . . . . . 13 2.2.1 Lastreduzierung . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.2.2 Time Devision Multiple Access . . . . . . . . . . . . . . . . . 15 2.2.3 Einsatz von Switches . . . . . . . . . . . . . . . . . . . . . . . 16 2.2.4 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.3.1 Packet-Sockets . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.3.2 802.1Q VLAN Modul . . . . . . . . . . . . . . . . . . . . . . . 20 2.3.3 2.3.2.1 Aufbau und Funktionsweise . . . . . . . . . . . . . . 20 2.3.2.2 Konfiguration . . . . . . . . . . . . . . . . . . . . . . 21 Queueing Disziplinen . . . . . . . . . . . . . . . . . . . . . . . 24 2.3.3.1 Funktionsweise . . . . . . . . . . . . . . . . . . . . . 24 2.3.3.2 Aufbau . . . . . . . . . . . . . . . . . . . . . . . . . 26 iii 3 Analyse 3.1 3.2 3.3 3.4 29 Anforderungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.1.1 Anfordern von QoS . . . . . . . . . . . . . . . . . . . . . . . . 30 3.1.2 Steuern und Regeln . . . . . . . . . . . . . . . . . . . . . . . . 30 3.1.3 Alarm-Signalisierung . . . . . . . . . . . . . . . . . . . . . . . 31 3.1.4 Datei-Download / Upload . . . . . . . . . . . . . . . . . . . . 31 3.1.5 Human-Machine-Interface . . . . . . . . . . . . . . . . . . . . 31 3.1.6 Sonstige Anforderungen . . . . . . . . . . . . . . . . . . . . . 32 3.1.6.1 Entwicklung unter Linux . . . . . . . . . . . . . . . . 32 3.1.6.2 Portierbarkeit . . . . . . . . . . . . . . . . . . . . . . 33 3.1.6.3 Modularität . . . . . . . . . . . . . . . . . . . . . . . 33 3.1.6.4 Performanz-Optimierung auf Senden und Empfangen 33 Kommunikationsformen und ihre QoS-Anforderungen . . . . . . . . . 33 3.2.1 Steuern und Regeln . . . . . . . . . . . . . . . . . . . . . . . . 33 3.2.2 Alarm-Signalisierung . . . . . . . . . . . . . . . . . . . . . . . 34 3.2.3 Datei-Download / Upload . . . . . . . . . . . . . . . . . . . . 34 3.2.4 Human-Machine-Interface . . . . . . . . . . . . . . . . . . . . 35 Einflussnahme auf die QoS-Parameter . . . . . . . . . . . . . . . . . . 35 3.3.1 Service Availability . . . . . . . . . . . . . . . . . . . . . . . . 38 3.3.2 Packet Loss Rate . . . . . . . . . . . . . . . . . . . . . . . . . 38 3.3.3 Delay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 3.3.3.1 Delay im Sendeknoten . . . . . . . . . . . . . . . . . 39 3.3.3.2 Delay auf dem Medium . . . . . . . . . . . . . . . . 42 3.3.3.3 Delay im Empfangsknoten . . . . . . . . . . . . . . . 43 3.3.4 Jitter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.3.5 Throughput . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 API der Linux-Queueing-Disziplinen . . . . . . . . . . . . . . . . . . 44 3.4.1 Kommunikation über Netlink-Sockets . . . . . . . . . . . . . . 44 3.4.2 Addressierung der Netlink-Messages . . . . . . . . . . . . . . . 45 3.4.3 Aufbau der Netlink-Messages . . . . . . . . . . . . . . . . . . 46 3.4.3.1 3.4.4 Traffic-Control-Messages . . . . . . . . . . . . . . . . 48 Verfügbare Queueing Disziplinen . . . . . . . . . . . . . . . . 50 iv 3.4.5 3.4.4.1 First-In-First-Out (bfifo, pfifo, pfifo fast) . . . . . . . 50 3.4.4.2 Stochastical Fair Queueing (sfq) . . . . . . . . . . . . 51 3.4.4.3 Prio (prio) . . . . . . . . . . . . . . . . . . . . . . . 52 3.4.4.4 Hierarchical Token Bucket (htb) Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 3.4.5.1 3.5 . . . . . . . . . . . 52 Der U32-Filter . . . . . . . . . . . . . . . . . . . . . 57 Designentscheidungen . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 3.5.1 Statische Verkehrscharakteristik . . . . . . . . . . . . . . . . . 60 3.5.2 Linux ohne Echtzeiterweiterung . . . . . . . . . . . . . . . . . 60 3.5.3 Einsatz von Switches . . . . . . . . . . . . . . . . . . . . . . . 60 4 Konzept 61 4.1 Grobentwurf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 4.2 Die Konfiguration (chConfRead) . . . . . . . . . . . . . . . . . . . . . 64 4.3 Die Bibliotheksschnittstelle (chContr) . . . . . . . . . . . . . . . . . . 66 4.4 Der Kanalendpunkt (chDescr) . . . . . . . . . . . . . . . . . . . . . . 72 4.5 Prozesszugriffsverwaltung (chAvailContr/ chAvail) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 4.6 Netzwerk-Device (chDev) . . . . . . . . . . . . . . . . . . . . . . . . . 76 4.7 QoS-Controller (chQoSContr) . . . . . . . . . . . . . . . . . . . . . . 78 5 Implementierung 85 5.1 Implementierungsumgebung . . . . . . . . . . . . . . . . . . . . . . . 86 5.2 chConfRead . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 5.3 chContr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 5.4 chDescr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 5.5 chAvailContr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 5.6 chDev . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 5.7 chQoSContr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 5.8 Testapplikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 5.9 Gesamtaufwand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 5.10 Kompilierung und Installation . . . . . . . . . . . . . . . . . . . . . . 104 5.11 Aufgetretene Probleme . . . . . . . . . . . . . . . . . . . . . . . . . . 105 v 6 Bewertung 107 6.1 Messumgebung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 6.2 Messgrößen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 6.3 Instrumentierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 6.3.1 Ende-zu-Ende Propagation . . . . . . . . . . . . . . . . . . . . 108 6.3.2 Delay durch sendto-Systemcall und QDisc . . . . . . . . . . . 108 6.4 Lastmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 6.5 Lastquelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 6.6 Messergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 6.7 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 7 Zusammenfassung und Ausblick 117 8 Literaturverzeichnis 119 A Prioritätentabelle 123 B Diagramme 125 B.1 HTB/Prio QDisc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 B.2 HTB/Prio End-to-End . . . . . . . . . . . . . . . . . . . . . . . . . . 127 B.3 Prio QDisc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 B.4 Prio End-to-End . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 B.5 FIFO QDisc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 B.6 FIFO End-to-End . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 B.7 HTB/Prio QDisc variierender Burst-Parameter . . . . . . . . . . . . 135 B.8 Ein Prio 1 Kanal ohne Störlast . . . . . . . . . . . . . . . . . . . . . 136 B.9 Ein Prio 1 Kanal mit Störlast . . . . . . . . . . . . . . . . . . . . . . 137 B.10 Ein Prio 7 Kanal ohne Störlast . . . . . . . . . . . . . . . . . . . . . 138 B.11 Ein Prio 7 Kanal mit Störlast . . . . . . . . . . . . . . . . . . . . . . 139 C Inhalt der CD 141 vi Abbildungsverzeichnis 2.1 IEEE 802.1Q VLAN Tag . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.2 Beispiel: Strict Priority Queueing . . . . . . . . . . . . . . . . . . . . 11 2.3 Beispiel: Weighted Round Robin Queueing . . . . . . . . . . . . . . . 12 2.4 Beispiel: Token Bucket Algorithmus . . . . . . . . . . . . . . . . . . . 14 2.5 Aufbau eines Switch . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.6 Aufbau der VLAN-Devices . . . . . . . . . . . . . . . . . . . . . . . . 20 2.7 Funktionsweise von Queueing Disziplinen . . . . . . . . . . . . . . . . 25 2.8 Aufbau von Queueing Disziplinen . . . . . . . . . . . . . . . . . . . . 27 3.1 Use-Case Diagramm . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.2 Service- und Ankunftskurve . . . . . . . . . . . . . . . . . . . . . . . 37 3.3 Netlink-Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.4 Traffic-Control-Message . . . . . . . . . . . . . . . . . . . . . . . . . . 48 3.5 Attribute einer Traffic-Control-Message . . . . . . . . . . . . . . . . . 50 4.1 Konzeptionelles Klassenmodell . . . . . . . . . . . . . . . . . . . . . . 62 4.2 Übersicht Klassendiagramm . . . . . . . . . . . . . . . . . . . . . . . 64 4.3 Übersicht Klassendiagramm . . . . . . . . . . . . . . . . . . . . . . . 65 4.4 Kollaborationsdiagramm zu initChannels() . . . . . . . . . . . . . . 67 4.5 Kollaborationsdiagramm zu closeChannels() . . . . . . . . . . . . . . 68 4.6 Kollaborationsdiagramm zu chOpen() . . . . . . . . . . . . . . . . . . 69 4.7 Kollaborationsdiagramm zu chClose() . . . . . . . . . . . . . . . . . 70 4.8 Kollaborationsdiagramm zu chOpen() . . . . . . . . . . . . . . . . . . 71 4.9 Kollaborationsdiagramm zu chRecv() . . . . . . . . . . . . . . . . . . 72 4.10 chDesrc-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 4.11 Konzept für den gepufferten Empfang . . . . . . . . . . . . . . . . . . 74 vii 4.12 chAvailContr- und chAvail-Klasse . . . . . . . . . . . . . . . . . . . . 75 4.13 registerChannel-Methode . . . . . . . . . . . . . . . . . . . . . . . . . 76 4.14 Die chDev-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 4.15 Die chQoSContr-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . 78 4.16 Basis-QDisc-Konfiguration . . . . . . . . . . . . . . . . . . . . . . . . 80 4.17 Best-Effort-QDisc-Konfiguration . . . . . . . . . . . . . . . . . . . . . 81 4.18 QDisc-Konfiguration für Kanäle . . . . . . . . . . . . . . . . . . . . . 82 5.1 Semaphore für Prozess- und Threadsynchronisation . . . . . . . . . . 89 5.2 Speichermodell für chDescr-Objekte . . . . . . . . . . . . . . . . . . . 90 B.1 QDisc 200Byte HTB/Prio . . . . . . . . . . . . . . . . . . . . . . . . 125 B.2 QDisc 500Byte HTB/Prio . . . . . . . . . . . . . . . . . . . . . . . . 126 B.3 QDisc 1496Byte HTB/Prio . . . . . . . . . . . . . . . . . . . . . . . . 126 B.4 End-to-End 200Byte HTB/Prio . . . . . . . . . . . . . . . . . . . . . 127 B.5 End-to-End 500Byte HTB/Prio . . . . . . . . . . . . . . . . . . . . . 128 B.6 End-to-End 1496Byte HTB/Prio . . . . . . . . . . . . . . . . . . . . 128 B.7 QDisc 200Byte Prio . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 B.8 QDisc 500Byte Prio . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 B.9 QDisc 1496Byte Prio . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 B.10 End-to-End 200Byte Prio . . . . . . . . . . . . . . . . . . . . . . . . 130 B.11 End-to-End 500Byte Prio . . . . . . . . . . . . . . . . . . . . . . . . 131 B.12 End-to-End 1496Byte Prio . . . . . . . . . . . . . . . . . . . . . . . . 131 B.13 QDisc 200Byte FIFO . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 B.14 QDisc 500Byte FIFO . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 B.15 QDisc 1496Byte FIFO . . . . . . . . . . . . . . . . . . . . . . . . . . 133 B.16 End-to-End 200Byte FIFO . . . . . . . . . . . . . . . . . . . . . . . . 133 B.17 End-to-End 500Byte FIFO . . . . . . . . . . . . . . . . . . . . . . . . 134 B.18 End-to-End 1496Byte FIFO . . . . . . . . . . . . . . . . . . . . . . . 134 B.19 QDisc 500Byte HTB/Prio cburst+1000 . . . . . . . . . . . . . . . . . 135 B.20 QDisc 500Byte HTB/Prio Minburst-1000 . . . . . . . . . . . . . . . . 135 B.21 QDisc 500Byte HTB/Prio Minburst-500 . . . . . . . . . . . . . . . . 136 B.22 QDisc Kanal Prio 1 ohne Störlast . . . . . . . . . . . . . . . . . . . . 136 viii B.23 End-to-End Kanal Prio 1 ohne Störlast . . . . . . . . . . . . . . . . . 137 B.24 QDisc Kanal Prio 1 mit Störlast . . . . . . . . . . . . . . . . . . . . . 137 B.25 End-to-End Kanal Prio 1 mit Störlast . . . . . . . . . . . . . . . . . . 138 B.26 QDisc Kanal Prio 7 ohne Störlast . . . . . . . . . . . . . . . . . . . . 138 B.27 End-to-End Kanal Prio 7 ohne Störlast . . . . . . . . . . . . . . . . . 139 B.28 QDisc Kanal Prio 7 mit Störlast . . . . . . . . . . . . . . . . . . . . . 139 B.29 End-to-End Kanal Prio 7 ohne Störlast . . . . . . . . . . . . . . . . . 140 ix x Tabellenverzeichnis 2.1 Integrated Services Klassen . . . . . . . . . . . . . . . . . . . . . . . 7 2.2 Service Klassen im Automatisierungskontext . . . . . . . . . . . . . . 7 2.3 Abbildung der User-Priority-Werte auf Traffic-Klassen 2.4 Zuteilung der Traffic Klassen pro Anzahl der Ausgangsqueues . . . . 17 2.5 VLAN ioctl Kommandos . . . . . . . . . . . . . . . . . . . . . . . . . 24 2.6 Namensgebung des VLAN Device . . . . . . . . . . . . . . . . . . . . 24 3.1 RTNetlink-Kommandos . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.2 Flags für Netlink-Messages . . . . . . . . . . . . . . . . . . . . . . . . 47 3.3 Flags für Netlink-Messages (forts.) . . . . . . . . . . . . . . . . . . . 48 5.1 Implementierungsaufwand chConfRead . . . . . . . . . . . . . . . . . 87 5.2 Implementierungsaufwand chContr . . . . . . . . . . . . . . . . . . . 92 5.3 Implementierungsaufwand chDescr . . . . . . . . . . . . . . . . . . . 94 5.4 Implementierungsaufwand chAvailContr . . . . . . . . . . . . . . . . 95 5.5 Implementierungsaufwand chDev . . . . . . . . . . . . . . . . . . . . 96 5.6 Implementierungsaufwand chQoSContr . . . . . . . . . . . . . . . . . 102 5.7 Implementierungsaufwand Testapplikation . . . . . . . . . . . . . . . 103 5.8 Implementierungsaufwand chQoSContr . . . . . . . . . . . . . . . . . 104 6.1 Implementierungsaufwand Instrumentierung . . . . . . . . . . . . . . 111 6.2 Lastkonfigurationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 6.3 Genutzte Bandbreite der Lastkonfigurationen . . . . . . . . . . . . . 112 6.4 Maximaler Delay der Kanäle in der Queueing Disziplin . . . . . . . . 112 6.5 Mittlerer Delay für Prio/HTB-Konfiguration . . . . . . . . . . . . . . 114 6.6 Maximaler Delay für Prio/HTB-Konfiguration . . . . . . . . . . . . . 114 6.7 Mittlerer Delay für Prio-Konfiguration . . . . . . . . . . . . . . . . . 114 xi . . . . . . . . 10 6.8 Maximaler Delay für Prio-Konfiguration . . . . . . . . . . . . . . . . 114 6.9 Mittlerer Delay für FIFO-Konfiguration . . . . . . . . . . . . . . . . . 114 6.10 Maximaler Delay für FIFO-Konfiguration . . . . . . . . . . . . . . . . 115 A.1 Übersicht über alle prioritätsbezogenen Werte . . . . . . . . . . . . . 123 xii Verzeichnis der Quelltexte 2.1 Der socket()-Systemcall für Packet-Sockets . . . . . . . . . . . . . . 19 2.2 Der ioctl()-Systemcall . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.3 Die vlan_ioctl_args Struktur . . . . . . . . . . . . . . . . . . . . . . 22 3.1 Erforderliche Header Dateien für Netlink-Kommunikation . . . . . . . 44 3.2 Die msghdr Struktur . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 3.3 Die iovec Struktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 3.4 Die sockaddr_nl Struktur . . . . . . . . . . . . . . . . . . . . . . . . 45 3.5 Die nlmsghdr Struktur . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.6 Die tcmsg Struktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 3.7 Die tc_sfq_qopt Struktur . . . . . . . . . . . . . . . . . . . . . . . . 51 3.8 Die tc_prio_qopt Struktur . . . . . . . . . . . . . . . . . . . . . . . . 52 3.9 Die tc_htb_glob Struktur . . . . . . . . . . . . . . . . . . . . . . . . 54 3.10 Die tc_ratespec Struktur . . . . . . . . . . . . . . . . . . . . . . . . 55 3.11 Die tc_htb_opt Struktur . . . . . . . . . . . . . . . . . . . . . . . . . 55 3.12 Die tc_u32_sel Struktur . . . . . . . . . . . . . . . . . . . . . . . . . 58 3.13 Die tc_u32_key Struktur . . . . . . . . . . . . . . . . . . . . . . . . . 59 4.1 Document Type Definition der Konfigurationsdatei . . . . . . . . . . 64 5.1 Fehlerrückgabewerte . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 5.2 Zuweisung der Kommunikationmethoden . . . . . . . . . . . . . . . . 88 5.3 Setzen der Socket-Priotität . . . . . . . . . . . . . . . . . . . . . . . . 89 5.4 Makros zur Umrechnung der Indizes in chContain . . . . . . . . . . . 91 5.5 Stufenweises Allokieren in chContain . . . . . . . . . . . . . . . . . . 91 5.6 Die chDescr-Struktur . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 5.7 Suchschleife in chAvailContr . . . . . . . . . . . . . . . . . . . . . . . 94 5.8 Abruf der Shm-Statistiken . . . . . . . . . . . . . . . . . . . . . . . . 94 xiii 5.9 Filter-Selector für Prio-QDisc . . . . . . . . . . . . . . . . . . . . . . 97 5.10 Hinzufügen der Prio-QDisc . . . . . . . . . . . . . . . . . . . . . . . . 98 5.11 User-Priority auf Prio-QDisc Mapping . . . . . . . . . . . . . . . . . 98 5.12 Basis-QDiscs und Filter . . . . . . . . . . . . . . . . . . . . . . . . . 98 5.13 Hinzufügen der Best-Effort-Klasse . . . . . . . . . . . . . . . . . . . . 99 5.14 Setzen der QDisc-Basis-Parameter . . . . . . . . . . . . . . . . . . . . 100 5.15 Generieren der Netlink-Message . . . . . . . . . . . . . . . . . . . . . 100 5.16 Versenden der Netlink-Message . . . . . . . . . . . . . . . . . . . . . 101 5.17 Hinzufügen von Attributen . . . . . . . . . . . . . . . . . . . . . . . . 101 5.18 Generieren einer Netlink-Message für eine HTB-Klasse . . . . . . . . 101 6.1 Die msrFrm-Struktur . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 6.2 testModeSend()-Funktion . . . . . . . . . . . . . . . . . . . . . . . . . 109 6.3 Instrumentierung an der enqueue()-Funktion . . . . . . . . . . . . . . 110 xiv Kapitel 1 Einführung Mit Beginn der Industrialisierung versuchte der Mensch immer mehr Arbeitsabläufe (Prozesse) zu automatisieren. Dies geschah zunächst nur durch Einsatz rein mechanischer Mittel. Mit der Entwicklung der Transistortechnik hielten Computer Einzug in die Industrie. Mit den Rechnern war es möglich an industriellen Fertigungsanlagen sowohl Zustände zu erfassen, als auch deren Verhalten zu steuern und zu regeln. Prozessund Steuerwerte wurden mittels analoger Spannungs- oder Stromwerte übertragen. Der baldige Anstieg in der Komplexität der Anlagen forderte die Entwicklung von dezentraler Peripherie, die über eine gewisse Eigenintelligenz verfügte. Über eine Anlage verteilte Sensoren und Aktoren gaben Prozesswerte nicht mehr nur als analoge Signale weiter, sondern übermittelten diese digitalisiert. Dies ebnete den Weg zum Austausch von Sensor- und Steuerdaten, sowie Statusmeldungen und Diagnoseinformationen. Um diese Art der Kommunikation zu ermöglichen, wurden Bussysteme entwickelt. Sie waren echtzeitfähig, was heißt, dass für alle Daten, die über den Bus gesendet wurden, garantiert wurde, dass sie nach einer maximalen Verzögerungszeit an ihrem Ziel eintreffen [Kop97]. Über die typischen Zweidrahtleitungen der Feldbusse findet immer eine Master-Slave-Kommunikation statt, bei der das Automationssystem zyklisch alle Slave-Komponenten (Peripherie) nach Daten abfragt (Polling) und an sie gerichtete Daten sendet. Heute existieren über 50 verschiedene Feldbusssysteme. Durch den weiter stetigen Komplexitätsanstieg der Industrianlagen, genügte bald ein zentraler Rechner zu deren Steuerung nicht mehr. Die Verteilung der Steuerungs- 1 Kapitel 1. Einführung aufgaben auf mehrere Rechner war eine eindeutige Konsequenz. Dadurch konnten wiederum kleinere, kostengünstigere, spezialisierte Microcontroller eingesetzt werden, was die Wartung der Anlagen günstiger machte, da einzelne Komponenten ausgetauscht werden konnten. Dadurch erhöhte sich weiterhin die Komplexität in der Kommunikation, da gleichberechtigte Steuereinheiten Daten miteinander austauschen mussten. Eine reine Master-Slave-Architektur war nun nicht mehr anwendbar; es musste eine Master-zu-Master-Kommunikation ermöglicht werden. Die vertikale Integration macht Mechanismen erforderlich, die sicher stellen, dass echtzeitkritischer Datenverkehr mit Protokollen wie FTP oder HTTP störungsfrei koexistieren. Weiterhin hat dies höhere Anforderungen an die zur Verfügung stehende Bandbreite zur Folge, denen die auf kleine Datenmengen optimierten Feldbusse nicht gewachsen sind [Jas02]. Als eine Alternative zu Feldbussen dafür hat sich Ethernet herauskristallisiert. Es bietet günstige, standardisierte Hardware mit hoher Geschwindigkeit, einfache Verkabelung und die Nutzung standardisierter Protokolle zur vertikalen Integration. Doch da Ethernet nicht für die Automatisierungsumgebung entwickelt wurde, lässt es sich dort nicht ohne Anpassungen integrieren. Es sind einige, zum Teil sehr erfolgreiche, proprietäre Lösungen entwickelt worden, die die Integration von Ethernet ermöglichen, wie z.B. PROFINET [eH]. Jedoch sind offene Lösungen, welche heute auch immer mehr im Office und Home-Computing Bereich Einzug halten, bisher die Ausnahme. Als ein offenes Betriebssystem bietet Linux die Möglichkeit, mit ihm eine offene Lösung für die Problematik, die Ethernet im Automatisierungskontext stellt, zu entwickeln. Linux ist kostengünstig und bietet große Flexibilität, da es durch Modularität den jeweiligen Anforderungen einer Automationsanlage angepasst werden kann. Diese Arbeit ist eingebettet in das Forschungsprojekt ”Real-Time Data Propagation in Distributed Environments”, des Labors für verteilte System der FH Wiesbaden. Dieses sieht eine mehrschichtige Architektur vor, deren unterste Schicht eine Quality-of-Service-orientierte Kommunikation über Ethernet herstellen soll, basierend auf den von Linux gebotenen Möglichkeiten. Ziel dieser Arbeit ist es diese Kommunikationsschicht zu entwickeln, unter Berücksichtigung der Anforderungen, die eine Automatisierungsumgebung an diese stellt. In Kapitel 2 werden die Grundlagen erläutert, die zum Verständnis des Themas notwendig sind. Abschnitt 2.1 beschreibt was unter Quality of Service zu verstehen ist 2 Kapitel 1. Einführung und gibt einen Überblick über die in diesem Kontext eingesetzten Mechanismen. In Abschnitt 2.2 werden Aspekte von Ethernet betrachtet, die der Erlangung von Echtzeitfähigkeit im Wege stehen. Dabei wird ein Überblick über die bisher eingesetzten Lösungen zur Beseitigung dieser Aspekte gegeben. Abschnitt 2.3 erklärt die Implementierung der in Abschnitt2.1 erläuterten Mechanismen im Linux Kernel. Hierbei werden die Funktionsweise und Programmierschnittstelle zu den jeweiligen Modulen aufgezeigt. In Kapitel 3 werden die Anforderungen und Randbedingungen analysiert, die sich aus der Automationsumgebung ergeben. Abschnitt 3.1 geht auf die Anforderungen verschiedener Anwendugsfälle ein, die in der verteilten Automation auftreten. In Abschnitt 3.2 werden die einzelnen Kommunikationsformen in Bezug zu deren Anforderungen an QoS-Parameter beleuchtet. Wie diese Parameter beeinflusst werden können wird in Abschnitt 3.3 analysiert. Die Analyse der in der Arbeit zu verwendenden Queueing-Disziplinen wird in 3.4 vorgenommen. Zuletzt werden einige Designentscheidungen getroffen, die in Abschnitt 3.5 beschrieben werden. In Kapitel 4 wird ein Software-Design zur Lösung der Problemstellung konzipiert. In Abschnitt 4.1 wird zunächst ein Grobkonzept vorgestellt, das in den Abschnitten 4.2 bis 4.7 für jedes Subsystem verfeinert wird. Kapitel 5 zeigt, wie das in Kapitel 4 erstellte Konzept in die Implementierung umgesetzt wurde. Abschnitt 5.1 beschreibt kurz die Entwicklungsumgebung und die Hilfsmittel, die zur Erstellung der Software genutzt wurde. Die Abschnitte 5.2 bis 5.7 gehen näher auf die Implementierungsaspekte der einzelnen Module ein. In Abschnitt 5.8 wird eine Applikation beschrieben, die zum Testen der Grundfunktionen der Implementierung genutzt werden kann. Der Abschnitt 5.10 erklärt die Kompilierung und Installation der implementierten Software. In Abschnitt 5.11 werden Probleme behandelt, die im Laufe der Implementierung aufgetreten sind. In Kapitel 6 wird eine Bewertung der Implementierung anhand von Messungen durchgeführt. Abschnitt 6.1 beschreibt die Rechner- und Switchkonfiguration in der gemessen wurde. Abschnitt 6.2 geht auf die einzelnen Messgrößen ein. Die vorgenommene Instrumentierung wird in Abschnitt 6.3 beschrieben. Abschnitt 6.4 stellt das Lastmodell vor, dessen Implementierung in Abschnitt 6.5 beschrieben wird. In Abschnitt 6.6 werden die besprochen und dazu in 6.7 ein Fazit dazu gegeben. Die Arbeit wird Kapitel 7 zusammengefasst und es wird ein Ausblick mögliche Weiterentwicklungen gegeben. 3 Kapitel 1. Einführung 4 Kapitel 2 Grundlagen 2.1 Quality of Service Der Begriff Quality of Service, oder Dienstqualität [Tan02] ist in der Fachwelt nicht klar definiert [Jas02]. Ursprünglich stammt er aus der Multimediakommunikation. Bei der Übertragung von Video- oder Audiosignalen über ein Datennetzwerk treten gewöhnlich Verzögerungen auf. Überschreiten diese Verzögerungen einen bestimmten Grenzwert, so sind Verluste in der Qualität des Signals deutlich erkennbar. Um dies zu vermeiden und damit die Qualität des Services zu gewährleisten, wurden diverse Mechanismen entworfen, die unter dem Begriff Quality of Service (QoS) zusammengefasst wurden. Heute weitet sich deren Anwendung auf verschiedene andere Gebiete aus, unter anderem auf die Industrieautomation, wo sie eingesetzt werden, um harte Echtzeitanforderungen durchzusetzen. Der QoS-Begriff wird durch eine Reihe von Parametern bestimmt, die Einfluss auf die Qualität eines Dienstes haben. Mit ihnen können Anforderungen beschrieben werden, die ein Dienst stellt, um die benötigte Qualität aufrecht zu erhalten. Die Parameter sind nach [Fur03]: Service Availability (Dienstverfügbarkeit) Service Availability beschreibt die Zeit, die ein Service in einem Netzwerk zur Verfügung steht. Diese wird als Prozentsatz der Zeit angegeben, die ein Service innerhalb der Gesamtzeit verfügbar ist. Paket Loss Rate (Paketverlustrate) Fällt an einem Netzwerkknoten mehr Datenverkehr an als verarbeitet werden kann, so müssen unter Umständen Pakete 5 2.1. Quality of Service Kapitel 2. Grundlagen verworfen werden. Die Paketverlustrate gibt den prozentualen Anteil der Pakete an, die auf dem Weg durch das Netzwerk verloren gehen. Delay, Latency (Latenz) Der Delay ist die Propagationszeit zwischen dem Senden und Empfangen eines Pakets. Der Delay eines Pakets bewegt sich immer zwischen einem Worst-Case-Delay und einem Best-Case-Delay. Er besteht aus einem konstanten und einem variablen Teil. Der konstante Teil setzt sich aus der Verarbeitung der Daten durch die Hardware und der Signallaufzeiten in den Kabeln zusammen. Der variable Teil entsteht durch die Verzögerungen und Wartezeiten in den Betriebssystemen, Treibern und Firmware jedes passierten Netzwerkknotens. Jitter Werden Pakete in festen Zeitintervallen (isochron) gesendet, so kommt es beim Empfänger durch Unregelmäßigkeiten in der Latenz zu einer Varianz in den Intervallen. Der Jitter beschreibt diese Varianz im Delay in Form einer vorzeichenbehafteten Variablen, deren Wert negativ ist, wenn ein Paket früher als erwartet eintrifft und positiv, wenn es später eintrifft. Troughput(Durchsatz) Throughput beschreibt den Datendurchsatz im Netz, gemessen in Bytes oder Paketen pro Sekunde. Um QoS in einem Netzwerk durchsetzen zu können, müssen zwei Anforderungen erfüllt sein: Die Pakete, die ein QoS-orientiertes Netzwerk passieren, müssen klassifizierbar sein. Jeder Netzwerkknoten, den ein klassifiziertes Paket passiert, muss in der Lage sein, das Paket entsprechend seiner Klassifizierung zu verarbeiten. 2.1.1 Klassifizierung Um Pakete klassifizieren zu können, müssen zunächst verschiedene Klassen definiert werden. In RFC 1633 werden drei Dienstklassen vorgeschlagen, die in Tabelle 2.1 erläutert werden: Guaranteed Service, Predictive Service und Best-Effort-Service. 6 Kapitel 2. Grundlagen 2.1. Quality of Service Dienstklasse Eigenschaften Guaranteed Pakete werden innerhalb einer festgelegten Verzögerungszeit ausgeliefert. Um den Jitter zu minimieren können Pakete, die zu früh eintreffen, beim Empfänger gepuffert werden. Predictive Pakete dieser Klasse haben keine festgelegten maximalen Verzögerungszeiten, dennoch werden sie bevorzugt behandelt um ihre Ankunftszeit grob vorhersagbar zu halten. Ihr Verhalten sollte Best-Effort-Verkehr widerspiegeln, der auf einem unbelasteten Netz stattfindet. Best-Effort Normaler, unprivilegierter Netzwerkverkehr Tabelle 2.1: Integrated Services Klassen Die Eigenschaften dieser Klassen sind ursprünglich für die Multimediakommunikation konzipiert und nur grob umrissen. In [Fur03] werden diese Klassen in den Automatisierungskontext gebracht und um angemessene Werte in Bezug auf die restlichen QoS-Parameter erweitert (Tabelle 2.2). Dienstklasse Anw. i.d. Automation QoS-Parameter Guaranteed Echtzeitanwendung Synchronisation Fehlerbehandlung Verfügbarkeit: sehr hoch Verlustrate: sehr tief Verzögerung: niedrig+konstant Jitter: sehr klein Durchsatz: konstant Predictive Download Produktionsdaten Programmierung Debugging Monitoring Verfügbarkeit: sehr hoch Verlustrate: sehr tief Verzögerung: kontrolliert Jitter: kontrolliert Durchsatz: kontrolliert Best-Effort Bedienen & Beobachten Internet-Zugriff Extranet-Anbindung Verfügbarkeit: hoch Verlustrate: tief Verzögerung: unspezifiziert Jitter: unspezifiziert Durchsatz: unspezifiziert Tabelle 2.2: Service Klassen im Automatisierungskontext [Fur03] 7 2.1. Quality of Service Kapitel 2. Grundlagen Der Guaranteed Service soll für echtzeitkritische Anwendungen, wie Synchronisation und Fehlerbehandlung, eingesetzt werden. Der Predictive Service übernimmt Aufgaben wie Download, Programmierung der Peripherie, sowie Debugging und Monitoring. Der restliche Verkehr wird vom Best-Effort-Service bedient. Dies trifft auf Anwendungen wie Internet-Zugriff und Extranet-Anbindung zu. Auf IP-Ebene existieren zwei Ansätze zur Klassifizierung von Paketen : Integrated Services und Differentiated Services. Das in RFC 1633 beschriebene Integrated Services (IntServ) teilt den Netzwerkverkehr in einzelne Paketströme, sogenannte Flows, ein. Diesen Flows wird jeweils eine Serviceklasse zugeordnet. Damit jedes Netzwerkelement auf dem Pfad das Paket erkennen und ihm Resourcen zuteilen kann, wird das Resource Reservation Protokoll (RSVP) eingesetzt, das in RFC 2205 beschrieben ist. Jedes Netzwerkelement muss sich eine Tabelle halten, die die Flows und ihre zugehörige Klasse verwaltet. Das in RFC 2475 beschriebene Differentiated Services (DiffServ) verfolgt einen anderen Ansatz. Jedes Paket erhält entsprechend seiner Klasse eine Markierung. Dies geschieht durch die Zuweisung eines Wertes (DS-Wert), der seine Klasse identifiziert. Dieser Wert wird vom Paket mitgeführt und von den Netzwerkknoten innerhalb einer DS-Domäne zwecks Klassifizierung ausgelesen. Das schont die Resourcen der Netzwerkknoten, da keine Tabelle mehr im Speicher gehalten werden muss, und macht somit den Einsatz von RSVP unnötig. Allerdings müssen alle Netzwerkknoten innerhalb der DS-Domäne die Resourcen schon statisch für jede Klasse vorkonfiguriert haben. Für den DS-Wert muss ein Feld im Protokoll-Header existieren, das zu diesem Zweck verwendet werden kann. Da das Type-of-Service-Feld im IP-Header nur selten Verwendung fand, wird es mit Einführung von DiffServ als DS-Feld genutzt. Für Ethernet existiert ein ähnlicher Ansatz, bei dem wie bei DiffServ das Paket selbst markiert wird. Der Standard IEEE 802.1p (seit 2004 integriert in IEEE 802.1D [IEE04]) beschreibt die Möglichkeit einem 802.3 Ethernet Frame eine Priorität zuzuweisen. Dies geschieht durch den sogenannten User Priority Wert, für den jedoch im klassischen Ethernet-Header kein Feld vorgesehen ist. Daher wird von IEEE 802.1p vorgeschlagen diesen Wert aus einem Feld in einer höheren Protokollschicht, wie dem DS-Feld im IP-Header, zu berechnen oder das im 802.1Q-Standard beschriebene User-Priority-Feld im VLAN-Tag zu verwenden. Der Standard IEEE 802.1Q [IEE03] beschreibt die Aufteilung eines physischen, von einem Switch vermittelten Local Area Network, in mehrere virtuelle LANs. Um das zu erreichen, wird zwischen der Zieladresse und dem Type-Feld ein 4 Byte langes 8 Kapitel 2. Grundlagen 2.1. Quality of Service VLAN-Tag eingefügt (Abbildung 2.1). Abbildung 2.1: IEEE 802.1Q VLAN Tag Eingeleitet wird das Tag durch den Tag Protocol Identifier (TPID), der immer den Wert 0x8100 hat. Darauf folgt die Tag Control Information (TCI), die aus dem 3Bit User Priority Feld, dem 1-Bit Canonical Format Indicator (CFI) und der 12-Bit VLAN-ID (VID) besteht. Frames mit unterschiedlicher VID werden vom Switch wie Frames in physikalisch voneinander getrennten LANs behandelt. Drei der VIDs sind reserviert. VID 0 VID 0 bezeichnet einen Priority Tagged Frame. Ein solcher Frame besitzt keine VLAN-Zugehörigkeit; das Tag wird nur genutzt, um den Frame über das User-Priority-Feld klassifizieren zu können. VID 1 Jeder Switch muss über die Funktion verfügen, einem Frame ein Tag hinzuzufügen, sofern er noch keins besitzt. Die VID, die dem Frame zugewiesen wird, wird Port-VID (PVID) genannt. Als Default PVID legt 802.1Q die VID 1 fest. VID 0xFFF Diese VID ist für Implementationszwecke reserviert und sollte niemals verwendet werden. Mit dem User-Priority-Feld kann die Priorität eines Frames angegeben werden, wodurch der Frame für alle Netzwerkknoten auf seinem Pfad klassifizierbar ist. Zu diesem Zweck definiert IEEE 802.1D [IEE04] acht Verkehrsklassen, auf die der Wert des User-Priority-Feldes abgebildet wird. Tabelle 2.3 zeigt diese Klassen. 9 2.1. Quality of Service Kapitel 2. Grundlagen Priorität User-Priority Akronym Verkehrsklasse hoch 7 6 5 4 3 0 2 1 NC VO VI CL EE BE BK Network Control Voice Video Controlled Load Excellent Effort Best Effort Nicht belegt Background niedrig Tabelle 2.3: Abbildung der User-Priority-Werte auf Traffic-Klassen Die Priorität der einzelnen Verkehrsklassen entspricht ihrem Wert im User-PriorityFeld, d.h. 7 entspricht der höchsten Priorität, 6 der nächst niedrigeren, usw. Grundsätzlich wird davon ausgegangen, dass ein Frame ohne Tag der Best-Effort-Klasse angehört. Wird einem Frame ein Tag hinzugefügt, und seine Klasse ist nicht ersichtlich, wird ihm eine User-Priority von 0 zugewiesen. Der Standard IEEE 802.1p definiert jedoch zwei Klassen, die eine niedrigere Priorität aufweisen als Best-EffortVerkehr. Aus diesem Grund wurde dem User-Priority-Wert 0 eine höhere Priorität zugewiesen, als den User-Priority-Werten 1 und 2. Dies geschah laut [Jas02] aus Kompatibilitätsgründen. 2.1.2 Paket Scheduling, Traffic Shaping und Policing Kann ein Paket klassifiziert werden, muss gewährleistet sein, dass jeder Netzwerkknoten auf dem Pfad in der Lage ist, es entsprechend seiner Klasse zu verarbeiten. Paket Scheduling Algorithmen legen die Art und Weise fest, wie ein Knoten ein Paket verarbeitet. Sie haben die Aufgabe die Ausgangsqueue eines Netzwerkknotens neu zu ordnen und somit bestimmten Paketen den Vorrang vor anderen Paketen zu geben. Eine andere Maßnahme, wie man den Verkehr in einem Netzwerk beeinflussen kann, stellt dass Traffic Shaping dar. Hier wird der Durchsatz eines Datenstroms begrenzt, um die Bandbreite für andere Datenströme freizuhalten. Pakete, die die Bandbreitenbegrenzung überschreiten würden, werden in einer Queue gehalten, bis wieder genügend Bandbreite zu Verfügung steht. Eine Umordnung der Pakete wird grund- 10 Kapitel 2. Grundlagen 2.1. Quality of Service sätzlich nicht berücksichtigt, ist jedoch nicht ausgeschlossen. Werden Pakete verworfen, anstatt sie in einer Queue zwischen zu lagern, so spricht man von Traffic Policing. Alle diese Algorithmen stellen nicht-preemptive Scheduling-Algorithmen dar, da die Übertragung eines Paketes nicht abgebrochen werden kann, selbst wenn ein Paket höherer Priorität zum Senden eintrifft. Es existieren unzählige Algorithmen, die auf verschiedene Arten und mit teils unterschiedlichen Zielen diese Aufgaben erledigen. Hier sollen nur die besprochen werden, die für diese Arbeit Relevanz haben. Als relevant werden die Algorithmen eingestuft, die unter Linux oder in Switches implementiert sind und auch mit reinem Ethernet einwandfrei genutzt werden können. 2.1.2.1 Strict Priority Queueing Unter Strict Priority Queueing versteht man die Umsortierung streng nach Priorität. Zu versendende Pakete werden bei diesem Algorithmus entsprechend ihrer Priorität in verschiedene Ausgangsqueues sortiert. Zuerst werden die Pakete aus der Queue mit der höchsten Priorität verschickt. Ist diese Queue leer, werden die Pakete aus der Queue mit der nächst niedrigeren Priorität versendet usw. Die Pakete in einer Queue müssen immer so lange warten, bis die Queues mit höherer Priorität keine Pakete mehr enthalten. Innerhalb einer Queue werden die Pakete nach FIFO abgearbeitet. Abbildung 2.2: Beispiel: Strict Priority Queueing 11 2.1. Quality of Service Kapitel 2. Grundlagen Abbildung 2.2 zeigt ein Beispiel für Strict Priority Queueing, in dem drei Queues mit den Prioritäten 1 bis 3 verwendet werden, wobei Priorität 1 die höchste darstellt. Das Paket aus der Prio 1 Queue wird bedient (1). Da die Prio 1 Queue leer ist, wird das erste Paket aus der Prio 2 Queue verschickt. Während dessen trifft ein weiteres Paket in der Prio 1 Queue ein, das Versenden des Pakets in der Prio 2 Queue kann jedoch nicht unterbrochen werden(2). Die verbleibenden Pakete werden in Reihenfolge ihrer Priorität verschickt (3)-(6). 2.1.2.2 Weighted Round Robin Queueing Dieser Scheduling Algorithmus baut auf dem Round Robin Algorithmus auf, bei dem eine bestimmte Menge von Queues existiert. Unter diesen wird die gesamte Bandbreite aufgeteilt, womit jede Queue ein sogenanntes Quantum an Bandbreite erhält. Beim Weighted Round Robin lässt sich das Quantum gewichten. So ist es möglich, einer Queue mit hoher Priorität ein größeres Quantum an Bandbreite zukommen zu lassen, als einer mit niedriger Priorität. Jede Queue nimmt die Bandbreite seines Quantums in Anspruch, unabhängig davon, ob sie genutzt werden kann. Abbildung 2.3: Beispiel: Weighted Round Robin Queueing Das in Abbildung 2.3 gezeigte Beispiel für den Weighted Round Robin Algorithmus zeigt drei Queues, mit den Quanten 1, 2 und 3. Die Queue mit Quantum 3 kann alle enthaltenen Pakete versenden (1). In der Queue mit Quantum 2 werden nur zwei der drei Pakete verschickt(2) und die Queue mit Quantum 1 bedient ihr einziges 12 Kapitel 2. Grundlagen 2.2. Ethernet im Echtzeitbetrieb Paket (3). Die Queue mit Quantum 3 hat keine Pakete mehr zu versenden, nimmt aber trotzdem ihre Zeiteinheiten in Anspruch (4). Die Queue mit Quantum 2 hat zwar nur ein Paket zu versenden, nimmt aber auch ihre die Zeit von zwei Quanten in Anspruch. 2.1.2.3 Token Bucket Algorithmus Der Token Bucket Algorithmus stellt eine Traffic Shaping Methode dar. Der Algorithmus sieht einen Container (Bucket) vor, in dem mit einer festgelegten Rate Token generiert werden. Jedes Token entspricht dabei einer festgelegten Anzahl Bytes. Soll ein Paket verschickt werden, muss eine Anzahl Token, entsprechend der Größe des Pakets, aus dem Container entfernt werden. Sind nicht genug Token im Container, muss das Paket warten, bis genügend Token generiert wurden. Die Rate, in der die Token generiert werden, entspricht somit der Bandbreite, die einem Datenstrom zur Verfügung steht. Die Kapazität des Containers gibt die Anzahl Bytes an, die in einem Burst verschickt werden können, soweit entsprechend viele Token generiert werden konnten. Die Kapazität muss mindestens so groß gewählt werden, wie ein maximal großes Paket, da sonst nie ausreichend Token im Container vorhanden wären, um ein solches Paket zu verschicken, und es die Queue blockieren würde. Das in Abbildung 2.4 gezeigte Beispiel zeigt einen Bucket mit einer Kapazität von acht Token. Es wird in dem Beispiel davon ausgegangen, dass ein Paket beim Senden drei Token verbraucht. In (1) ist der Bucket komplett gefüllt. Vier Pakete treffen ein, von denen zwei sofort als Burst versandt werden können. Die zwei übrig gebliebenen Token, reichen zum Versenden eines weiteren Pakets nicht aus (2). Abhängig von der eingestellten Rate wird ein weiteres Token generiert, womit das dritte Paket bedient werden kann(3). Das letzte Paket wartet auf weitere drei Token (4)+(5), bis es an der Reihe ist. Solange keine weiteren Pakete in der Queue eintreffen, wird der Bucket in der eingestellten Rate wieder bis zu seiner Kapazität gefüllt(6). 2.2 Ethernet im Echtzeitbetrieb IEEE 802.3 Ethernet ist grundsätzlich nicht echtzeitfähig konzipiert worden und kann in seiner ursprünglichen Form nicht als Feldbusersatz eingesetzt werden [Fel00]. Das größte Problem dabei ist das nicht-deterministische Medienzugriffsverfahren 13 2.2. Ethernet im Echtzeitbetrieb Kapitel 2. Grundlagen Abbildung 2.4: Beispiel: Token Bucket Algorithmus CSMA/CD [Tan02, Hal96]. Da Kollisionen von CSMA/CD nur erkannt und nicht verhindert werden, müssen Übertragungen abgebrochen und deren Neusendung initiiert werden. Diese kann bei hoher Last wiederum zu weitern Kollisionen führen, die somit zu unbestimmbaren Verzögerungen führen. Im wesentlichen existieren drei Ansätze, wie Kollisionen im Ethernet verhindert werden können: Lastreduzierung, TDMA und der Einsatz von Switches. 2.2.1 Lastreduzierung Der erste Ansatz Lastreduzierung ist von rein probabilistischer Natur und zielt darauf ab, die genutzte Bandbreite weit unter der verfügbaren zu halten [JL04]. Damit wäre eine statistische Garantie für die Einhaltung von Delay-Grenzen gegeben. Da es sich aber nur um eine statistische Garantie handelt, kann dieser Ansatz harten Echtzeitanforderungen nicht gerecht werden. Zugleich würde diese Lösung einer Konfiguration mit hoher Anzahl an Knoten nicht mehr standhalten, da die zur Verfügung stehende Bandbreite dafür kaum ausreichen würde. Somit würde dabei die Wahrscheinlichkeit des Auftretens von Kollisionen wieder ansteigen. 14 Kapitel 2. Grundlagen 2.2.2 2.2. Ethernet im Echtzeitbetrieb Time Devision Multiple Access Der zweite Ansatz sieht ein TDMA-Verfahren vor, bei dem jedem Knoten auf dem Medium ein Zeitschlitz zugewiesen ist, während dessen er senden darf. Die Zuteilung der Zeitschlitze wird statisch oder durch einen Master-Knoten bestimmt. Die Synchronisation wird durch den Einsatz einer fein granularen Uhr und eines Zeitprotokolls wie PTP [IEE] durchgesetzt. Die festen Delay-Grenzen werden von der gewählten Zykluszeit bestimmt. Durch die Wahl einer geringen Zykluszeit entsteht ein geringer Delay, jedoch wird auch die zur Verfügung stehende Bandbreite verringert. Durch die festen Zeitabstände entsteht ein geringer Jitter. Da nicht jeder Knoten in seinem Zeitschlitz sendebereit ist, bleibt Bandbreite teilweise ungenutzt, die für Best-Effort-Verkehr hätte genutzt werden können. Dieses Verfahren wird in Zusammenhang mit Ethernet unter anderem von RTnet [KS04] eingesetzt, welches die Nutzung der Echtzeiterweiterung RTAI unter Linux vorsieht. RTNet implementiert einen kompletten, echtzeitfähigen UDP/IP-Stack, der durch ein BSD-Socket-Interface erschlossen wird. Es setzt ein geschlossenes und durch einen Hub verschaltetes Netz voraus, in dem sich alle Teilnehmer an das TDMA-Protokoll halten und ein Masterknoten für die Synchronisierung der Stationen sorgt. Der Stack der beteiligten Rechner wird durch einen modifizierten LinuxTreiber an Ethernet angebunden. Eine Fernkonfiguration dieser Rechner ist durch ein eigenes, vom RTNet-Protokoll unabhängiges Protokoll namens RTcfg möglich. Die vertikale Integration erfolgt durch Tunnelung der betroffenen Protokolle über einen Gatewayknoten. Ein weiteres Beispiel für TDMA ist Profinet IRT (Isochronous Real Time) [eH]. Dieses teilt das Medium zunächst in einen deterministischen und einen offenen Zeitschlitz auf. Im offenen Zeitschlitz wird der gesamte Best-Effort-Verkehr, sowie der Verkehr mit weichen Echtzeitanforderungen abgewickelt. Die weiche Echtzeit wird durch Priorisierung nach IEEE 802.1p durchgesetzt. Der deterministische Zeitschlitz ist in weitere Zeitschlitze unterteilt, in denen Kommunikation nach harten Echtzeitanforderungen abläuft. Die Reservierung der Zeitschlitze erfolgt über einen speziell für diesen Zweck entwickelten Chip, der in die verwendeten Switches integriert ist. Im Zusammenhang mit TDMA soll noch das Token-Passing-Verfahren genannt werden. Auch hier wird das Medium in Zeitschlitze aufgeteilt, jedoch ist das Synchronisieren der Uhren in den Knoten nicht notwendig. Die Sendeberechtigung wird durch ein Token zugeteilt, das in Form eines Pakets zyklisch unter den Stationen ausge- 15 2.2. Ethernet im Echtzeitbetrieb Kapitel 2. Grundlagen tauscht wird. Der Besitzer des Tokens sendet seine Daten und reicht dann das Token an den nächsten weiter. 2.2.3 Einsatz von Switches Ein Switch ist ein Vermittlungselement zwischen Netzen, welches auf der MACTeilschicht der in IEEE 802 Standard [IEE01] beschriebenen Architektur arbeitet. Die Funktionsweise wird im Standard IEEE 802.1D [IEE04] festgelegt. Durch den Einsatz eines in Voll-Duplex-Modus arbeitenden Switches ist es möglich, Kollisionsdomänen zu segmentieren. Durch Microsegmentierung, d.h Zuweisung eines Ports für jeden Rechner, sind Kollisionen ausgeschlossen, was ein vollkommen deterministisches Verhalten von Ethernet zur Folge hat [Jas02]. Im Folgenden sollen die Funktionsweise und der Aufbau anhand der Abbildung 2.5 erläutert werden. Abbildung 2.5: Aufbau eines Switch Ein Switch besitzt mindestens zwei Ports, die sowohl unterschiedliche Datenraten, 16 Kapitel 2. Grundlagen 2.2. Ethernet im Echtzeitbetrieb als auch Duplex-Modi aufweisen können. Die Vermittlung zwischen den einzelnen Ports wird durch eine Vermittlungseinheit vorgenommen. Diese verwaltet eine Tabelle mit MAC-Adressen, die jeweils einem Port zugeordnet sind. Trifft ein Frame an einem Port ein, so wird er im Shared Memory des Switches abgelegt. Dort wird die Zieladresse des Frames ausgelesen und durch die Vermittlungseinheit mit den Adressen in der MAC-Tabelle verglichen. Gehört diese Adresse zu dem Port, auf dem der Frame empfangen wurde, wird der Frame verworfen. Ansonsten wird ein Zeiger auf die Speicheradresse des Frames in die Ausgangsqueue des entsprechenden Ports zwecks Weiterleitung eingereiht. Für den Fall, dass die Adresse nicht in der Tabelle enthalten ist, leitet er den Frame an alle Ports weiter (Flooding). Sobald der adressierte Rechner antwortet, kann die Vermittlungseinheit die MAC-Adresse dem Port zuordnen, auf dem er empfangen wurde, und sie in die Tabelle eintragen. Der Frame verweilt so lange im Shared Memory des Switches, bis die Ausgangsqueue auf die Position des zugehörigen Zeigers abgearbeitet ist und der Frame verschickt werden kann. Die Ausgangsqueues der Ports weisen in der Regel FIFOCharakteristik auf; um jedoch User-Prioritäten nach 802.1p berücksichtigen zu können, sieht der Standard 802.1D [IEE04] auch die Ausstattung der Ports mit mehr als einer Queue vor. Da dort nicht zwingend acht Queues vorgesehen ,sind erfolgt eine Zuteilung der Verkehrsklassen aus Tabelle 2.3 aufgrund von Tabelle 2.4. Diese Queues werden in der Regel dann nach dem Strict Priority Algorithmus abgearbeitet, können auf den einzelnen Switches aber auch per Weighted Round Robin bedient werden. Anzahl Queues Traffic Klasse 1 BE 2 BE 3 BE VO 4 BK BE 5 BK BE 6 BK BE 7 BK 8 BK – CL VO CL VO CL VI VO EE CL VI VO BE EE CL VI VO NC BE EE CL VI VO NC Tabelle 2.4: Zuteilung der Traffic Klassen pro Anzahl der Ausgangsqueues [IEE04] 17 2.2. Ethernet im Echtzeitbetrieb Kapitel 2. Grundlagen Durch die gezielte Verteilung der Frames an ihre Zielnetze, werden die Netzwerke bzw. die zugehörigen Ports nicht belastet und es kommt nicht so schnell zu Überläufen und damit zu Paketverlust. Durch den Einsatz von Multicasts [IEE01] macht man diesen Vorteil zunichte, da diese, wie Broadcasts, über alle Ports gesendet werden. Um diesen Vorteil auch mit Multicasts nutzen zu können, wird in IEEE 802.1p [IEE04] das GARP Multicast Registration Protocol (GMRP) vorgestellt. Ein Switch der GMRP unterstützt leitet grundsätzlich erst einmal keine Multicast-Frames weiter. Empfängt er ein GMRP Paket mit der Information, dass eine Station Frames einer Multicast-Gruppe empfangen möchte, so wird diese in die MAC-Adresstabelle eingetragen. Das bewirkt, dass diese Frames über diesen Port weitergeleitet werden. Genauso ist es auch möglich, Multicast-Gruppen per GMRP zu deregistrieren. Managebare Switches, die kein GMRP unterstützen, lassen oft auch zu, die MACAdresstabelle für den Multicastbetrieb manuell zu konfigurieren. Switches, die über mehrere Ausgangsqueues pro Port verfügen und den IEEE 802.1p Standard unterstützen, werden unter anderem von Cisco (Catalyst 2950 mit 4 Queues) und Siemens (ELS TP40M mit 2 Queues) hergestellt. 2.2.4 Fazit Die vorgestellten Verfahren zur Erlangung von Determinismus unter Ethernet haben jeweils ihre Vorteile, jedoch auch ihre Einschränkungen. So ist Lastreduzierung zur Erlangung einer statistischen Kollisionsfreiheit in einem kleinen Netzwerk mit wenigen Teilnehmern eine günstige Lösung, jedoch skaliert diese nicht bei ansteigender Zahl der beteiligten Knoten. TDMA ist sehr aufwendig in der Umsetzung und setzt eine Anpassung des ProtokollStacks oder die Verwendung angepasster Hardware voraus. Dafür bietet es harte Echtzeitfähigkeit mit kontrolliertem Jitter. Der Einsatz von Switches bietet Determinismus durch Microsegmentierung des Netzwerks. Darüber hinaus kann QoS durch standardisierte Protokollerweiterungen durchgesetzt werden, wenn der Switch diese unterstützt und über geeignete Hardware verfügt. Ob sich dadurch harte Echtzeitanforderungen durchsetzen lassen, hängt von den Anforderungen des technischen Prozesses ab. Bei geeigneter Topologie und konfigurierter Last, können Transaktionszeiten im Millisekunden-Bereich garantiert werden [Jas02]. 18 Kapitel 2. Grundlagen 2.3 2.3. Linux Linux Linux bietet als freies Betriebssystem diverse Einstellmöglichkeiten für Quality of Service, auf die im Folgenden eingegangen wird. In dieser Arbeit soll der LinuxKernel ab Version 2.6 betrachtet werden, dessen System-Timer seit der Version 2.4 einen Granularitätsanstieg um den Faktor 10 auf 1000Hz erfahren hat. 2.3.1 Packet-Sockets Ein Packet-Socket ist eine direkte Schnittstelle zur Kommunikation über Ethernet. Bei den durch einen Packet-Socket versendeten Daten, wird nur der Ethernet-Header hinzugefügt, wodurch der TCP/UDP/IP-Stack umgangen wird. Der Packet-Socket ist zwar kein explizites QoS-Werkzeug, doch durch seinen Einsatz können Delay und Jitter implizit reduziert werden. Die Konfiguration erfolgt auf die gleiche Weise, wie die eines INET-Socket, durch den Aufruf des Systemcalls socket(). Quelltext 2.1: Der socket()-Systemcall für Packet-Sockets packet_socket = socket ( PF_PACKET , int socket_type , int protocol ) ; Gibt man für den Parameter socket_type den Wert SOCK_DGRAM an, so wird der Header des Frames beim Versand durch das Betriebssystem generiert und beim Empfang automatisch wieder entfernt. Gibt man SOCK_RAW, an müssen diese von dem aufrufenden Prozess bereitgestellt bzw. entfernt werden. Der Parameter protocol nimmt den Protokolltyp auf, den der Socket empfangen soll. Der verwendete Wert wird in Network-Byte-Order übergeben und ist im EthernetHeader im Type-Feld wiederzufinden. In der Header-Datei linux/if ether.h sind die allgemein bekannten Protokolle, wie ETH_P_IP (0x8000) oder auch ETH_P_8021Q (0x8200) definiert. Es ist jedoch auch möglich ein eigenes Protokoll zu definieren, wobei darauf geachtet werden sollte, dass es nicht mit den reservierten Protokollen kollidiert. Zur Adressierung wird die sockaddr_ll-Struktur genutzt, die bei Aufrufen von sendto () übergeben wird. Die Struktur wird ausführlich auf der Manpage packet(7) besprochen. Dort wird auch erklärt, wie ein Paket-Socket für den Empfang von EthernetMulticast-Frames eingerichtet wird. 19 2.3. Linux 2.3.2 Kapitel 2. Grundlagen 802.1Q VLAN Modul Die Virtual LAN Funktionalität wird unter Linux von dem Modul 8021q gewährleistet. Um das Modul nutzen zu können, muss es entweder dynamisch geladen oder fest in den Kernel kompiliert werden. Die dafür erforderliche Kernel-Option für Kernel 2.6 ist unter ”Device Drivers/Networking support/Networking options/802.1Q VLAN Support” zu finden. 2.3.2.1 Aufbau und Funktionsweise Wird ein neues VLAN konfiguriert, so wird dafür ein neues logisches NetzwerkDevice erstellt. Normalerweise repräsentiert ein logisches Device unter Unix/Linux ein in Hardware vorhandenes Netzwerkinterface. Das für das VLAN erstellte Device repräsentiert keine Hardware. Vielmehr ist es einem realen, logischen NetzwerkDevice vorgeschaltet und wird als virtuelles Gerät bezeichnet(Abbildung 2.6). Der Name des virtuellen Devices ist abhängig von der Konfiguration des Moduls. Tabelle 2.6 zeigt hierfür alle Möglichkeiten der Konfiguration auf, sowie deren DefaultEinstellung. Normalerweise wird das Device, über das Pakete verschickt werden, durch den Routing-Prozess ermittelt. Daher ist es empfehlenswert, die Devices verschiedenen Subnetzen zuzuordnen oder die Sockets durch Socket-Optionen an das jeweilige Device zu binden. Ansonsten ist nicht eindeutig, auf welchem der Devices ein Paket gesendet werden soll. Jedem Device können bis zu 4093 VLANs (212 − 3) zugewiesen werden. Abbildung 2.6: Aufbau der VLAN-Devices 20 Kapitel 2. Grundlagen 2.3. Linux Wird ein Paket über ein VLAN verschickt, muss das über das dazugehörige virtuelle Device geschehen. Dort wird das VLAN-Tag mit der entsprechenden VID in das Paket eingefügt und an das reale Device weitergegeben. Umgekehrt werden am realen Device empfangene Pakete, die VLAN-gekennzeichnet sind, an das entsprechende virtuelle Device weitergereicht. Dort wird das Tag entfernt und über den ProtokollStack an den Empfängerprozess weitergegeben. Das Setzen des Wertes im User-Priority-Feld wird durch zwei Priority-Maps erreicht: der Egress-Priority-Map und der Ingress-Priority-Map. Die Egress-Priority-Map bildet beim Senden eines Pakets die Priorität dessen Socket-Buffers auf den Wert im User-Priority-Feld ab. Der Socket-Buffer ist eine Struktur im Linux Kernel, mit der die zu sendenden und empfangenen Pakete durch den Protokoll-Stack nach unten bzw. nach oben weitergereicht werden. Desweiteren enthält die Egress-Priority-Map weitere Flags und Werte, u.a. Socket-Priority, die mit dem zugehörigen Socket assoziiert werden. Für die Möglichkeit, die User-Priority auf die Socket-Priority zurück zu rechnen, ist die Ingress-Priority-Map zuständig. Diese wird aber nur für den Fall benötigt, dass Linux Bridging-Funktionen übernehmen soll. Die Priorität aller Socket-BufferStrukturen, die durch einen Socket gesendet werden, kann über eine Socket-Option gesetzt werden. Ansonsten wird der Wert des DS-Feldes im IP-Header auf die Priorität abgebildet. Die Prioritäten des User-Priority-Feld können mit dem 802.1Q Modul zwar gesetzt werden, jedoch wird die Einhaltung der Prioritäten nicht durchgesetzt. Dazu ist der Einsatz von Paket Scheduling Algorithmen nötig, die die Pakete nach dem User-Priority-Feld klassifizieren und entsprechend bedienen. 2.3.2.2 Konfiguration Die Konfiguration der VLANs und deren Parameter erfolgt durch das Open-Source Tool-vconfig, welches in jeder größeren Linux Distribution enthalten ist. Weitere Informationen sind der Manpage des Programms zu entnehmen. Möchte man die Konfiguration aus einem C-Programm vornehmen, so empfiehlt es sich, dass Application Programming Interface (API) zu nutzen, das auch von vconfig verwendet wird. Das Wissen über die Funktionalität des API wurde hauptsächlich aus dem Quellcode des vconfig-Tools und dem 8021q Kernelmodul hergeleitet. Daher wird nicht der Anspruch auf Vollständigkeit erhoben. 21 2.3. Linux Kapitel 2. Grundlagen Die dafür erforderlichen Header-Dateien sind: # include < sys / ioctl .h > # include < linux / if_vlan .h > Der Systemcall ioctl() (Listing 2.2) dient als Schnittstelle zum 8021q Kernelmodul. Für die Handhabung der Funktion sei wiederum auf die entsprechende Manpage verwiesen. Quelltext 2.2: Der ioctl()-Systemcall int ioctl ( int d , int request , void * argp ) ; Für den ”request”-Parameter wird der definierte Wert ”SIOCSIFVLAN” verwendet. Um die Anfrage näher zu spezifizieren, ist in if_vlan.h die Struktur struct vlan_ioctl_args definiert. Im Folgenden soll nun deren Aufbau und Funktion anhand des QuelltextListings 2.3 beschrieben werden. Quelltext 2.3: Die 1 2 3 4 5 6 7 8 9 10 11 12 13 vlan_ioctl_args Struktur struct vlan_ioctl_args { int cmd ; char device [24] union { char device2 [24]; int VID ; unsigned int skb_priority ; unsigned int name_type ; unsigned int bind_type ; unsigned int flag ; } u; short vlan_qos ; }; Das Datenmember cmd (Zeile 2) dient der Spezifizierung des Kommandos, das an das Kernel-Modul abgesetzt werden soll. Abhängig von den Kommandos (Tabelle 2.5) werden weitere Member-Argumente genutzt. Für jeden Befehl wird der den Namen des Devices, das konfiguriert werden soll in device (Zeile 3) gespeichert. Um ein virtuelles Device mit dem Kommando ADD_VLAN _CMD anzulegen, wird der Name des realen Device angegeben, an dem das virtuelle konfiguriert werden soll. Alle anderen Kommandos haben den Namen des zu konfigurierenden, virtuellen Devices als Argument. Das Member device2 (Zeile 5) der Union u dient der Übergabe eines zweiten DeviceNamens. Bisher ist kein Kommando definiert, welches dies nutzt. VID (Zeile 6) wird beim Kommando ADD_VLAN_CMD genutzt, um die gewünschte VID an das Kernel-Modul zu übergeben. 22 Kapitel 2. Grundlagen 2.3. Linux Mit skb_priority (Zeile 7) wird die Priorität eines Socket-Buffers übergeben, die auf einen Wert im User-Priority-Feld eines Frames abgebildet werden soll. Dieses Member wird von den Kommandos SET_VLAN_INGRESS_PRIORITY_MAP_CMD und SET_VLAN_EGRESS_PRIORITY_MAP_CMD genutzt. name_type (Zeile 8) bestimmt die Art, wie die virtuellen Devices benannt werden. Ta- belle 2.6 beschreibt die in if_vlan.h dafür definierten Werte und deren Auswirkung auf die Namensgebung der virtuellen Devices. In bind_type (Zeile 9) wird bestimmt, ob eine VID für den gesamten Kernel oder für jedes reale Device eindeutig ist. Zur Zeit der Erstellung dieser Arbeit existiert kein Kommando, das dieses Member nutzt. flag (Zeile 10) nimmt den Key für ein Flag auf, das gesetzt werden soll, wenn für cmd (Zeile 2) das Kommando SET_VLAN_FLAG_CMD gesetzt wurde. Zur Zeit dieser Arbeit existiert nur das REORDER_HDR Flag, das angibt, ob der Header neu geordnet werden soll. Dies soll die Kompatibilität zu diversen Programmen wahren, bedeutet aber auch Performance-Einbußen. Wenn die Option nicht unbedingt erforderlich ist, wird empfohlen, sie nicht zu nutzen. Das Datenmember vlan_qos (Zeile 12) hat zwei Funktionen: Hauptsächlich wird es genutzt, um bei den Kommandos SET_VLAN_INGRESS_PRIORITY_MAP_CMD und SET_VLAN _EGRESS_PRIORITY_MAP_CMD den User-Priority-Wert anzugeben, auf den ein Prioritätswert der Socket-Buffer abgebildet wird. Zusätzlich wird es beim Kommando SET_VLAN_FLAG_CMD genutzt, um einen optionalen Wert für das in flag angegebene Flag aufzunehmen. Kommando Bedeutung Argumente (Zeilennr.) ADD_VLAN_CMD Erstelle ein VLAN VID (3) DEL_VLAN_CMD Entferne ein VLAN SET_VLAN_INGRESS- Setze ein Socket-/User-Priority Wertepaar für Ingress-Priority-Map skb_priority (8) Setze ein Socket-/User-Priority Wertepaar für Egress-Priority-Map skb_priority (8) _PRIORITY_MAP_CMD SET_VLAN_EGRESS_PRIORITY_MAP_CMD GET_VLAN_INGRESS_PRIORITY_MAP_CMD GET_VLAN_EGRESS_PRIORITY_MAP_CMD Wird zur Zeit der Arbeit nicht unterstützt Wird zur Zeit der Arbeit nicht unterstützt 23 vlan_qos (13) vlan_qos (13) 2.3. Linux Kapitel 2. Grundlagen name_type (9) _TYPE_CMD Setze die Namensgebung des virtuellen Interfaces SET_VLAN_FLAG_CMD Setze ein Flag flag (11) SET_VLAN_NAME- vlan_qos (13) Tabelle 2.5: VLAN ioctl Kommandos Wert Namensbildung Beispiel VLAN_NAME_TYPE- vlan<4 Stellen VID> vlan0002 <Device Name>.<4 Stellen VID> eth0.0002 vlan<VID> vlan2 <Device Name>.<VID> eth0.2 _PLUS_VID VLAN_NAME_TYPE_RAW_PLUS_VID VLAN_NAME_TYPE_PLUS_VID_NO_PAD VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD (default) Tabelle 2.6: Namensgebung des VLAN Device 2.3.3 Queueing Disziplinen Queueing Disziplinen (QDiscs) sind die Implementierung von Paket Scheduling Algorithmen unter Linux. Jedem Netzwerk-Device ist eine QDisc zugeordnet. 2.3.3.1 Funktionsweise QDiscs sind zwischen dem jeweiligen Netzwerktreiber und den höheren Protokollschichten angeordnet (Abbildung 2.7). Ist vor dem realen Device ein virtuelles geschaltet, liegen zwischen dem Treiber und den höheren Protokollschichten zwei QDiscs. Die Funktionsweise einer QDisc ist aus der Sicht der angrenzenden Protokollschichten absolut transparent. Für sie existieren nur vordefinierte QDisc-Methoden. Die wichtigsten sind enqueue(), dequeue() und requeue(). Alle Informationen und die Funktionalitäten eines Netzwerk-Devices werden in der net_device-Struktur gehalten, die in netdevice.h definiert ist. Hier wird auch ein 24 Kapitel 2. Grundlagen 2.3. Linux Abbildung 2.7: Funktionsweise von Queueing Disziplinen Zeiger auf die QDisc des Devices gehalten. Wird ein Paket über ein Netzwerk-Device verschickt, so ruft die höhere Protokollschicht die Funktion dev_queue_xmit() in der Struktur auf. Diese übergibt den Socket-Buffer des Pakets an die enqueue()-Funktion der QDisc. Daraufhin startet dev_queue_xmit() die Funktion qdisc_run(), die dafür sorgt, dass die Funktion qdisc_restart() zyklisch ausgeführt wird. Bevor die beiden Abbruchbedingungen dieser Schleife erläutert werden, wird näher auf die Funktion qdisc_restart() eingegangen. qdisc_restart() ruft die dequeue()-Funktion der QDisc auf. Wird ein Paket von der QDisc zurückgeliefert, so wird das Paket mit der Funktion hard_start_xmit() an den Treiber übergeben und über die Hardware versendet. Im Falle eines virtuellen Devices wird das Paket mit der Funktion dev_queue_xmit() an das reale Device übergeben. Ist das Netzwerk-Interface nicht bereit ein Paket entgegen zu nehmen, blockiert es. In diesem Fall wird das Paket mit der Funktion requeue() wieder in die QDisc an der ursprünglichen Position eingefügt. Das Blockieren des NetzwerkInterfaces gilt als eine Abbruchbedingung für die Schleife in qdisc_run(). Die zweite Abbruchbedingung betrifft den Fall, dass die Queueing Disziplin kein Paket zurück liefert. Das heißt nicht, dass sich kein Paket in der Queue befindet. Je nach Bedingung des verwendeten Algorithmus könnte die QDisc enthaltene Pakete noch zurückhalten, um z.B. die Bandbreite zu begrenzen. Gibt die QDisc ein zurückgehaltenes Paket zum Senden frei oder kehrt das Device aus einem blockierten Zustand 25 2.3. Linux Kapitel 2. Grundlagen zurück, kann nicht auf das Anstoßen von qdisc_run() durch das Eintreffen eines weiteren Pakets in der QDisc gewartet werden. Hierzu existiert der Software-Interrupt NET_TX_SOFTIRQ, der die Funktion aufruft. Ausgelöst wird der Interrupt zum einen durch den Jiffies-Timer, der den Kernel-Takt von 1ms erzeugt, und zum anderen durch den Übergang des Hardware-Devices vom blockierten in den unblockierten Zustand. Die Granularität des Jiffies-Timers beträgt im Linux-Kernel 2.6 1ms, der auf 1µs heruntergerechnet wird. Bei der Kernel-Konfiguration stehen Alternativen zum JiffiesTimer zur Verfügung, die im Folgenden genannt werden. Auf Architekturen, die über ein CPU-Cycle-Counter verfügen, kann dieser als Timer für das QoS-Modul konfiguriert werden. Er weist eine weit höhere Granularität als 1µs auf, die dann auf 1µs hochgerechnet wird. Es wird empfohlen diesen zu nutzen falls er zur Verfügung steht, allerdings kann es zu Problemen auf Mehrprozessorsystemen kommen oder bei Prozessoren, die Frequenzwechsel unterstützen. Die zweite Alternative ist die Funktion gettimeofday(), die eine Granularität von genau 1µs aufweist. Es wird jedoch von dieser Möglichkeit abgeraten, da sie zu viel CPU-Zeit verbraucht. 2.3.3.2 Aufbau Queueing-Disziplinen unterscheiden sich untereinander in ihrem internen Aufbau (Abbildung 2.8). Grundsätzlich lassen sie sich in zwei Kategorien einordnen: die klassenlosen und die klassenbehafteten Queueing Disziplinen. Die klassenlosen QDiscs sind Implementierungen von Paket-Scheduling-Algorithmen, deren interner Aufbau nicht weiter konfigurierbar ist. Sie besitzen eine in Grenzen konfigurierbare Funktionalität und sind nicht erweiterbar. Die Art, wie Daten klassifiziert werden, ist fest vorgegeben und lässt sich nicht verändern. Klassenbehaftete Queueing Disziplinen sind modular aufgebaut [WPR+ 02]. Intern lassen sich Klassen konfigurieren, die jeweils verschiedene Eigenschaften, wie z.B. Prioritäten, besitzen. Mit diesen Klassen lassen sich Baumstrukturen herstellen, deren Aufbau jedoch von der jeweiligen Queueing Disziplin abhängt. So kann die Klassenhierarchie bei bestimmten QDiscs nicht in die Tiefe wachsen. Klassen halten selbst keine Daten, ihnen muss eine QDisc zugeordnet werden, die die Daten auffängt. Dies kann sowohl eine klassenlose als auch eine klassenbehaftete QDisc sein, was wiederum eine weitere Verästelung der Baumstruktur zur Folge hat. 26 Kapitel 2. Grundlagen 2.3. Linux Abbildung 2.8: Aufbau von Queueing Disziplinen Das Verhalten der Blatt-QDiscs nimmt durch ihr eigenes Verhalten Einfluss auf das Verhalten ihrer Eltern-QDisc. Um Pakete den Klassen zuzuordnen, bringen viele QDiscs ihren eigenen Klassifizierer mit, die auf ihr Verhalten zugeschnitten sind. Um höhere Flexibilität beim Klassifizieren der Pakete zu erreichen, stehen eine Reihe von individuellen Filtern zur Verfügung, deren Funktionalität vom einfachen Erkennen eines Feldes im IPHeader (dsmark), über die Auswertung von Routinginformationen (route, fw), bis hin zur kompletten Mustererkennung im gesamten Paket (u32) reicht. Wird ein Filter an einer QDisc oder Klasse angebracht, überschreibt er deren eigenen Klassifizierer. Existieren mehrere Filterregeln nebeneinander, kann die Reihenfolge, in der sie abgefragt werden, durch einen Prioritätswert bestimmt werden. Wird ein Klasse oder QDisc gelöscht, so werden automatisch alle ihre Kindklassen und deren QDiscs mitgelöscht. Jedoch kann eine Klasse nur gelöscht werden, wenn kein Filter mehr auf diese Klasse verweist. Vom Linux-Kernel werden alle Elemente des Baums über 32-Bit Handles verwaltet. Diese sind zu jeweils 16-Bit in eine Major- und eine Minor-Nummmer aufgeteilt, welche nichts mit den Major- und Minor-Nummern der Linux-Gerätedateien zu tun haben. Die Handles sind für jedes Device eindeutig, können sich somit über alle Devices wiederholen. Die Minor-Nummer von Queueing Disziplinen ist stets null. Ausnahme davon sind die Root Queueing Disziplin und die Ingress Queueing Disziplin. Das Handle der 27 2.3. Linux Kapitel 2. Grundlagen Root QDisc ist FFFF:FFFF und als TC_H_ROOT definiert. Für die Ingress QDisc ist das Handle FFFF:FFF1 reserviert, welches als TC_H_INGRESS definiert ist. Beide beschreiben keine Queueing Disziplin im eigentlichen Sinne, sondern sind vielmehr Andockpunkte für die Wurzel-QDisc. Queueing Disziplinen, die an der Ingress QDisc angebracht werden, können nur für Traffic Policing genutzt werden. Nicht alle Queueing Disziplinen sind dazu geeignet. Beim Hinzufügen einer QDisc kann der Benutzer die Major-Nummer aus einem Intervall von 0x0001 bis 0x7FFF selbst wählen. Wird kein Handle vom Benutzer übergeben, weist der Kernel der QDisc eine Major-Nummer zwischen 0x8000 und 0xFFFF zu. Die Major-Nummer einer Klasse entspricht immer der Major-Nummer der QDisc, der sie angehört. Die Minor-Nummer hat einen zur Major-Nummer eindeutigen Wert im Intervall von 0x0001 bis 0xFFFF. Zur Konfiguration der Queueing Disziplinen steht das Programm tc zur Verfügung, dass im Paket iproute2 enthalten ist. Möchte man jedoch die QDiscs von einem Programm heraus konfigurieren, so existiert hierfür die Netlink-Socket-Schnittstelle. Deren Dokumentation ist jedoch sehr spärlich bis gar nicht vorhanden, so dass sie im Rahmen dieser Arbeit analysiert werden musste. Aus diesem Grund wird sie im Kapitel 3.4 beschrieben. Zu näheren Information über die Konfiguration der Queueing Disziplinen mit tc, sei [Hub03] empfohlen. 28 Kapitel 3 Analyse 3.1 Anforderungen Abbildung 3.1: Use-Case Diagramm 29 3.1. Anforderungen 3.1.1 Kapitel 3. Analyse Anfordern von QoS Es gibt zwei Möglichkeiten, wie QoS-Eigenschaften für einen Kommunikationspfad angefordert werden können. Zunächst kann ein Prozess versuchen, die QoS-Eigenschaften zur Laufzeit zu reservieren. Ein Modul, dass die Resourcen verwaltet, entscheidet dann, ob sie diese für den Prozess reservieren kann. Voraussetzung dafür ist, dass das Modul die Topologie des Netzes kennt, da sonst QoS-Parameter, wie Ende-zu-Ende-Verzögerung, nicht ermittelbar sind. Die zweite Möglichkeit ist, mit einer Scheduability-Analyse zu bestimmen, ob die benötigten QoS-Parameter für die Kommunikationspfade bereitgestellt werden können. Dies findet meistens in der Industrieautomation Anwendung, denn dort findet man gewöhnlich statische Konfigurationen vor. Die Konfiguration der QoS-Parameter lässt sich dadurch schon vor der Inbetriebnahme der Anlage vornehmen und muss nicht erst von den einzelnen Knoten beantragt werden. 3.1.2 Steuern und Regeln In einer automatisierten Anlage treten sich zyklisch wiederholende Prozesse auf. Zur Steuerung einer solchen Anlage müssen Stellgrößen jeweils einmal pro Zyklus an die entsprechenden Aktoren übertragen werden. Desweiteren werden auch die Prozesszustände von den Sensoren für jeden Zyklus erfasst. Dies resultiert in isochron propagierten Datagrammen, die an jeden Knoten gerichtet sind, der sie benötigt. Damit der sendende Knoten nicht die Adressen aller Knoten halten muss, die seine Informationen benötigen, und kein Overhead durch mehrfach gesendete Daten an verschiedene Knoten entsteht, wird für jede Nachrichtenklasse eine MulticastGruppe eingerichtet. Somit können die Knoten, die bestimmte Daten benötigen, sich diese aus dem Verkehr herausgreifen. Diese Form der Kommunikation tritt meistens im hohen bis mittleren Prioritätsspektrum auf. So werden für die Prozesssteuerung kritischen Mess- und Steuerdaten mit hoher Priorität verteilt, während Messdaten, die einer statistischen Erfassung dienen, eine mittlere Priorität aufweisen. Die Stationen, die eine Anlage steuern, befinden sich in der Regel in einem Netzwerksegment, dessen Topologie statisch ist. Da alle Adressen bekannt sind, wird die 30 Kapitel 3. Analyse 3.1. Anforderungen Nutzung der höheren Protokollschichten unnötig. Daher besteht der Protokoll-Stack in der Automation meist nur aus Layer 1, 2 und 7 [Fur03]. Dies hat mehrere Vorteile: Zum einen verringern sich der Delay und der Jitter im Stack, da weniger Code ausgeführt werden muss und zeitaufwendige Protokolle wie ARP nicht mehr zur Anwendung kommen. Zum anderen haben Mess- und Steuerdaten in der Regel nur eine geringe Größe. Durch Aussparen der höheren Protokolle wird ein massiver Overhead durch die Protokoll-Header verhindert. 3.1.3 Alarm-Signalisierung Kommt es in einer automatisierten Anlage zu Fehlerzuständen, Unfällen oder anderen unvorhersehbaren Ereignissen, muss die Anlage sofort gestoppt werden können, um schwere Verletzungen oder eine Beschädigung der Anlage zu verhindern. Um das zu erreichen, muss eine Nachricht höchster Priorität an alle Knoten der Anlage gesandt werden, um sie von dem Fehlerzustand unterrichten. Diese stellen den Betrieb ein und lassen die Anlage in einen sicheren Zustand zurückkehren. 3.1.4 Datei-Download / Upload Durch vertikale Integration ist es möglich, von einem dedizierten Rechner Dateien von bzw. auf einen Automationsrechner zu laden. Dies wird genutzt, um z.B Firmware-Updates auf eine Steuerungseinheit zu laden oder eine lokal gespeicherte Log-Datei bzw. Messdaten von einem Automationsrechner herunterzuladen. Um große Datenmengen zu transferieren, ist die Nutzung klassischer Netzwerkprotokolle, wie FTP oder HTTP in Verbindung mit TCP/IP empfehlenswert. TCP/IP spaltet selbstständig große Dateien auf und stzt sie nach Ankunft am Empfänger wieder aus den einzelnen Paketen zusammen. Das Bestreben ist es, möglichst große Pakete zu bilden, um einem zu großen Overhead durch die Protokoll-Header entgegenzuwirken. Datenverkehr dieser Art hat meist eine niedrige Priorität. 3.1.5 Human-Machine-Interface Die Überwachung einer Anlage geschieht meist von einem Rechner durch ein HumanMachine-Interface (HMI), welches den laufenden Prozess visualisiert und die Abfragemöglichkeiten von Mess- und Statusinformationen bietet. Weiterhin kann mit dem 31 3.1. Anforderungen Kapitel 3. Analyse HMI in einen Automatisierungsprozess eingegriffen werden, um z.B. Regelgrößen zu justieren oder einen Nothalt auszulösen. Auch der Datei-Download bzw. -Upload kann darüber angestoßen werden. Die Informationen, die für die Überwachung eines Prozesses gebraucht werden, können sowohl nach dem Push, als auch nach dem Pull-Modell erfolgen. Ersteres kommt hauptsächlich bei der Visualisierung zum Einsatz, wo zyklische Updates von Daten nötig sind. Teilweise werden die Steuer- und Messdaten genutzt, die im Netz schon für die Steuerung und Regelung des Systems propagiert werden. Rein für die Visualisierung benötigte Daten, werden in der Regel in größeren Intervallen mit niedrigerer Priorität verteilt. Einzelne Statusabfragen erfolgen in der Regel nach dem Pull-Modell, da diese nur sporadisch benötigt werden. Als Reaktion auf eine Statusabfrage wird eine einzelne Aktualisierung der letzten Statusinformationen erwartet. Wird eine Visualisierung nicht durchgängig benötigt, so kann die Verteilung der rein für diesen Zweck benötigten Daten nach dem Publish-Subscriber-Modell angestoßen bzw. abgemeldet werden um Netzressourcen zu schonen. Eingriffe in den Ablauf des Automationsprozesses erfolgt wiederum durch einzelne Nachrichten, die an einzelne Rechner oder Rechnergruppen im Netz gerichtet sind. 3.1.6 Sonstige Anforderungen Aufgrund des Automatisierungskontextes in dem diese Arbeit steht, ergeben sich weitere Anforderungen an die Entwicklung der Kommunikationsschicht, die im Folgenden erläutert werden. 3.1.6.1 Entwicklung unter Linux Die Entwicklung unter dem offenen Betriebssystem Linux wird aufgrund der Einbettung der Arbeit in ein Forschungs- und Entwicklungs-Thema des Labors für Verteilte Systeme der FH Wiesbaden vorausgesetzt. Das Thema ”Real-Time Data Propagation in Distributed Environments” sieht eine Netzwerk-Kommunikationsschicht mit QoS-Fähigkeit auf Basis des Linux-QoS-Frameworks vor. 32 Kapitel 3. Analyse 3.1.6.2 3.2. Kommunikationsformen und ihre QoS-Anforderungen Portierbarkeit In der Automation kommt eine Vielzahl von Embedded-PC-Architekturen zum Einsatz. Die Kommunikationsschicht sollte auf den meisten dieser Architekturen lauffähig sein. Dies kann gewährleistet werden, indem die von Linux gebotenen, Hardwareunabhängigen Schnittstellen genutzt werden. Damit würden alle Architekturen, die Linux unterstützt, von der Kommunikationsschicht mit unterstützt werden. Obwohl das Ziel der Arbeit eindeutig auf Ethernet ausgerichtet ist, soll auch eine Portierung der Kommunikationsschicht auf andere Layer-2-Protokolle, wie Firewire oder PPP, so einfach wie möglich sein. 3.1.6.3 Modularität Die zu entwickelnde Software soll modular aufgebaut sein. Damit soll eine einfache Portierung und Weiterentwicklung gewährleistet werden. 3.1.6.4 Performanz-Optimierung auf Senden und Empfangen Für die Einhaltung von QoS ist ein reibungsloses und schnelles Senden bzw. Empfangen nötig. Daher muss bei der Entwicklung der Software eine Optimierung der Performanz in Bezug auf den Sende- und Empfangsbetrieb vorgenommen werden. 3.2 Kommunikationsformen und ihre QoS-Anforderungen Die in Abschnitt 3.1 vorgestellten Kommunikationsmuster stellen unterschiedliche Anforderungen an die in Kapitel 2.1 definierten QoS-Parameter. In diesem Abschnitt werden diese Anforderungen für die betreffenden Use-Cases erkläutert. 3.2.1 Steuern und Regeln Mess- und Steuerwerte sind meist kritische Daten, bei deren Verlust der Zustand einer Anlage nicht definierbar ist. Daher stellt die Form der Kommunikation höchste 33 3.2. Kommunikationsformen und ihre QoS-Anforderungen Kapitel 3. Analyse Anforderungen an Dienstverfügbarkeit und Paketverlustrate. Die Dienstverfügbarkeit sollte so hoch wie möglich sein, wohingegen die Paketverlustrate bei Null liegen sollte. Um die Aktualität der Daten zu wahren, ist ein sehr geringer Delay notwendig, der in vielen Fällen harten Echtzeitanforderungen standhalten muss. Handelt es sich dabei um Daten mittlerer Priorität, ist auch ein höherer Delay tolerierbar. Da es sich um isochron propagierte Daten handelt, wird ein geringer Jitter angestrebt, damit die Synchronität von Abläufen gewährleistet werden kann. Die Anforderung an den Durchsatz eines isochron propagierten Datenstroms hängt von der Paketgröße und dem Sende-Intervall ab. Jedoch sind diese Anforderungen eindeutig bestimmbar, da die Paketgröße und das Sendeintervall in der Regel bekannt sind. 3.2.2 Alarm-Signalisierung Für die Alarm-Signalisierung sind die Anforderungen an die Verfügbarkeit höher, als die für Steuern und Regeln. Denn, fällt die Propagation von Mess- und Steuerdaten aus, kann der Schaden durch schnelles Signalisieren eines Alarms noch begrenzt werden, nicht jedoch, wenn gleichzeitig auch der Service für die Alarmsignalisierung ausfällt. Genauso verhält es sich für die Paketverlustrate. Um einen schnellstmöglichen Stop aller Vorgänge zu gewährleisten, muss die geringstmögliche Verzögerung erzielt werden, daher sind die Anforderungen an den Delay sehr hoch. Da es sich bei der Alarm-Signalisierung nur um die Propagation einzelner Pakete handelt, bestehen praktisch keine Anforderungen an Jitter oder Durchsatz. 3.2.3 Datei-Download / Upload Ein Datei-Download bzw. Upload hat recht geringe Anforderungen an Service-Availability und Paket-Verlust-Rate. Es ist zwar eine hohe Dienstverfügbarkeit erwünscht, jedoch ist diese nicht kritisch für die Stabilität einer Anlage. Noch unkritischer ist die Paket-Verlust-Rate, da verloren gegangene Pakete durch TCP erneut gesendet werden. 34 Kapitel 3. Analyse 3.3. Einflussnahme auf die QoS-Parameter Delay und Jitter sind ebenfalls nicht von kritischer Bedeutung für die Übertragung von Dateien. Allein der Durchsatz der Pakete ist von größerer Bedeutung, um Dateien so schnell wie möglich zu übertragen. Dies gilt aber nur in dem Rahmen, in dem die Anforderungen der Kommunikationsmuster höherer Priorität nur geringfügig oder gar nicht beeinflusst werden. Wobei auch hier ein hoher Durchsatz wünschenswert ist, um die Belastung des Netzwerks zeitlich so kurz wie möglich zu halten. 3.2.4 Human-Machine-Interface Die Überwachung und der Eingriff in den Automationsprozess erfolgen nach allen bisher genannten Kommunikationsformen, jedoch teilweise nach eigenen Prioritäten. Die ausschließlich zur Visualisierung benötigten Messdaten werden, wie Daten zum Steuern und Regeln, isochron propagiert, jedoch in größeren Intervallen und mit geringerer Priorität. Desweiteren ist deren Toleranz gegenüber Delay, Jitter und Paket-Verlust weitaus größer. Eingriffe in den Prozessablauf und Statusabfragen gleichen im Ablauf der Kommunikationsform der Alarmsignalisierung. Die Anforderungen an Delay und Paket-Verlust hängen stark von der Art der Nachrichten und deren Priorität ab. Eine einfache Abfrage von Messdaten ist von weit geringerer Priorität, als ein vom HMI ausgelöstes Alarmsignal. Die Änderung einer Stellgröße liegt in einem Bereich dazwischen. Vom HMI ausgelöste Datei-Up- und Downloads haben genau die Anforderungen, die für diesen Anwendungsfall definiert wurden. 3.3 Einflussnahme auf die QoS-Parameter Um die einzelnen QoS-Parameter zu beeinflussen, und damit geforderte Grenzen einhalten zu können, müssen zunächst deren Abhängigkeiten analysiert werden. Für die Betrachtungen einzelner Parameter wird auf die, in [Jas02] verwandten, analytischen Methoden zurückgegriffen, die aud [BT04] basieren. Diese beschreiben das Worst-Case-Verhalten eines Netzwerkelements anhand einer Ankunftskurve und einer Servicekurve. Alle hier verwendeten Betrachtungen gehen von einer konstanten Paketgröße aus. 35 3.3. Einflussnahme auf die QoS-Parameter Kapitel 3. Analyse Die Ankunftskurve α(t) eines Elements, wie z.B. einer QDisc, ist eine stetig steigende Funktion, die die eintreffenden Daten über die Zeit charakterisiert. Sie begrenzt den Datenfluss R nach oben, wenn für alle Zeitpunkte u ≤ t gilt: R(t) − R(u) ≤ α(t − u) (3.1) Eine einfache Möglichkeit die Ankunftskurve zu beschreiben, ist der Token-Bucket, der durch die Parameter b für die Bucketgröße und r für die Abflussrate beschrieben werden kann. Es wird bei der Verwendung der Ankunftskurve davon ausgegangen, dass alle Daten eines Bursts gleichzeitig in der Queue eintreffen. αr,b (t) = r · t + b (3.2) Der Parameter Bucketgröße gibt dabei die Größe von Bursts, bei angesammelten Token, in [Bytes] oder [Paketen], an. Die maximale Ankunftsrate entspricht der Abflussrate des Token-Buckets in [Bytes/s] oder [Pakete/s]. Die Servicekurve β(t) beschreibt, wann ein Paket in einem Element bedient wird. Sie wird in den meisten Fällen in Form der ”rate latency function” dargestellt. ( βR,T (t) = R[t − T ]+ = R(t − T ) wenn t > T 0 wenn t ≤ T (3.3) R beschreibt die Rate in der Daten, die sich im System befinden abgearbeitet werden, in [Bytes/s] oder [Pakete/s]. Der Parameter T stellt die maximale Latenz [s] des Systems dar, bevor die ersten Daten abgearbeitet werden können. Ankunfts- und Servicekurve werden anhand eines Beispiels in Abbildung 3.2 erklärt. Das Beispielsystem stellt eine Queue mit FIFO-Charakteristik dar. Die Ankunftskurve wird durch einen Tokenbucket begrenzt, dessen Parameter b die maximale Paketgröße Lmax zugewiesen wird. Diese wird mit der Länge eines maximal großen Frames, inklusive Präambel, Header, Frame-Check-Sequence und Inter-Frame-Gap, angenommen und entspricht damit b = Lmax = 1538Byte. Präambel, Frame-Check-Sequence und Inter-Frame-Gap werden zwar erst in der Hardware hinzugefügt [WPR+ 02], jedoch ist die Bedienungszeit der Pakete von deren Größe abhängig. Um den maximalen, belegten Speicher in der Queue zu berechnen, sind Präambel, Frame-Check-Sequence und Inter-Frame-Gap wieder abzuziehen. Die Ankunftsrate r wird mit 25M Bit/s angenommen. 36 Kapitel 3. Analyse 3.3. Einflussnahme auf die QoS-Parameter Abbildung 3.2: Service- und Ankunftskurve Der Parameter R der Servicekurve entspricht der Linkkapaziät des Netzwerk-Devices C = 100M Bit/s. Die Latenz T ist abhängig davon, wie schnell ein Paket über die Queue durchgereicht werden kann. Angenommen das Durchlaufen der Queue würde 1000 Instruktionen in Anspruch nehmen, dann würde die Queue auf einem Prozessor mit 1GHz Taktfrequenz und einer Instruktion pro Takt eine Latenz von 1µs aufweisen. Mit diesen Parametern lassen sich nun die folgenden Werte berechnen: • die maximale Verweilzeit eines Pakets in der Queue. • den maximal genutzten Speicher der in der Queue gehaltenen Pakete Die Formel für die maximale Verweilzeit dmax in der Queue lautet: dmax = T + b R Der maximal belegte Speicher Bmax berechnet sich mit: 37 (3.4) 3.3. Einflussnahme auf die QoS-Parameter Bmax = α(T ) = r · T + b1 Kapitel 3. Analyse (3.5) Das Ergebnis muss auf das nächste n-fache der Paketgröße aufgerechnet werden. Durch Einsetzen ergeben sich folgende Werte: dmax = 124, 04µs Bmax = 3028Byte 3.3.1 Service Availability Die Dienstverfügbarkeit ist stark abhängig von der Stabilität aller beteiligten Elemente. Es darf zu keinen Hardwareausfällen kommen. Die Software muss sorgfältig programmiert sein, unter den Gesichtspunkten, dass Deadlock-Situationen um jeden Preis zu verhindern sind. Um die Dienstverfügbarkeit zu erhöhen, sind redundante Lösungen denkbar. Diese sind jedoch nicht Schwerpunkt dieser Arbeit und sollen daher nicht weiter betrachtet werden. 3.3.2 Packet Loss Rate Pakete können aus zwei Gründen verloren gehen. Zum einen kann es bei der Übertragung eines Pakets zu Fehlern durch Störeinflüsse von außen kommen, wodurch das Paket beim Empfang verworfen wird. Dem kann man durch Einsatz störungsarmer Hardware, wie z.B. Kategorie 5 Kabel, entgegenwirken. Die zweite Ursache ist das Überlaufen eines Puffers in einem Switch oder Host. In einem Host kommt das nicht vor, solange der empfangende Prozess die Pakete schnell genug abholt. In einem Switch werden Pakete mit einer höheren Wahrscheinlichkeit verworfen. Treffen an zwei Ports eine Serie von Pakete mit dem gleichen Zielport ein, und deren addierte Ankunftskurven liegen höher als die Servicekurve des Zielports, beginnt sich die Ausgangsqueue des Ports mit Verweisen auf die gespeicherten Pakete zu füllen. Geschieht das über einen längeren Zeitraum, so kann die Queue des Ports 1 b entspricht hier der maximalen Paketgröße inklusive Header, aber ohne Präambel, CRC und IFG: 1514Byte 38 Kapitel 3. Analyse 3.3. Einflussnahme auf die QoS-Parameter überlaufen. Daraufhin werden alle weiteren Pakete verworfen, bis wieder Platz zur Aufnahme eines Zeigers zur Verfügung steht. Um das zu verhindern, muss für die Menge aller Ports P gelten [Jas02]: X ri < R j (3.6) ∀ i 6= j ∈ P Bedingt durch die niedrige Ankunftsrate, können weiter auftretende Bursts abgebaut werden können. 3.3.3 Delay Die Garantie zur Einhaltung dieser Grenzen ist von verschiedenen Faktoren abhängig. Grob setzt sich der Delay aus drei Teilen zusammen: Delay im Sendeknoten, auf dem Medium und im Empfängerknoten. Im Folgenden sollen alle Teile genauer betrachtet werden. 3.3.3.1 Delay im Sendeknoten Hier sind wiederum einzelne Elemente für unterschiedliche, variable und konstante Anteile an der Gesamtlatenz beteiligt. Zum einen ist der Delay im Systemcall zu betrachten, mit dem die zu versendenden Daten an das Betriebssystem übergeben werden. Linux-Systemcalls der Socket-Schnittstelle haben grundsätzlich eine vorhersehbare Bearbeitungszeit, die von der Schnelligkeit der CPU abhängt. Da sie jedoch von einem Aktivitätsträger im Kern unterbrochen werden können, ist der dadurch verursachte Delay nicht deterministisch. Das ist im Falle von Soft- und HardwareInterrupts recht unkritisch, da diese in der Regel nur aus ein paar Anweisungen bestehen, nach deren Abarbeitung der Systsemcall weiter ausgeführt werden kann. Problematisch kann es jedoch bei sogenannten Bottom-Halfs und Tasklets werden. Dabei handelt es sich um asynchron auftretende Aktivitätsträger im Kern, die dazu ausgelegt sind auch Tasks, die mehrere tausend Taktzyklen in Anspruch zu nehmen. Diese können nur von Interrupts unterbrochen werden, sind jedoch dazu in der Lage Systemcalls zu unterbrechen. Das Problem könnte durch die Verwendung einer Echtzeiterweiterung umgangen werden. Für weitere Informationen zu Tasklets und Bottom-Halfs sei [BC02] empfohlen. 39 3.3. Einflussnahme auf die QoS-Parameter Kapitel 3. Analyse Das nächste Element, das den Delay beeinflusst, ist der Protokoll-Stack. Um diesen Teil-Delay gering zu halten, besteht der Stack in der Automation nur aus Layer 1, 2 und 7. Daten, die nicht den Echtzeitanforderungen unterliegen und der vertikalen Integration dienen, sollten die restlichen Layer weiterhin nutzen können. Einen nicht unerheblichen Einfluss auf den Delay hat die verwendete Queueing Disziplin. Der Delay innerhalb der Queueing Disziplin sich lässt mit dem Ankunftsbzw. Servicekurven-Modell ermitteln, wenn folgende Rahmenbedingungen erfüllt sind [Jas02]: • Der Ankunftsprozess muss für jede Verbindung, d.h. sendende Quelle auf einer Station, charakterisiert werden können. Dies kann, wie oben erläutert, durch den Einsatz eines Token Buckets erreicht werden. Dieser sollte nach erwarteter Senderate und Paketgröße der Quelle dimensioniert werden, um eine obere Grenze durchzusetzen. Überschreitet der Prozess diese Grenze, wird er mit einem erhöhten Delay durch den Token Bucket bestraft, nicht jedoch die restlichen Quellen. • Die maximale Paketgröße muss bestimmbar sein. Dies ist wichtig, da der Worst-Case-Delay stark abhängig von der Paketgröße ist. Diese bestimmt in Abhängigkeit mit der Linkkapazität, die Zeit, die es braucht um ein Paket auf das Medium zu senden. Wird die Paketgröße klein gehalten, verkürzt sich der Delay. In der Automation werden in der Regel kleine Pakete verschickt, die Sensordaten enthalten. Durch die vertikale Integration von Netzwerkkommunikation aus dem Office-Bereich auf dem gleichen Netzwerksegment, besteht die Gefahr, dass diese Pakete den Worst-Case-Delay alleine durch ihre Größe drastisch anheben. Dem kann durch ein Verkleinern der Maximum Transmission Unit (MTU) am Netzwerktreiber entgegengewirkt werden. • Die verwendeten Paket Scheduling Algorithmen müssen bekannt sein. Durch das Wissen über die Algorithmen kann deren Verhalten durch das Ankunfts-/Servicekurven-Modell berechnet werden. Dies ist durch die Queueing Disziplinen unter Linux gewährleistet. Verwendet man die Prio Queueing Disziplin, so lassen sich nach den oben genannten Bedingungen Aussagen über den Worst-Case-Delay innerhalb der QDisc treffen. Hierzu sind einige Definitionen nötig: 40 Kapitel 3. Analyse 3.3. Einflussnahme auf die QoS-Parameter • Für Lmax wird ein Wert zwischen 84 und 1538Byte angenommen. In diesem Rahmen bewegt sich die Größe eines validen Ethernet-Frames, wie er auf dem physischen Medium gesendet wird. • Die Prio QDisc besteht aus n Bändern, mit den aufsteigenden Prioritäten i ∈ [0; n − 1], wobei 0 die niedrigste Priorität darstellt. • Für jedes Band der Priorität i der Prio-QDisc existieren mi Quellen, für die gelten mi ∈ N . • Jede Quelle j wird durch einen eigenen Token-Bucket begrenzt, dessen BurstParameter bj auf die maximale Paketgröße Pmax festgelegt wird. Der Parameter rj ist für jede Quelle unterschiedlich. Zum Ermitteln der Ankunftskurve αi des Bandes i werden die Token-Bucket-Parameter jeder Quelle addiert. bi = X bj = mi ∗ Pmax (3.7) rj (3.8) ∀j∈mi ri = X ∀j∈mi αi (t) = ri · t + bi (3.9) • Um Stausituationen zu vermeiden, dürfen die addierten Ankunftsraten der Bänder mit der Priorität i > 0 nicht die Linkkapazität C übersteigen. Daher gilt: n−1 X X rj < C (3.10) i=1 ∀j∈mi Betrachtet man nun das Band mit der höchsten Priorität, so gelten die Bedingungen einer Queue mit FIFO-Charakteristik, wie sie oben schon betrachtet wurde. Da ein Paket der höchsten Priorität immer gegenüber den Paketen niedriger Priorität bevorzugt wird, kann es nur von einem gerade gesendeten Paket oder von Paketen gleicher Priorität, die vor ihm eingetroffen sind, verzögert werden. Daher gilt für das Band mit der höchsten Priorität die Formel 3.4. Für alle Bänder niedrigerer Priorität gilt, dass die Ankunftskurven der höherprioren Bänder zur eigenen hinzu addiert wird. Also gilt für alle Bänder: 41 3.3. Einflussnahme auf die QoS-Parameter dmaxi = T + Kapitel 3. Analyse n−1 X bj j=i R (3.11) Diese Formel lässt die konstanten Verzögerungen außer Acht, die durch die Ausführung des Codes im Betriebssystem verursacht werden. Diese muss durch Zeitmessungen im Kernel ermittelt werden. Der Delay durch den Treiber nimmt nur einen sehr geringen Teil ein, da Treiber dazu ausgelegt sind, die Daten schnellstmöglich an die Hardware zu übergeben. Daher ist die Wahrscheinlichkeit einer Unterbrechung sehr gering. Der Delay in der Hardware entsteht sowohl durch Hinzufügen von Präambel, SFD und Frame-Check-Sequence, als auch durch die Zeit, die benötigt wird ein Paket auf das Medium zu legen. Die Zeit um die Daten auf das Medium zu senden wird durch seine Linkkapazität gegeben. Ein Fast-Ethernet-Interface (100MBit) sendet 12,5Byte/µs, was heißt, dass ein 1538Byte großes Paket in 123.04µs versendet wird. 3.3.3.2 Delay auf dem Medium Der Delay auf dem Medium ist abhängig von sowohl der Signallaufzeit, und somit der Länge der verwendeten Kabel, als auch der Anzahl und Verzögerung in den Netzwerkvermittlungselementen. Die Signallaufzeit in einem UTP-Kabel der Kategorie 5 beträgt V = 2 · 102 m/µs [Hal96]. Dadurch ergibt sich folgende Formel für die Verzögerung Tp [µs]: Tp = S[m] V [m/µs] (3.12) Zur Signallaufzeit addiert sich der Delay, der in den Netzwerkvermittlungselementen auftritt. Da sich der Umfang dieser Arbeit auf die Betrachtung von Ethernet beschränkt, kommen hier nur zwei Typen dieser Elemente in Frage: Hubs und Switches. Hubs sind keine Vermittlungselemente im eigentlichen Sinne, da sie das empfangene Signal nur verstärken und auf allen Ports weiterleiten. Sie werden jedoch erwähnt, da ihr Einsatz eine weitere Verzögerungszeit hinzu addiert. Der Standart IEEE 802.3 [IEE02] definiert zwei Klassen von Repeatern mit einer 100MBit Rate, die sich durch unterschiedliche Verzögerungszeiten auszeichnen. Bei der Nutzung eines schnelleren Klasse II Hub gilt: Delay ≤ 3.68µs. 42 Kapitel 3. Analyse 3.3. Einflussnahme auf die QoS-Parameter Der Delay eines Switches setzt sich wiederum aus zwei Zeiten zusammen. Zum einen die Zeit, die ein Switch benötigt, um ein Paket zu vermitteln, ohne dass QueueingEffekte eintreten. Diese Zeit ist vom jeweiligen Switch abhängig. Kommt es zum Queueing, kann je nach verwendeten Paket Scheduling Algorithmus entweder die Formel 3.4 oder 3.11 zur Anwendung kommen. 3.3.3.3 Delay im Empfangsknoten Im Empfangsknoten kommen wieder die Verzögerungen in Hardware, Treiber und Systemcall zum Tragen, mit den jeweiligen Abhängigkeiten. Paket Scheduling Algorithmen werden hier nicht verwendet. 3.3.4 Jitter Der Jitter wird durch die variablen Aspekte im Delay bestimmt. Er entsteht bereits durch den sendenden Prozess, der niemals hundertprozentig isochron senden kann. Dies kann durch den Einsatz eines entsprechenden Prozess-Schedulers und fein-granularer Timer im Betriebssystem minimiert werden . Einen großen Einfluss auf den Jitter hat der Protokoll-Stack. Daher wird er, wie schon erwähnt, auf Layer 1, 2 und 7 reduziert. Die in Linux implementierten Paket Scheduling Algorithmen können nur zum Eingrenzen von maximalem Delay eingesetzt werden. Pakete werden trotzdem weiterhin zu den frühest möglichen Zeitpunkt gesendet. Daher bewegt sich der Jitter zwischen dem Best- und dem Worst-Case-Delay. Wird dennoch ein geringer Jitter benötigt, um z.B. für die Visualisierung eine konstante Bildwiederholrate zu erzielen, können die Daten am Empfänger gepuffert werden, um dann mit geringem Jitter ausgelesen zu werden. Dies führt jedoch zu einem Anstieg im Delay, was in dem Fall der Visualisierung durchaus akzeptabel wäre. 3.3.5 Throughput Der Durchsatz eines Datenstroms ist abhängig davon, wieviel Bandbreite von anderen Datenströmen verbraucht wird, und ist nach oben begrenzt durch die Linkkapazität des Netzwerk-Device. Um den Durchsatz eines Datenstroms zu sicheren müssen die der restlichen Datenströme begrenzt werden. 43 3.4. API der Linux-Queueing-Disziplinen 3.4 Kapitel 3. Analyse Application Programming Interface der LinuxQueueing-Disziplinen In diesem Abschnitt soll beschrieben werden, wie aus Sicht einer Applikation, die unter Linux zur Verfügung stehenden Queueing Disziplinen konfiguriert werden können. Es werden die für diese Arbeit benötigten QDiscs vorgestellt und deren Parameter erläutert. Die Informationen über diese Schnittstellen sind zum größten Teil aus der Analyse der Linux-Kernel Quellen und der Quellen des Programms TC aus dem Paket iproute2 gewonnen worden. 3.4.1 Kommunikation über Netlink-Sockets Zur Konfiguration aus einer Applikation heraus stellt der Kernel Netlink-Sockets bereit [WPR+ 02]. Diese sind das Kommunikationsmittel, mit dem man vom UserSpace aus mit dem Kernel in einen Dialog treten kann. Die dafür erforderlichen Header-Dateien sind in Listing 3.1 dargestellt. Quelltext 3.1: Erforderliche Header Dateien für Netlink-Kommunikation # include # include # include # include # include < sys / types .h > < sys / socket .h > < asm / types > < linux / netlink .h > < linux / rtnetlink .h > Geöffnet werden Netlink-Sockets mit dem Systemcall socket(), dessen genaue Beschreibung der entsprechenden Manpage zu entnehmen ist. Als domain-Parameter ist PF_NETLINK zu wählen, sowie für den type-Parameter SOCK_DGRAM oder SOCK_RAW . In netlink.h sind verschiedene Protokolle definiert, die jeweils eine Schnittstelle zu einem anderen Subsystem im Kernel repräsentieren. So bildet NETLINK_ROUTE die Schnittstelle zu Routing und Netzwerk, NETLINK_FIREWALL zur Firewall und NETLINK_ARPD zur ARP-Tabelle. Zum Manipulieren der Queueing-Disziplinen muss als protocol-Parameter NETLINK_ROUTE eingetragen werden, dessen Definition in rtnetlink.h enthalten ist. Um mit dem Kernel zu kommunizieren, werden über die Systemcalls sendmsg() und recvmsg() Netlink-Messages ausgetauscht. Zum Transport der Netlink-Messages werden diese in die Struktur struct msghdr eingebettet, die im Folgenden erklärt wird (Listing 3.2). 44 Kapitel 3. Analyse 3.4. API der Linux-Queueing-Disziplinen Quelltext 3.2: Die 1 2 3 4 5 6 7 8 9 msghdr Struktur struct msghdr { void * msg_name ; socklen_t msg_namelen ; struct iovec * msg_iov ; size_t msg_iovlen ; void * msg_control ; socklen_t msg_controlle n ; int msg_flags ; }; Im Zeiger msg_name wird die Speicheradresse eines Adressierungsobjekts gespeichert, dessen Größe in msg_namelen übergeben wird. Auf den Aufbau dieses Adressierungsobjekts wird in Abschnitt 3.4.2 eingegangen. Der Daten-Member msg_iov ist ein Zeiger auf einen Vektor von iovec-Strukturen. Eine Struktur nimmt die Basisadresse und Größe des Adressbereichs auf, in dem sich die zu versendende Message befindet. In msg_iovlen wird die Anzahl der zu versendenden Messages angegeben und nicht die Bytes, die der Zeiger einnimmt. Listing 3.3 zeigt den Aufbau von struct iovec. Quelltext 3.3: Die 1 2 3 4 iovec Struktur struct iovec { void * iov_base ; size_t iov_len ; }; msg_control, msg_controllen und msg_flags können genutzt werden, um optionale, Protokoll-spezifische Informationen zu übergeben. Für die Nutzung von NetlinkMessages werden sie nicht benötigt. 3.4.2 Addressierung der Netlink-Messages Da die Kommunikation über Netlink-Sockets zwischen User-Prozessen und dem Kernel erfolgt, werden zur Addressierung die Prozess IDs genutzt. Zu diesem Zweck ist in netlink.h die Struktur struct sockaddr_nl definiert, die in Listing 3.4 gezeigt wird. Quelltext 3.4: Die 1 2 3 4 5 6 sockaddr_nl struct sockaddr_nl { sa_family_t nl_family ; unsigned short nl_pad ; __u32 nl_pid ; __u32 nl_group ; }; 45 Struktur 3.4. API der Linux-Queueing-Disziplinen Kapitel 3. Analyse Als nl_family wird immer AF_NETLINK übergeben. nl_pad füllt die Lücke zwischen nl_family und nl_pid bis zur nächsten 4-Byte- Grenze auf. In nl_pid wird die Prozess ID des Empfängerprozesses übergeben. Da von User-Seite aus der Kernel der Empfänger ist, wird das Feld auf Null gesetzt 2 . Es ist prinzipiell möglich in nl_group eine Multicast-Gruppenmaske zu übergeben und so mehrere Prozesse zu adressieren. Wie dies genau funktioniert, war im Laufe dieser Arbeit nicht in Erfahrung zu bringen. 3.4.3 Aufbau der Netlink-Messages Der grundsätzliche Aufbau einer Netlink-Message wird in Abbildung 3.3 gezeigt und anhand des Listings 3.5 erläutert. Abbildung 3.3: Netlink-Message In netlink.h ist die Struktur struct nlmsghdr (Listing 3.5) definiert, die die Daten des Headers aufnimmt. Quelltext 3.5: Die 1 2 3 4 5 6 7 nlmsghdr Struktur struct nlmsghdr { __u32 nlmsg_len ; __u16 nlmsg_type ; __u16 nlmsg_flags ; __u32 nlmsg_seq ; __u32 nlmsg_pid ; }; Das nlmsg_len Feld hält die Länge der Message inklusive Header und Attribute in Bytes. In nlmsg_type (Zeile 2) wird das Kommando eingetragen, das von dem Kernelsubsystem ausgeführt werden soll. Die zur Verfügung stehenden Kommandos sind vom genutzten Netlink-Protokoll abhängig. Das NETLINK_ROUTE Protokoll enthält einen 2 Die PID des Kernel ist 0 46 Kapitel 3. Analyse 3.4. API der Linux-Queueing-Disziplinen Satz von 27 Befehlen, von denen jedoch nur neun für die Konfiguration von QDiscs relevant sind. Diese werden in Tabelle 3.1 zusammengefasst. RTNetlink-Messagetyp Bedeutung RTM_NEWQDISC Neue QDisc anlegen RTM_DELQDISC QDisc löschen RTM_GETQDISC QDisc-Parameter anfordern RTM_NEWTCLASS Neue Klasse anlegen RTM_DELTCLASS Klasse löschen RTM_GETTCLASS Klassenparameter anfordern RTM_NEWTFILTER Neuen Filter anlegen RTM_DELTFILTER Filter löschen RTM_GETTFILTER Filterparameter anfordern Tabelle 3.1: RTNetlink-Kommandos Die in nlmsg_flags (Zeile 3) einzutragenden Flags verfeinern die Kommandos in Tabelle 3.1. Die zur Verfügung stehenden Flags sind in netlink.h definiert und werden in Tabelle 3.3 zusammengefasst. Message-Typ Flag Bedeutung NEW-Messages NLM_F_CREATE Element wird erstellt, falls es noch nicht existiert Element wird nicht erstellt, wenn es schon existiert Überschreibe existierendes Element Hänge Element an das Ende der Liste (Keine Angabe des Handles) NLM_F_EXCL NLM_F_REPLACE NLM_F_APPEND DEL-Messages - Tabelle 3.2: Flags für Netlink-Messages In nlmsg_seq wird die Sequenznummer der Message in der Kommunikation zwischen Kernel und User-Space-Applikation festgehalten. Im Feld nlmsg_pid wird die Prozess ID des sendenden Prozesses eingetragen. Diese 47 3.4. API der Linux-Queueing-Disziplinen Kapitel 3. Analyse dient als Quelladresse, da im Addressierungsobjekt selbst keine solche vorgesehen ist. Message-Typ Flag Bedeutung GET-Messages NLM_F_ROOT NLM_F_ATOMIC Spezifiziere die Wurzel des Elementbaums Liefere alle passenden Elemente zurück Befehl ist nicht unterbrechbar NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) NLM_F_REQUEST Es handelt sich um eine Request-Message Multipart-Message Wird durch NLMSG_DONE terminiert Message-Quittierung Kann Error-Codes enthalten Echo-Message zu Überprüfung der Kommunikation NLM_F_MATCH Alle Messages NLM_F_MULTI NLM_F_ACK NLM_F_ECHO Tabelle 3.3: Flags für Netlink-Messages (forts.) 3.4.3.1 Traffic-Control-Messages Eine Traffic-Control-Message (Abbildung 3.4) mit angehängten Attributen wird als Payload der Netlink-Message verschickt. Zur Bildung der Traffic-Control-Message ist in rtnetlink.h die Struktur struct tcmsg (Listing 3.6) definiert. Abbildung 3.4: Traffic-Control-Message 48 Kapitel 3. Analyse 3.4. API der Linux-Queueing-Disziplinen Quelltext 3.6: Die 1 2 3 4 5 6 7 8 9 struct tcmsg { unsigned char unsigned char unsigned short int __u32 __u32 __u32 }; tcmsg Struktur tcm_family ; tcm_pad1 ; tcm_pad2 ; tcm_ifindex ; tcm_handle ; tcm_parent ; tcm_info ; Das Feld wird tcm_family (Zeile 2) laut der Definition in rtnetlink.h stets mit dem Wert AF_UNSPEC belegt. Die beiden Padding-Felder tcm_pad1 (Zeile 3) und tcm_pad2 (Zeile 4) werden auf den Wert Null gesetzt. Sie dienen dazu, das tcm_ifindex-Feld auf die nächste 4-ByteGrenze zu schieben. Im Feld tcm_ifindex (Zeile 4) wird der Index-Wert des betreffenden NetzwerkDevices übergeben. Es handelt sich dabei nicht um den Namen des Devices, sondern dessen interne Identifizierungsnummer. Um den Index eines Netzwerk-Devices zu ermitteln, wird der Systemcall ioctl() mit SIOCGIFINDEX als request-Parameter genutzt. Zu näheren Informationen sei auf die Manpage netdevice(7) verwiesen. Das Handle des jeweiligen Traffic-Control-Elements wird in tcm_handle (Zeile 5) gespeichert. Wird als Handle Null angegeben, so wird dem Element ein Handle vom Kernel zugewiesen. In tcm_parent wird das Handle des Elternelements angegeben. Handelt es sich bei dem zu konfigurierenden Element um die Root-QDisc, so ist als Elternelement entweder TC_H_ROOT oder TC_H_INGRESS anzugeben. Die Verwendung des tcm_info-Feldes (Zeile 6) ist abhängig vom verwendeten MessageTyp, und ob es sich bei dem Ziel um eine QDisc, Klasse oder einen Filter handelt. Wird mit RTM_GETQDISC oder RTM_GETTCLASS der Status einer Queueing Disziplin oder Klasse abgefragt, nutzt der Kernel das Feld zur Übermittlung eines Teils der Statusinformationen. Welche das im einzelnen sind wurde in diese Arbeit nicht ermittelt. Bei der Konfiguration von Filtern wird in den oberen 16 Bit des tcm_info-Feldes die Priorität des Filters und in den unteren 16-Bit das Protokoll, das gefiltert werden soll, an den Kernel übergeben. Darauf wird im Abschnitt 3.4.5 eingegangen. Die Attribute die der jeweiligen Message angehängt werden, sind vom verwendeten Element abhängig. Grundsätzlich ist in einem TCA_KIND-Attribut die Bezeichnung des Elements als String zu übergeben. In linux/pkt_sched.h sind für alle QDiscs 49 3.4. API der Linux-Queueing-Disziplinen Kapitel 3. Analyse Strukturen definiert, die in den meisten Fällen im Attribut TCA_OPTIONS an den Kernel übergeben werden. Alle Attribute, die zur Konfiguration eines Elements notwendig sind, werden in Abschnitt 3.4.4 bzw. 3.4.5 im Einzelnen erklärt. Der Aufbau eines Attributes ist in Abbildung 3.5 dargestellt. In rtnetlink.h sind Makros zur Erstellung und Bearbeitung von Attributen definiert. Abbildung 3.5: Attribute einer Traffic-Control-Message 3.4.4 Verfügbare Queueing Disziplinen Unter Linux-Kernel 2.6 existieren 13 verschiedene Queueing Disziplinen, von denen acht klassenlos und fünf klassenbehaftet sind. Die meisten lassen sich für die Zwecke dieser Arbeit nicht verwenden, da sie entweder den Anforderungen von Automatisierungskommunikation zuwiderlaufen oder für die Arbeit auf höheren Protokollschichten ausgelegt sind. So verwirft Random Early Detection zufällig gewählte Pakete, um den Durchsatz fair über alle Verbindungen hinweg zu drosseln, und DSMark, der vollwertigen Implementierung von DiffServ, ist auf das DS-Feld im IP-Header angewiesen. Im Folgenden sollen die für diese Arbeit relevanten Queueing Disziplinen erläutert werden. Die in den Überschriften, in Klammern, angegebenen Namen entsprechen den Namen der jeweiligen QDisc, mit dem sie in dem TCA_KIND-Attribut an den Kernel übergeben werden müssen. 3.4.4.1 First-In-First-Out (bfifo, pfifo, pfifo fast) Bei diesen Queueing Disziplinen handelt es sich um verschiedene Implementierungen des FIFO-Algorithmus. Der einzige Unterschied zwischen bfifo und pfifo besteht darin, dass die Größe der Queue bei bfifo Byte und bei pfifo in Paketen berechnet wird. Die pfifo fast-QDisc stellt einen Spezialfall dar. Sie besteht eigentlich aus drei FIFO-Queues, die nach den Strict-Priority-Algorithmus abgearbeitet werden. Die Zuordnung der Pakete, zu den Queues richtet sich nach dem DS-Feld im IP-Header. 50 Kapitel 3. Analyse 3.4. API der Linux-Queueing-Disziplinen pfifo fast ist die Default-QDisc für alle Netzwerk-Devices und Blattklassen einer QDisc, wenn für diese keine QDisc explizit konfiguriert wurde. Der einzige Parameter dieser QDiscs ist die Größe der Queue in Byte für bfifo und in Paketen für pfifo und pfifo fast. Er kann nur indirekt mit dem Befehl ifconfig <device> txqueuelen <len> konfiguriert werden. Bei einem Überfüllen der Queue, werden alle nachkommenden Pakete verworfen, bis wieder ein Platz für ein weiteres Paket frei ist. Die anderen beiden Varianten lassen sich hingegen über die Netlink-Messages konfigurieren. Die Struktur struct tc_fifo_qopt enthält den Parameter limit mit dem die Größe der Queue, je nach Variante, in Bytes oder in Paketen übergeben wird. Die Struktur wird im Attribut TCA_OPTIONS an die Netlink-Message angefügt. 3.4.4.2 Stochastical Fair Queueing (sfq) Die Stochastical Fair Queue verhält sich wie ein Round Robin Algorithmus, indem sie die zur Verfügung stehende Bandbreite unter allen Verbindungen (Flows) aufteilt und sie auf mehrere interne Queues verteilt. Eine Verbindung wird durch eine TCP/UDP-Port Nummer klassifiziert, dies macht die QDisc zwar für die Automationsspeziefischen Zwecke ungeeignet, sie ist jedoch gut einsetzbar um den Best-EffortVerkehr auf den höheren Protokoll-Ebenen fair zu halten. Die SFQ besitzt eine Reihe von Parametern, mit der sie beeinflusst werden kann. Diese sind in der Struktur tc_sfq_qopt (Listing 3.7) zusammengefasst. Quelltext 3.7: Die 1 2 3 4 5 6 7 tc_sfq_qopt Struktur struct tc_sfq_qopt { unsigned quantum ; int perturb_period ; __u32 limit ; unsigned divisor ; unsigned flows ; }; Der Parameter quantum legt die Anzahl Bytes fest, die ein Flow senden darf, bevor der nächste an der Reihe ist. Da insgesamt 65535 verschiedene Port-Nummern existieren, ist es nicht optimal für jeden Flow eine eigene Queue zu verwalten. Deshalb wird nur eine begrenzte Anzahl Queues angelegt, auf die dann alle Flows verteilt werden. Treten mehr Flows auf, als Queues vorhanden sind, werden sie doppelt belegt. Um eine statistische Fairness zu ermöglichen, werden die Flows nach einer Zeit umsortiert. In welchen 51 3.4. API der Linux-Queueing-Disziplinen Kapitel 3. Analyse Sekundenintervallen das geschehen soll, wird im Paramter perturb_period festgelegt. Wird der Parameter nicht gesetzt, findet keine Umsortierung statt. Die letzten drei Parameter sind zur Zeit dieser Arbeit fest in den Code der QDisc einkompiliert und können nicht beeinflusst werden. limit legt die Queue-Größe und flows die Anzahl der Queues fest. Bei divisor handelt sich um einen Hash-Divisor. 3.4.4.3 Prio (prio) Die Prio-QDisc ist die Implementierung des Strict Priority Algorithmus. Sie besteht aus einer Reihe von Klassen, die je eine Prioritätsstufe darstellen. Dabei stellt, im Gegensatz zur Wertigkeit des User-Priority-Feldes im VLAN-Tag, der Wert 0 die höchste Priorität dar. Um eine Prio-QDisc zu konfigurieren, wird die tc_prio_qopt-Struktur im TCA_OPTIONS -Attribut an die Netlink-Message angehängt. Quelltext 3.8: Die 1 2 3 4 tc_prio_qopt Struktur struct tc_prio_qopt { int bands ; __u8 priomap [ TC_PRIO_MAX +1]; }; Im Parameter bands wird die Anzahl der Klassen angegeben, die die QDisc enthalten soll. Diese werden beim Erzeugen der QDisc automatisch mit erschaffen, und erhalten ein Handle mit einer Minor-Nummer entsprechend der Priorität, inkrementiert um eins. Bis zu 16 Klassen können auf diese Weise erzeugt werden, jedoch können keine Klassen nachträglich erzeugt werden. Sind keine Filter für die QDisc konfiguriert, so werden Pakete über ein PrioritätsMapping klassifiziert, bei dem die Socket-Priorität auf die entsprechende Klasse abgebildet wird. Diese Map wird über den Parameter priomap übergeben. Dieser besteht aus einem Array von 16 Werten, bei der die Einzelnen Positionen der SocketPrioritäten entspricht. Als Werte werden an den Positionen die Prioritäten der Klassen eingetragen. 3.4.4.4 Hierarchical Token Bucket (htb) Der Hierarchical Token Bucket(HTB) stellt eine klassenbehaftete Implementierung des Token Bucket Flow Algorithmus dar. Nicht der QDisc selbst, sondern ihren 52 Kapitel 3. Analyse 3.4. API der Linux-Queueing-Disziplinen Klassen werden Burst- und Rate-Parameter zugewiesen. Die Klassen können in beliebigen Baumstrukturen angeordnet werden, wobei die Parameter der Kindklassen nicht die der Elternklassen übersteigen dürfen. Hat eine Elternklasse mehr Bandbreite zur Verfügung, als die Kindklassen verbrauchen, so kann die Bandbreite auf die Kindklassen verteilt werden, solange diese unter einem maximalen Grenzwert bleiben. Die Verteilung der Bandbreite an die Kindklassen erfolgt als Sendezeit, gemessen in der internen Einheit ”Ticks”. Die folgenden Informationen zur Umrechnung von Microsekunden nach Ticks und zurück, stammt aus einer E-Mail-Korrespondenz mit Stephen Hemminger, einem Mit-Autor der iproute2-Utilities. In der Datei /proc/net/psched sind zwei Variablen eingetragen, t2us und us2t, die als hexadezimale Werte ausgelesen werden können. Der erste Wert in der Datei entspricht der Variablen t2us und der zweite der Variablen us2t. Welche Bedeutung die beiden anderen Werte haben, konnte nicht ermittelt werden. Möchte man die Ticks pro Microsekunde ausrechnen, dividiert man t2us durch us2t. Um die benötigten Ticks für eine bestimmte Paketgröße zu ermitteln, verwaltet jede Klasse intern zwei Rate-Tabellen mit je 256 Einträgen, die die Anzahl der von einem Paket verbrauchten Token auf die benötigten Ticks abbilden. Die Tabelle rtab gilt dabei für die konfigurierte Mindestrate und die Tabelle ctab für die Maximalrate, die nicht überschritten werden darf, unabhängig davon, wieviel Bandbreite der Elternklasse noch zur Verfügung steht. Da die Tabellen nur 256 Einträge enthalten, nimmt ein maximal großes Paket 256 Token ein. Um die Größe eines Tokens zu ermitteln teilt man die maximale Paketgröße [Byte] durch 256 und rundet zu nächsten 2er Potenz auf. Ein Eintrag Ei in eine Rate-Tabelle mit der Tokengröße T und der Rate R wird wie folgt berechnet: Ei = ( i · T [Byte] ) · T icks[ticks/µsec] R[Byte/µs] (3.13) Um die QDisc zu konfigurieren wird die nachstehend beschriebene Struktur tc_htb_glob genutzt. Jedoch wird sie nicht, wie bei anderen QDiscs im TCA_OPTIONS-Attribut versendet, sondern in einem eigenen TCA_HTB_INIT-Attribut. Das TCA_OPTIONS-Attribut wird jedoch als leeres Attribut davor eingefügt. 53 3.4. API der Linux-Queueing-Disziplinen Quelltext 3.9: Die 1 2 3 4 5 6 7 8 struct tc_htb_glob { __u32 __u32 __u32 __u32 __u32 Kapitel 3. Analyse tc_htb_glob Struktur version ; rate2quantum ; defcls ; debug ; direkt_pkts ; }; Der Parameter version dient zum Versionsabgleich zwischen Software und verwendeter HTB-Version, da neue Versionen des TC-Programms nicht mit alten HTBVersionen kompatibel sind. Für die im Rahmen dieser Arbeit genutzten QDisc muss Versionsnummer 3 angegeben werden. rate2quantum ist eine Berechnungsgröße für die Zuteilung von überschüssiger Band- breite. Diese wird als Quantum nach einem Weighted Round Robin Algorithmus von Eltern an die Kindklassen weitergegeben. Wird beim Erzeugen einer Klasse ein Quantum nicht explizit angegeben, so wird es durch Division ihrer Rate durch den rate2quantum-Wert ermittelt. Der Default-Wert für rate2quantum beträgt 10. In defcls wird die Minor-Nummer der Klasse angegeben, die Pakete aufnehmen soll, die nicht durch einen Filter klassifiziert werden konnten. Wird kein Wert angegeben, so entspricht er null, was bedeutet, dass unklassifizierte Pakete aus der QDisc herausfallen und mit der vollen Linkkapazität bedient werden. Der debug-Parameter nimmt Debug-Level Informationen auf. Leider konnte im Rahmen dieser Arbeit nicht ermittelt werden, wie diese anzugeben sind. Der Parameter direkt_pkts gibt die Anzahl der Pakete an, die an der HTB-QDisc direkt bedient wurden, wenn bei der Konfiguration keine Default-Klasse angegeben wurde. Dieser Parameter wird bei Statusabfragen vom Kernel befüllt. Um eine Klasse eines HTB zu konfigurieren, wird eine die tc_htb_opt benutzt. Um einige der Parameter erklären zu können, muss jedoch zuerst näher auf eine andere Struktur eingegangen werden. Mit der tc_ratespec-Struktur werden Datenraten spezifiziert, die zur Konfiguration der Klassen nötig sind. Es werden jedoch nicht alle Parameter der Struktur benötigt. 54 Kapitel 3. Analyse 3.4. API der Linux-Queueing-Disziplinen Quelltext 3.10: Die 1 2 3 4 5 6 7 8 tc_ratespec Struktur struct tc_ratespec { unsigned char cell_log ; unsigned char __reserved ; unsigned short feature ; short addend ; unsigned short mpu ; __u32 rate ; }; Der Parameter cell_log ist der binäre Logarithmus der Tokengröße. Mit ihm kann man durch eine einfache Bitshift-Operation die Anzahl Token für ein Paket, und somit dessen Sendezeit in den rtab- und ctab-Tabellen ermitteln. rtab [ pkt_size > > cell_log ] = pkt_xmit_time ; Die Paramter __reserved, feature und addend werden zur Konfiguration der HTBKlasse nicht genutzt. mpu ist die Minimum Packet Unit, also die Mindestgröße eines Pakets. Für alle Einträge in einer der Rate-Tabellen, deren Paketgröße kleiner wäre als mpu, wird zur Berechnung der Paketsendezeit der Wert von mpu verwendet. Der rate-Parameter ist die Datentransferrate, angegeben in Bytes pro Sekunde. Die tc_htb_opt-Struktur wird wie bei der QDisc nicht im TCA_OPTIONS-Attribut, sondern in dessen Anschluss in einem eigenen Attribut namens TCA_HTB_PARMS gesendet. Im Folgenden werden die einzelnen Parameter erklärt. Quelltext 3.11: Die 1 2 3 4 5 6 7 8 9 struct tc_htb_opt { struct tc_ratespec struct tc_ratespec __u32 __u32 __u32 __u32 __u32 }; tc_htb_opt Struktur rate ; ceil ; buffer ; cbuffer ; quantum ; level ; prio ; Die Parameter rate und ceil geben die Mindest- bzw. Maximalrate an, die der Klasse zur Verfügung stehen sollen. Dabei ist zu beachten, dass die beiden Parameter für eine Klasse, deren Elternknoten die QDisc ist, gleich sind. Über die beiden Parameter buffer und cbuffer wird die Menge an Sendezeit eingestellt, die eine Klasse ansammeln und als Burst versenden kann. buffer ist also der 55 3.4. API der Linux-Queueing-Disziplinen Kapitel 3. Analyse Burst-Parameter für rate und cbuffer für ceil. Die Werte für die Parameter werden als benötigte Sendezeit für die Größe bei der entsprechenden Rate übergeben. Durch den Timer-Takt von 1ms ergibt sich Mindestwert für buffer. In Byte wird dieser wie folgt berechnet: b = rate/1000Hz (3.14) Das würde für eine Klasse deren Rate 1200000Byte/s beträgt einen Burst-Wert von mindestens 1200Bytes haben muss um seine Rate erreichen zu können. Das ist jedoch bei kleinen Paketen nicht unbedingt erwünscht. Daher kann laut [Hub03] der Paramter cbuffer genutzt werden, um den Burst weiter nach unten zu beschränken. Der quantum-Parameter bestimmt wie groß der Anteil an überschüssiger Bandbreite ist, den die Klasse im Verhältnis zu den Geschwisterklassen abbekommt. Wird der Parameter nicht angegeben, wird er durch den rate2quantum Divisor der QDisc bestimmt. Die Verwendung des Parameters level ist nirgendwo dokumentiert, und auch das Programm TC stellt diese Option nicht bereit. Die überschüssige Bandbreite der Elternklassen wird nach dem Weighted Round Robin Algorithmus auf die Kindklassen verteilt. Die Reihenfolge in der sie verteilt werden, wird durch den prio-Parameter bestimmt. Der Wert liegt im Intervall [0; 7], wobei 0 die höchste Priorität darstellt. Nach dem TCA_HTB_PARMS-Attribut müssen noch die Attribute TCA_HTB_RTAB und TCA_HTB_CTAB angefügt werden. Diese enthalten die beiden Tabellen rtab und ctab, deren Inhalt mit der Formel 3.13 berechnet wird. 3.4.5 Filter Die Konfiguration von Filtern funktioniert genauso, wie die Konfiguration von Klassen und QDisc, jedoch mit ein paar Besonderheiten. Zunächst ist da der Aufbau der Handles zu erwähnen. Dieser ist nicht, wie bei Klassen und QDiscs, einheitlich, sondern von dem jeweils verwendeten Filtertyp abhängig. Daher soll weiter unten darauf eingegangen werden, wo der verwendete Filter näher erklärt wird. Elternknoten eines Filters kann jede klassenbehaftete Queueing Disziplin sein, oder eine ihrer Klassen. Passiert ein Paket diesen Knoten, wird eine Klassifizierung durchgeführt. 56 Kapitel 3. Analyse 3.4. API der Linux-Queueing-Disziplinen Eine weitere Besonderheit ist die Nutzung des tcm_info-Feldes in der tcmsg-Struktur (Listing 3.6). Ihm wird die Information über Priorität des Filters und des zu filternden Protokolls zugewiesen. Der Prioritätswert liegt im Intervall [0; 7], mit 0 als höchster Priorität, und bestimmt die Reihenfolge die Filter mit dem gleichen Elternknoten aufgerufen werden. Filter mit gleicher Priorität werden in der Reihenfolge aufgerufen, in der sie erzeugt wurden. Das zu filternde Protokoll entspricht einem gültigen Protokolltyp, wie er im TypeFeld des Ethernet-Headers vorkommt. Definierte Werte sind in der Header-Datei linux/if ether.h enthalten. Durch den Protokoll-Wert wird eine Art Vorfilterung vorgenommen. Sollen alle Protokolle berücksichtigen werden, muss der Wert ETH_P_ALL (0x0003) verwendet werden. Der Prioritätswert nimmt die oberen 16-Bit des tcm_info-Feldes ein und das Protokoll die unteren. 3.4.5.1 Der U32-Filter Die meisten zur Verfügung stehenden Filter filtern nach speziellen Merkmalen, die entweder eine höhere Protokollschicht voraussetzen (DSMark) oder vom RoutingModul am Socket-Buffer vorgenommene Markierungen (fw, route) benötigen. U32 filtert zwar prinzipiell nach Mustern in höheren Protokollschichten, ist jedoch so flexibel, dass er mit einem Trick 3 so konfiguriert werden kann, dass er nach Feldern im Ethernet-Header filtert. Grundsätzlich besteht der Filter aus einer Reihe von Schlüssel-, Maske-, OffsetDatensätzen, mit denen Muster in einem Paket erkannt werden können. Schlüssel und Maske decken je 32 Bit ab und können durch den Offset in 4Byte-Schritten jedes Muster an jeder Stelle, innerhalb der Payload des Ethernet-Headers, erkennen. Der Offset beginnt ab dem Ende des Ethernet-Headers. Handelt es sich dabei um einen VLAN-Tagged-Frame, beginnt der Offset zwischen dem Tag Protocol Identifier und der Tag Control Information. Soll jedoch ein Feld im Ethernet-Header selbst erkannt werden, so kann ein negativer Offset genutzt werden. Um zum Beispiel die Zieladresse eines Ethernet-Frames zu filtern, müssen zwei Datensätze angelegt werden, da die vollständige Adresse nicht von 32-Bit abgedeckt werden kann. Der erste Datensatz hätte als Schlüssel die obersten zwei Byte der Adresse, eine Maske mit dem Wert 0x00FF und ein Offset von -16. Der Schlüssel 3 Dieser Trick wurde von den Entwicklern vorgeschlagen 57 3.4. API der Linux-Queueing-Disziplinen Kapitel 3. Analyse des zweiten Datensatzes würde die restlichen vier Bytes der Adresse enthalten, die Maske hätte den Wert 0xFFFF und der Offset wäre -12. Das Handle des U32-Filters ist in drei Segmente unterteilt. Die oberen 12 Bit nimmt die ID einer Hash-Tabelle ein, die erzeugt wird, sobald an einem Elternknoten ein neuer Filter mit einer Priorität angelegt wird, die bisher kein Filter an diesem Knoten besitzt. Die Hash-Table-IDs werden nach diesem Prinzip automatisch vergeben, beginnend bei dem Wert 0x800. Diese ID ist eindeutig für den Elternknoten, an dem der Filter konfiguriert wurde. Die unteren 12 Bit nehmen das eigentliche Handle auf, das für die Hash-Tabelle eindeutig sein muss. Die mittleren acht Bit stehen einem Hash-Wert zur Verfügung. Zur Zeit dieser Arbeit ist jedoch kein Anwendungsfall aufgetreten, wo dieser benötigt wurde, weshalb dessen Bedeutung nicht weiter nachgegangen wurde. Um den U32-Filter anzulegen, müssen wie bei Klassen und QDiscs Attribute an die Netlink-Message angefügt werden. Das erste Attribut ist TCA_KIND mit der Filterkennung ”u32” als String, gefolgt von einem leeren TCA_OPTIONS-Attribut. Danach wird im Attribut TCA_U32_CLASSID das vollständige Handle der Zielklasse untergebracht, welche nicht zwingend zu der gleichen Elternklasse oder -qdisc angehören muss, wie der Elternknoten des Filters. Als letztes folgt das Attribut TCA_U32_SEL, in dem der Selektor des Filters untergebracht wird. Der Selektor wird aus zwei Strukturen gebildet: der tc_u32_selStruktur (Listing 3.12) und einem Array von tc_u32_key-Struktur (Listing 3.13). Beide Strukturen sollen im Folgenden erläutert werden. Quelltext 3.12: Die 1 2 3 4 5 6 7 8 9 10 11 12 13 14 tc_u32_sel Struktur struct tc_u32_sel { unsigned char flags ; unsigned char offshift ; unsigned char nkeys ; __u16 __u16 short offmask ; off ; offoff ; short __u32 hoff ; hmask ; struct tc_u32_key keys [0]; }; Das einzige relevante Flag das in flags gespeichert wird, ist das TC_U32_TERMINAL, 58 Kapitel 3. Analyse 3.4. API der Linux-Queueing-Disziplinen das gesetzt werden muss, wenn das TCA_U32_CLASSID-Attribut in der Message enthalten ist. Den Verwendungszweck der Parameter offshift, offmask, off, offoff, hoff und hmask wurde im Laufe dieser Arbeit nicht ermittelt, da sie zum Einsatz in dessen Rahmen nicht benötigt werden. Der Parameter nkeys gibt die Anzahl der folgenden tc_u32_key-Strukturen an. Der keys Parameter ist ein Zeiger auf den nachfolgenden Speicherbereich, in dem das Array der tc_u32_key-Strukturen angelegt wird. Dieses Array bildet die Datensätze, die zum erkennen der Muster benötigt werden. Quelltext 3.13: Die 1 2 3 4 5 6 tc_u32_key Struktur struct tc_u32_key { __u32 mask ; __u32 val ; int off ; int offmask ; }; Im mask-Parameter wird die 32-Bit Maske gespeichert, mit der durch eine ANDOperation die nicht zu betrachtenden Bits ausgeblendet werden. Der Parameter val nimmt den Schlüssel auf. off stellt den Offset für den Schlüssel dar. Dieser kann nur in 4-Byte-Schritten angegeben werden. Mit dem Parameter offmask wird bestimmt, ab welcher Position der Offset des Musters gemessen wird. Ist der Parameter 0, wird der Offset ab dem Ende des Ethernet-Headers gemessen, ist er -1, so wird er ab dem Beginn des nächsthöheren Headers gemessen. Wird eine Queueing Disziplin oder Klasse gelöscht, so werden automatisch alle Filter, deren Elternknoten sie ist, ebenfalls gelöscht. Ist das so erwünscht müssen für die Erzeugung des Filter nicht notwendigerweise Priorität und Handle angegeben werden. Soll jedoch ein Filter gezielt gelöscht werden muss dieser eindeutig identifizierbar sein. Dazu ist die Angabe des Handles des Elternkotens, des Handles des Filters inklusive Hash-Table-ID, dessen Priorität und die Art des Filters in einem TCA_KIND-Attribut nötig. 59 3.5. Designentscheidungen 3.5 3.5.1 Kapitel 3. Analyse Designentscheidungen Statische Verkehrscharakteristik Es wird, aufgrund der heute in der Regel statischen Verkehrscharakteristik einer Automationsumgebung, auf eine dynamische Allokation der QoS-Parameter verzichtet. Jedoch soll die entwickelte Software in Hinblick auf eine Erweiterung auf dynamische Umgebungen offen sein. 3.5.2 Linux ohne Echtzeiterweiterung Echtzeiterweiterungen für das Linux-Betriebssystem nutzen die Kern-im-Kern-Architektur. Dabei setzt sich der Echtzeit-Kern der Erweiterung zwischen das InterruptSystem des Rechners und den Linux-Kern. Der Linux-Kern greift auf ein simuliertes Interrupt-System des Echtzeit-Kerns zurück, hat aber noch vollen Zugriff auf alle seine Treiber. Echtzeitanwendungen laufen als Module im Kern-Adressraum und haben dadurch keinen Zugriff auf die Systemcall-Schnittstellen des Betriebssystems. Zugriff auf die Hardware, muss durch eigene Treiber realisiert werden. Eine solche Echtzeitanwendung könnte das vom Linux-Kern bereitgestellte QoSFramework nicht nutzen. Daher wird auf den Einsatz einer Echtzeiterweiterung verzichtet. Zur Zeit ist für die Echtzeiterweiterung RTAI eine experimentelle Entwicklung nahmens RTAI/fusion in Arbeit. Diese soll einen kurzzeitigen Wechsel von der RTAIin die Linux-Domäne ermöglichen, um deren Systemschnittstellen zu nutzen. Dies bringt aber auch einen Verlust des Determinismus für diesen Zeitraum mit sich. 3.5.3 Einsatz von Switches Um den Determinismus für Ethernet zu erreichen, soll eine Microsegmentierung des Netzwerks durch Switches durchgeführt werden. Die eingesetzten Switches sollen 802.1p-fähig sein und über Ports mit mindestens vier Ausgangsqueues verfügen, die nach dem Strict-Priority-Algorithmus bedient werden. 60 Kapitel 4 Konzept 4.1 Grobentwurf Um verschiedene Kommunikationsformen mit verschiedenen QoS-Eigenschaften, wie sie in Kapitel 3.2 besprochen wurden, unterscheiden zu können, bedarf es einer logischen Einteilung der Netzwerkressourcen. Der Kommunikationsschicht liegt ein Modell zugrunde, welches das Medium logisch in bidirektionale Kanäle mit eigenen QoS-Eigenschaften einteilt. Jeder Kanal entspricht einer Multicastgruppe mit zwei oder mehr Teilnehmern. Damit lässt sich jede Kommunikationsform aus Kapitel 3.2 unterstützen. Isochrone Propagation von Messdaten erfordert ohnehin MulticastBetrieb. Gezielte Adressierung einer Station erfordert einen eigenen Kanal mit zwei Teilnehmern. Dies ist unproblematisch, da davon ausgegangen wird, dass alle Kommunikationspfade vor der Inbetriebnahme der Anlage bekannt sind. Auch die Anmeldung und Abmeldung von Publish-Subscriber-Listen, wie sie in Abschnitt 3.1.5 erwähnt wurden, ist durch das Modell unterstützt, auch wenn deren Implementierung höheren Protokollschichten überlassen wird. Abbildung 4.1 zeigt ein konzeptionelles Klassenmodell, an dem das Grobkonzept weiter beschrieben wird. Um über einen Kanal kommunizieren zu können, muss von einem Prozess ein Kanalendpunkt (Channel ) geöffnet werden. Dem Kanalendpunkt sind lokale QoS-Eigenschaften zugewiesen, die so gewählt sind, dass die QoS-Eigenschaften des Kanals gewahrt werden. Es wird angenommen, dass lokalen Parameter vor der Inbetriebnahme der Anlage durch eine Scheduability-Analyse ermittelt und über eine Konfigurationsdatei eingelesen werden. 61 4.1. Grobentwurf Kapitel 4. Konzept Abbildung 4.1: Konzeptionelles Klassenmodell Ein Kanal wird über eine ID eindeutig identifiziert; diese wird bei jeder Interaktion angegeben. Eine Kanal ID wird auf eine Layer-2-Multicast-Adresse abgebildet. Für bestimmte Kanäle, wie einen Alarm-Kanal, ist es notwendig, dass mehr als ein Prozess eines Rechners auf den Kanal zugreifen kann. Für diesen Fall wird mit einem Sychronisationsobjekt(Sync) der wechselseitige Ausschluss beim Senden durchgesetzt. Die Kommunikation erfolgt über Packet-Sockets (Kapitel 2.3.1), um den TCP/UDP/IP-Stack zu umgehen. Die Adressierung ist vom verwendeten Device (Physical Device) und dessen Layer-2Protokoll abhängig. Diese wird vorgenommen, während der Kanalendpunkt an das Device gebunden wird. Um die Klassifizierung von Paketen durchzuführen, werden bei Initialisierung des Devices zwei VLANs erzeugt: Eins für die Kanal-Konfigurationsschicht und ein anderes um die vertikale Integration durchzusetzen. In dem virtuellen Device der Kanäle werden die Socket-Prioritäten eins zu eins auf die User Priority des VLAN-Tags abgebildet. Verkehr, der über das virtuelle Device für den Best-Effort-Verkehr gesendet wird, bekommt eine Standard-Priorität von 1 zugewiesen, was wie in 2.1.1 besprochen wurde, der niedrigsten Priorität entspricht. Die Konfiguration der Queueing-Disziplinen, entsprechend der Kanal-Parameter, wird durch einen QoS-Controller vorgenommen. Dieser nimmt die Parameter eines Kanalendpunktes auf und setzt sie in die entsprechenden Queueing-Disziplinen um. Als Schnittstelle zur Kommunikationsschicht dient eine Shared-Library, die zu der aufrufenden Applikation gebunden wird. 62 Kapitel 4. Konzept 4.1. Grobentwurf Die QoS-Parameter der Kanäle werden durch Linux-Queueing-Disziplinen durchgesetzt, die, wie in Kapitel 3.4.4 besprochen, über Netlink-Sockets konfiguriert werden. Zum Einsatz kommt eine Prio-Qdisc mit acht Bändern, deren Bandbreite durch Hierarchical-Token-Buckets für jeden Kanal eingegrenzt wird. Der Best-EffortVerkehr bekommt an dem Band mit der niedrigsten Priorität einen eigenen Bucket mit der verbleibenden Bandbreite zugewiesen. Die Parameter für die Kanalendpunkte auf einem Rechner, werden in Form einer Konfigurationsdatei bei der Initialisierung eingelesen. Auf Grundlage des konzeptionellen Klassenmodells wird ein detailiertes Klassenmodell erstellt, das in Abbildung 4.2 dargestellt ist. Die Klasse chContr dient als Bibliotheksschnittstelle und verwaltet die Kanalendpunkte, die in der chDescr-Klasse abgebildet sind. Die Klasse enthält das syncObjekt und noch ein Kommunikationsobjekt (com), das als Kommunikationsschnittstelle zu den Packet-Sockets dient. Bei Initialisierung wird, wie in Kapitel 3.5.1 besprochen, eine Konfigurationsdatei eingelesen, deren Inhalt durch die Klasse chConfRead zugreifbar ist. Die Prozess-Klasse im konzeptionellen Klassendiagramm ist der chAvailContr-Klasse gewichen, welche für jedes chDescr-Objekt ein chAvail-Objekt hält, das die maximale und die aktuelle Anzahl der Prozesszugriffe auf den Kanal verwaltet. Diese chAvail-Objekte werden in einem Shared-Memory-Segment gehalten, um für alle Prozesse einsehbar zu sein. Die Klasse chDev entspricht der Klasse Physical Device aus dem konzeptionellen Klassendiagramm und wird zu Adressierung und Konfiguration der VLANs eingesetzt. Die drei Instanzen der devInfo-Klasse halten nach der Initialisierung die Informationen über das physikalische und die beiden virtuellen Devices. Die Klasse chQoSContr setzt die QoS-Parameter der Kanalendpunkte in eine entsprechende Konfiguration der Queueing Disziplinen um. Da es sich bei dem Klassenmodell um eine Design-Methode aus der Objekt Orientierung handelt, jedoch bei der Implementierung eine prozedurale Programmiersprache zum Einsatz kommt, werden weitestgehend Utitlity-Klassen verwendet. Bei diesen handelt es sich nach [Oes01] um eine Sammlung globaler Variablen und Funktionen, die semantisch zu einer Klasse zusammengefasst werden. 63 4.2. Die Konfiguration (chConfRead) Kapitel 4. Konzept Abbildung 4.2: Übersicht Klassendiagramm 4.2 Die Konfiguration (chConfRead) Bei der Konfigurationsdatei handelt es sich um eine Datei im XML-Format, deren Aufbau im Folgenden anhand ihrer Document Type Definition erklärt werden soll. 64 Kapitel 4. Konzept 4.2. Die Konfiguration (chConfRead) Abbildung 4.3: Übersicht Klassendiagramm Quelltext 4.1: Document Type Definition der Konfigurationsdatei 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <? xml version = " 1.0 " encoding = " ISO -8859 -1 " ? > < DOCTYPE channelconf [ <! ELEMENT channelconf ( host +) > <! ELEMENT host ( name , netdevice , channel +) > <! ELEMENT name (# PCDATA ) > <! ELEMENT netdevice ( devname , mtu ) > <! ELEMENT devname (# PCDATA ) > <! ELEMENT mtu (# PCDATA ) > <! ELEMENT channel ( ID , processes , prio , delay , jitter , psize , bandwidth ) > <! ELEMENT ID (# PCDATA ) > <! ELEMENT processes (# PCDATA ) > <! ELEMENT prio (# PCDATA ) > <! ELEMENT delay (# PCDATA ) > <! ELEMENT jitter (# PCDATA ) > <! ELEMENT psize (# PCDATA ) > <! ELEMENT bandwidth (# PCDATA ) > ]> Die Datei kann die Parameter für mehrere Rechner übernehmen (Zeile 3), so dass für alle Rechner einer Automationsanlage die gleiche Konfigurationsdatei verwendet werden kann. Der Host (Zeile 4) wird durch den Rechnernamen identifiziert. Beim Einlesen der Datei wird der name-Parameter mit dem Rechnernamen verglichen, um die passenden Konfigurationsparameter zu ermitteln. Die Daten für das Netzwerk-Device (Zeile 6) bestehen aus dem Namen des Devices und der zu konfigurierenden MTU für das Device des Best-Effort-Verkehrs. Danach folgt eine Liste mit den Kanalparametern (Zeile 9). Die Klasse chConfRead dient als Interface zur Konfigurationsdatei. Mit deren Methode readConfFile() wird die Datei eingelesen und im Speicher gehalten und kann mit den folgenden Methoden ausgelesen werden. Über die Methode getDevName() werden der Name und die MTU für das Netzwerk-Device abgefragt. Mit jedem Aufruf der Methode getChannelParame() werden die Parameter eines Kanals in die per Referenz übergebenen Variablen eingetragen, bis alle Kanal-Daten übergeben sind. Sind alle gewünschten Daten abgerufen, kann mit closeConfFile() die Datei 65 4.3. Die Bibliotheksschnittstelle (chContr) Kapitel 4. Konzept geschlossen und der belegte Speicher freigegeben werden. 4.3 Die Bibliotheksschnittstelle (chContr) Die Klasse chContr dient als Bibliotheksschnittstelle und als zentrale ControllerKlasse, mit deren Methoden alle Aktivitäten, der mit ihr assozierten Klassen, gesteuert werden. Zusätzlich enthält sie eine Liste mit den geöffneten Kanalendpunkten, sowie denen, die zum Öffnen bereitstehen. Um den wechselseitigen Ausschluss sowohl beim Initialisieren von globalen Betriebsmitteln, wie Queueing Disziplinen und virtuellen Devices, als auch beim Zugriff auf die chAvailContr-Klasse zu gewährleisten, hält die Klasse chContr ein Semaphor. Die Interaktionsmöglichkeiten mit der Bibliotheksschnittstelle werden im Folgenden mit Hilfe einiger Kollaborationsdiagramme gezeigt. Initialisierung der Bibliothek Vor der Nutzung der Bibliothek muss diese initialisiert werden, damit die Konfigurationsdatei ausgelesen wird und daraufhin alle benötigten Resourcen reserviert werden können. Bei Initialisierung wird das Semaphor erschaffen, das den Vorgang schützen soll. Wird durch die Existenz des Semaphors feststellt, dass ein anderer Prozess die globalen Betriebsmittel bereits initialisiert hat, werden nur noch für den Prozess globale Variablen initialisiert. Das Diagramm in Abbildung 4.4 geht von der Annhame aus, dass noch kein Prozess vorher auf die Bibliothek zugegriffen hat. 1 Die Initialisierung der Bibliothek wird durch die Funktion initChannels() angestoßen. 1.1 Über die Konfigurationsdatei werden alle verfügbaren Kanal-IDs, sowie deren Parameter und Prozesszugriffsdaten, eingelesen und in dem chConfRead-Objekt gehalten. 1.2 Der Name des Netzwerk-Devices wird durch die getDevName()-Methode angefordert. 1.3 Der Name des Devices wird mit der Methode initDev() an das device-Objekt übergeben. Diese initialisiert alle erforderlichen Daten und legt die VLANs an. 66 Kapitel 4. Konzept 4.3. Die Bibliotheksschnittstelle (chContr) Abbildung 4.4: Kollaborationsdiagramm zu initChannels() 1.4 Das chAvailContr-Objekt wird durch die Methode initChAvail() angewiesen den Shared Memory anzulegen. 1.5 In initQoS() werden die QDisc-Grundkonfigurationen angelegt. Die Kanalspezifischen QoS-Einstellungen werden hier noch nicht vorgenommen, sondern erst beim Öffnen des Kanals. Dies ist in einer statischen Umgebung zwar nicht nötig, erleichtert aber die Erweiterung des Designs für dynamische Umgebungen. 1.6.*.1 Am chConfRead-Objekt werden zyklisch alle Kanaldaten sowie deren Prozesszugriffsdaten mit der Methode fillNextChannel() abgefragt. 1.6.*.2 Mit den ausgelesenen Daten wird ein chDescr-Objekt erstellt. 1.6.*.3 Die Prozesszugriffsdaten werden im chAvailContr-Objekt mit der Methode addAvailEntry() eingetragen. 1.7 Die Konfigurationsdatei wird geschlossen. Schließen der Bibliothek Nach Benutzung der Bibliothek muss diese wieder geschlossen werden, um alle allokierten Resourcen wieder freizugeben. Der letzte zugreifende Prozess entfernt damit 67 4.3. Die Bibliotheksschnittstelle (chContr) Kapitel 4. Konzept auch alle VLANs, QoS-Einstellungen und das Shared-Memory-Segment. Auch in dem Diagramm in Abbildung 4.5 wird davon ausgegangen, dass der ausführende Prozess, der letzte ist, der auf die Bibliothek zugreift. Abbildung 4.5: Kollaborationsdiagramm zu closeChannels() 1 Die Schließung der Bibliothek wird über die Methode closeChannels() durchgeführt. 1.1.* Bevor die Bibliothek geschlossen werden kann, muss geprüft werden, ob noch geöffnete Kanäle für den Prozess existieren. Ist dies der Fall, muss der Vorgang mit einer Fehlermeldung abgebrochen werden. 1.2.* Existieren keine geöffneten Kanäle mehr, werden alle chDescr-Objekte zerstört. 1.3 Mit der Methode closeAvailContr wird der Prozess vom Shared-Memory-Segment getrennt. 1.4 Der letzte zugreifende Prozess entfernt mit der Methode closeQoSContr alle QoS-Einstellungen. 1.5 Desweiteren entfernt der letzte Prozess mit closeDev am chDev-Objekt alle vorgenommen Einstellungen am Netzwerk-Device, inklusive VLANs. 68 Kapitel 4. Konzept 4.3. Die Bibliotheksschnittstelle (chContr) Öffnen eines Kanals Um Daten auf einem Kanal senden und empfangen zu können, muss ein Kanalendpunkt geöffnet werden. Ein geöffneter Kanal verfügt über ein Kommunikationsobjekt, das die nötigen send()- und recv()-Methoden bereitstellt. Abbildung 4.6: Kollaborationsdiagramm zu chOpen() 1 Die Bibliotheksschnittstelle zum Öffnen von Kanälen ist die Funktion chOpen(), bei der die ID des gewünschten Kanals übergeben werden muss. 1.1 Mit der Methode registerChannel() wird ermittelt, ob der Prozess auf den Kanal zugreifen darf und ob er dabei der erste ist. Außerdem wird ein Schlüssel zu einem globalen Synchronisationsobjekt im per Referenz übergebenen Parameter key eingetragen, wenn mehr als ein Prozess auf den Kanal zugreifen darf. Die registerChannel()-Methode ist durch das Semaphor geschützt, dass bei der Initialisierung erschaffen wird. 1.2 Wurde ein Wert größer Null zurückgeliefert, wird ein Kommunikationsobjekt für den Kanal erzeugt. 1.3 Weißt der Parameter key einen Wert ungleich Null auf, wird ein Sychronisationsobjekt mit dem enthaltenen Schlüssel generiert. 1.4 Sowohl Kommunikations- als auch Synchronisationsobjekt werden an das chDescr -Objekt übergeben. 1.5 Der Kanal wird mit der Methode bindChannel an das Netzwerk-Device gebunden. 69 4.3. Die Bibliotheksschnittstelle (chContr) Kapitel 4. Konzept 1.6 War der von der registerChannel()-Methode zurückgelieferte Wert gleich Eins, ist der Prozess der erste, der auf den Kanal zugreift. In diesem Fall werden die QoS-Eigenschaften des Kanals mit der Methode addChannel() an das chQoSContr-Objekt übergeben. Schließen eines Kanals Das Schließen eines Kanalendpunktes ist notwendig, um die Resourcen, die reserviert wurden, wieder freizugeben. Abbildung 4.7: Kollaborationsdiagramm zu chClose() 1 Um einen Kanalendpunkt zu schließen, muss die Methode chClose aufgerufen werden. 1.1 Der Kanal wird am chAvailContr mit der Methode unregisterChannel() deregistriert. 1.2 Konnte der Kanalendpunkt deregistriert werden, wird er mit der Methode releaseChannel () vom Netzwerk-Device entbunden. 1.3 Wurde von der Methode unregisterChannel() ein Wert gleich Eins zurückgeliefert, war der Prozess der letzte, der auf den Kanal zugegriffen hat. In diesem Fall werden die QoS-Eigenschaften des Kanal mit der Methode remChannel() 70 Kapitel 4. Konzept 4.3. Die Bibliotheksschnittstelle (chContr) am chQoSContr-Objekt wieder entfernt. Die registerChannel()-Methode ist durch das Semaphor geschützt, dass bei der Initialisierung erschaffen wird. 1.4 Das Kommunikations- und das Synchronisationsobjekt des Kanalendpunktes werden vom chDescr angefordert. 1.5 Wenn ein Synchronisationsobjekt vorhanden ist, wird es zerstört. 1.6 Das Kommunikationsobjekt wird zerstört. Senden auf einem Kanal Ist die Bibliothek initialisiert und ein Kanalendpunkt geöffnet, kann auf diesem gesendet bzw. empfangen werden. Abbildung 4.8: Kollaborationsdiagramm zu chOpen() 1 Zum Senden auf einem Kanal bietet die Biliotheksschnittstelle die Methode chSend (). 1.1 Vom chDescr-Objekt werden Synchronisations- und Kommunikationsobjekt angefordert. 1.2 Existiert ein Synchronisationsobjekt, so wird an ihm der exklusive Zugriff auf den Kanal reserviert. 1.3 chContr ruft die send-Methode am Kommunikationsobjekt auf. 1.4 Der exklusive Zugriff wird wieder aufgehoben, sofern er zuvor reserviert wurde. 71 4.4. Der Kanalendpunkt (chDescr) Kapitel 4. Konzept Empfangen auf einem Kanal Für den Empfang von Daten über einen Kanal ist keine Synchronisation notwendig, da die Information alle Kanalendpunkte erreichen soll. Abbildung 4.9: Kollaborationsdiagramm zu chRecv() 1 Mit der Methode chRecv() können Daten über einen geöffneten Kanalendpunkt empfangen werden. 1.1 Das Kommunikationsobjekt wird von chDescr geholt. 1.3 Die recv()-Methode wird aufgerufen. 4.4 Der Kanalendpunkt (chDescr) Die chDescr-Klasse stellt den Kanalendpunkt dar, durch den die Kommunikation mit den restlichen Teilnehmern stattfindet. Jedes chDescr-Objekt hat eine eindeutige ID, durch die es identifiziert wird und eine Reihe von QoS-Parametern, die es charakterisieren. Die ID ist eine netzwerkweite Identifikationsnummer, die für jeden Kanal eindeutig ist, und im Parameter id gespeichert wird. Kanalendpunkte, die auf einen Kanal zugreifen möchten, verwenden dessen ID. Durch die Art, wie die KanalID auf QDisc und Filter-Handles abgebildet wird, ergibt sich für die Kanal-ID ein Wertebereich von 0x001 bis 0xFFF. Der Ursprung dieser Grenzen wird in Abschnitt 4.7 beschrieben. Der Parameter prio entspricht einem Prioritätswert gemäß IEEE 802.1p. Der delay-Parameter ist die maximale Verweilzeit in der QDisc in µsec. Der Delay kann nicht direkt beeinflusst werden, sondern wird indirekt über die Priorität gesteuert. Der Parameter wird trotzdem in der Klasse gehalten, um zukünftigen Implementationen, die den Delay evtl. direkt beeinflussen können, zur Verfügung zu stehen. 72 Kapitel 4. Konzept 4.4. Der Kanalendpunkt (chDescr) Abbildung 4.10: chDesrc-Klasse Mit jitter verhält es sich genauso wie mit dem delay-Parameter. Der Jitter kann nicht direkt beeinflusst werden, sondern nur durch hohe Priorität oder durch Pufferung am Empfänger minimiert werden. psize ist die maximale Payload-Größe in Byte. Der konfigurierte Wert muss einge- halten werden, um die berechneten Parameter für den Delay einhalten zu können. Daher werden Daten, die größer sind als in psize spezifiziert, von der chSend()Methode der Bibliothek zurückgewiesen. Im Parameter bandwidth wird die benötigte Bandbreite des Kanals gespeichert. Diese wird in Pakete/s angegeben, kann aber über die Paketgröße in Byte/s umgerechnet werden. Alle Kanalendpunkte, die während des Betriebs gebraucht werden, werden zur Zeit der Bibliotheksinitialisierung erzeugt. Diese sind zu diesem Zeitpunkt noch ungeöffnet, was daran erkennbar ist, dass sie noch kein Kommunikationsobjekt besitzen. Das Kommunikationobjekt(com) kapselt im wesentlichen einen Packet-Socket und dessen Adresse, die mit dem Systemcall sendto() genutzt wird. 73 4.4. Der Kanalendpunkt (chDescr) Kapitel 4. Konzept Zusätzlich besitzt es die zwei abstrakten Methoden send()- und recv(), die durch Ableitung der Klasse überschrieben werden können. Die Basis-Methoden sind wiederum einfache Kapselungen der sendto() und recv() Systemcalls. Es sind jedoch auch andere Kommunikationsmethoden möglich, die bestimmte Kommunikationsformen besser unterstützen. So wäre es möglich, den Jitter auf Kosten des Delays zu verringern, indem man die empfangenen Pakete puffert. In Abbildung 4.11 wird das Konzept zur Umsetzung dieser Kommunikationsform gezeigt. Abbildung 4.11: Konzept für den gepufferten Empfang Für das Konzept benötigt man zwei verschiedene Ableitungen der Klasse com, von der die Klasse bufRecvCom die Senderseite des Kanals darstellt. Die Klasse für den Empfang bufProcCom wartet in einem eigenen Prozess auf die Ankunft von Paketen. Diese werden dann mit der send()-Methode an eine Message-Queue weitergegeben, sobald sie eintreffen. Das bufRecvCom-Objekt kann diese mit der eigenen recv()Methode Jitter-frei auslesen. Wird der Kanal von mehreren Prozessen genutzt, so muss der wechselseitige Ausschluss beim Senden gewährleistet sein. Zu diesem Zweck hält ein Kanalendpunkt, auf dem mehr als ein Prozess zugreifen darf, ein Synchronisationsobjekt (sync) bereit, über das ein Kanal zum Senden reserviert werden kann (Abbildung 5.1). 74 Kapitel 4. Konzept 4.5. Prozesszugriffsverwaltung (chAvailContr/ chAvail) Das in chDescr enthaltene Synchronisationsobjekt sync ist ein Prozess-übergreifendes, vom Betriebssystem verwaltetes Semaphor, das global über einen Schlüssel identifiziert wird. Das Semaphor wird beim Anlegen eines Eintrags im Shared-MemorySegment von der Methode addChAvailEntry() erzeugt und dessen Schlüssel wird im syncKey-Attribut des chAvail-Objekts abgelegt. 4.5 Prozesszugriffsverwaltung (chAvailContr/ chAvail) Abbildung 4.12: chAvailContr- und chAvail-Klasse Der Zugriff von mehr als einem Prozess auf einen Kanal ist nicht immer erwünscht. Zum einen hängen die QoS-Parameter stark von der genutzten Bandbreite ab, so dass nicht mehr Prozesse Daten senden dürfen, als vorher festgelegt wurden. Zum anderen ist auch die Anzahl der zur Verfügung stehenden Semaphoren begrenzt. Um zu verhindern, dass mehr Prozesse auf einen Kanal zugreifen, als vorgesehen sind, muss ein Prozess-übergreifender Mechanismus existieren, der den Zugriff auf die Kanäle einschränkt. Zu diesem Zweck existiert für jeden Kanal ein chAvailObjekt, das in einem Shared-Memory-Segment gehalten wird. Dies enthält sowohl die maximale Anzahl von Prozessen, die auf den Kanal zugreifen dürfen (max), als auch die aktuelle Anzahl von Prozessen, die schon auf den Kanal zugreifen (cur). Außerdem halten diese Objekte den Schlüssel zu dem Kanal-zugehörigen Semaphor, falls mehr als ein Prozess einen Endpunkt auf diesem Kanal öffnen darf. Ein neues Objekt wird mit der Methode addChAvailEntry() in das Shared-Memory-Segment eingetragen. Wurde der Eintrag schon von einem anderen Prozess vorgenommen, wird kein neuer Eintrag angelegt. Das Shared-Memory-Segment wird von dem ersten Prozess erzeugt, der die Methode initAvailContr aufruft, und vom letzten zugreifenden Prozess mit der Methode 75 4.6. Netzwerk-Device (chDev) Kapitel 4. Konzept closeAvailContr wieder zerstört. Alle anderen Prozesse verbinden bzw. lösen sich von dem bestehenden Segment. Die Methoden registerChannel() und unregisterChannel() dienen als Test-AndSet-Methoden zur Reservierung des Kanalzugriffs. Der Rückgabewert signalisiert den Zugriffsstatus des Kanals. Ein negativer Wert zeigt, dass der Kanalendpunkt nicht geöffnet bzw geschlossen werden kann. Ein Wert gleich Null gibt den Kanal zum Öffnen bzw. Schließen frei. Wird ein positiver Wert zurückgeliefert, heißt das, der Prozess ist der erste bzw. der letzte, der auf den Kanal zugreift. Damit wird signalisiert, dass die zum Kanal zugehörige QDisc angelegt bzw. gelöscht werden kann. Abbildung 4.13 zeigt den Vorgang exemplarisch für registerChannel als Aktivitätsdiagramm. Abbildung 4.13: registerChannel-Methode 4.6 Netzwerk-Device (chDev) Das Adressierungsschema eines Packet-Sockets ist vom unterlagerten Netzwerk-Device abhängig. Wird ein Kanal geöffnet, muss dessen Socket an das entsprechende Device gebunden und die Kanal-ID auf eine Adresse abgebildet werden. Die chDev-Klasse enthält für diesen Zweck zwei abstrakte Methoden, die durch Implementierung in abgeleiteten Klassen, für das jeweilige Adressierungsschema angepasst werden können. Für diese Arbeit ist lediglich eine abgeleitete Klasse für das Binden des Sockets an Ethernet-Devices vorgesehen. Eine Anpassung an andere Layer-2-Protokolle, soweit 76 Kapitel 4. Konzept 4.6. Netzwerk-Device (chDev) Abbildung 4.14: Die chDev-Klasse sie durch die Packet-Socket-Schnittstelle nutzbar sind, sollte durch dieses Design leicht fallen. Informationen über das reale und die zwei virtuellen Netzwerk-Devices werden vom chDev-Objekt in drei devInfo-Objekten gehalten. Die Information besteht aus dem Namen, dem Index und dem ARP-Addresstyp des Device. Bei der Initialisierung des chDev-Objekts mit der Methode initDev(), wird der Name des zu verwendenden realen Netzwerk-Devices übergeben, mit dem dann dessen Index und Adresstyp ermittelt werden. Je nach Adresstyp kann dann die Initialisierung fortgesetzt werden. Entspricht der Adresstyp Ethernet, werden als nächstes die beiden VLANs erzeugt, falls diese noch nicht existieren sollten. Das VLAN für die Kanäle bildet die Priorität der mit dem Kanal assoziierten Sockets auf die UserPriority eins zu eins ab. Das VLAN für den Best-Effort-Verkehr bildet alle SocketPrioritäten auf den User-Priority-Wert Eins ab, welcher nach 802.1p dem Wert mit der niedrigsten Priorität entspricht. Das VLAN-Device bekommt die IP-Adresse des physikalischen Device zugewiesen, und die MTU wird auf den in der initDev() übergebenen Wert gesetzt. Danach werden die Informationen über die beiden virtuellen 77 4.7. QoS-Controller (chQoSContr) Kapitel 4. Konzept Devices ermittelt und in deren devInfo-Objekten gespeichert. Die beiden abstrakten Methoden werden mit den Ethernet-spezifischen Methoden überschrieben. Die Konfiguration der virtuellen Devices wird über eine Konfigurationsbibliothek durchgeführt, die die nötigen ioctl()-Zugriffr kapselt. Die für Ethernet konzipierte bindChannel()-Methode öffnet einen Packet-Socket, bildet die Kanal-ID auf eine Ethernet-Multicast-Adresse ab und bindet den PacketSocket des Kommunikationsobjekts an diese Adresse am virtuellen Device. Die ermittelte Adresse wird dem Kommunikationsobjekt zugewiesen. releaseChannel() entfernt den Packet-Socket von dem Netzwerk-Device und schließt ihn. 4.7 QoS-Controller (chQoSContr) Abbildung 4.15: Die chQoSContr-Klasse Der QoS-Controller ist die Schnittstelle zu den Queueing-Disziplinen unter Linux. Beim Hinzufügen oder Entfernen eines Kanals übernimmt der QoS-Controller die Parameter des Kanals und fügt entsprechende Queueing-Disziplinen hinzu oder entfernt sie wieder. Dazu nutzt er eine dafür implementierte QDisc-KonfigurationsBibliothek, die den Zugriff auf die Netlink-Sockets kapselt. Die Filterregeln, die zum Klassifizieren der Pakete genutzt werden, sind vom verwendeten Layer-2-Protokoll abhängig. Unter Ethernet ist die ID eines Kanals Teil einer Filterregel. Diese ist ein Teil der Multicast-Adresse des Ethernet-Frames. Wird die Bibliothek für ein Device mit einem anderen Layer-2-Protokoll genutzt, kann diese 78 Kapitel 4. Konzept 4.7. QoS-Controller (chQoSContr) Regel nicht verwendet werden. Für diesen Fall wird in chQoSContr eine Tabelle mit Filterregeln für mögliche Adresstypen gehalten. Das chQoSContr-Objekt wird mit dem Index des zu verwendenden Device und dessen Adresstyp über die Methode initQoS() initialisiert. Wird für den boolschen Parameter initQDisc TRUE übergeben, so wird ein Grundgerüst an Queueing-Disziplinen erzeugt, an dem jeweils die benötigten Queueing-Disziplinen für die Kanäle angebracht werden. Die QDisc für den Best-Effort-Verkehr wird auch zur Zeit der Initialisierung erzeugt und bekommt zunächst die gesamte Bandbreite zugesprochen. Die Methode closeQoS entfernt alle QoS-Einstellungen vom konfigurierten NetzwerkDevice. Mit den Methoden addChQoS() und remChQoS() werden die Queueing-Disziplinen, entsprechend der übergebenen QoS-Parameter, erzeugt bzw. gelöscht. Gleichzeitig werden die Filter, entsprechend der Regeln für den Adresstyp, gesetzt. Mit jedem Hinzufügen und Entfernen von Kanälen, wird die QDisc des Best-Effort-Verkehrs mit der verbleibenden Bandbreite rekonfiguriert. Die Vorgehensweise, QDiscs und Filter für die Kanäle erst bei deren Öffnung bzw. Schließung zu konfiguriert, unterstützt die Erweiterbarkeit der Bibliothek für dynamische Umgebungen. Wie der Aufbau der QDiscs bei der Initialisierung und für jeden Kanal im Detail aussieht, soll im Folgenden genauer betrachtet werden. Basis-QDisc-Konfiguration Für die Basiskonfiguration (Abbildung 4.16) wird bei der Initialisierung zunächst eine Prio-QDisc mit acht Bändern als Root-QDisc konfiguriert. Wie in 2.3.3.2 erläutert, kann die Major-Nummer eines QDisc-Handles mit einem Wert zwischen 0x0001 und 0x7FFF erzeugt werden. Für die Prio-QDisc wird das Handle 0x7FFF:0 gewählt, damit die niedrigen Handles für die Blatt-QDiscs frei bleiben. In 3.4.4 wurde besprochen, dass eine Prio-QDisc ihre Klassen selbst in der Anzahl der konfigurierten Bänder erzeugt. Diese Klassen erhalten Handles deren Minor Nummer von 0x0001 bis 0x0008 durch nummeriert. Die Priorität der Klassen ist umgekehrt proportional zu deren Minor-Nummer. Switches klassifizieren Pakete nach dem in IEEE 802.1p definierten Schema (vgl. Kapitel 2.1.1), daher müssen Pakete in der QDisc auf die gleich Weise klassifiziert werden. Um das zu gewährleisten wird für jede Klasse der Prio-QDisc ein u32-Filter 79 4.7. QoS-Controller (chQoSContr) Kapitel 4. Konzept Abbildung 4.16: Basis-QDisc-Konfiguration erzeugt, der das User-Priority-Feld des VLAN-Tags ausliest und an die Zielklasse mit der entsprechenden Priorität sendet (vgl. Abbildung4.16 und Tabelle A.1). Um den Klassifizierungsaufwand für hochpriore Pakete zu minimieren, werden den Filtern Prioritäten entsprechend der Priorität ihrer jeweiligen Zielklasse zugewiesen. Grundsätzlich müsste man keinen eigenen Filter für Prio erzeugen, da dieser Pakete selbst aufgrund ihrer Socket-Priorität klassifizieren kann. Auf die Socket-Prioritäten der Pakete, die über das Best-Effort-Device versendet werden, kann jedoch kein Einfluss genommen werden. Daher wird die Socket-Prioritäten durch die EgressPrio-Map des Best-Effort-Devices alle auf die Priorität 1 abgebildet und für die QDisc die Filter erzeugt. 80 Kapitel 4. Konzept 4.7. QoS-Controller (chQoSContr) Wie in in Kapitel 3.3.3 besprochen, muss der Verkehr für eine nach dem StrictPriority-Algorithmus arbeitende Queue charakterisierbar sein, um den maximalen Delay innerhalb der Queue garantieren zu können. Dort wurde der Verkehr durch den Token-Bucket-Algorithmus eingegrenzt. Dies soll in der Konfiguration durch eine HTB-QDisc geschehen, deren Kindklassen die Bandbreite für jeden Kanal eingrenzen sollen. Die QDisc wird als reiner Bandbreitenbegrenzer eingesetzt werden, ohne das Automatische Verteilen von überschüssiger Bandbreite. In der Basiskonfiguration werden die Klassen für die Kanäle noch nicht erzeugt, sondern nur deren Eltern-QDiscs an jeder Prio-Klasse. Die Handles der QDiscs werden so gewählt, dass deren Major-Nummer jeweils der um eins inkrementierten UserPriority der Pakete entspricht, die ihnen durch die Filter zugewiesen werden. Auch hier sei zum besseren Versändnis wieder auf Tabelle A.1 verwiesen. Abbildung 4.17: Best-Effort-QDisc-Konfiguration An der QDisc 2:0, deren Elternklasse die niedrigste Priorität hat, wird die Klasse für den Best-Effort Verkehr angefügt (Abbildung 4.17). Diese bekommt das Handle 2:FFFF zugewiesen, da 0xFFFF dieses außerhalb des Wertebereichs der Kanal-ID und es daher nicht zu Kollisionen der Klassen Handles kommen kann. Bevor die 81 4.7. QoS-Controller (chQoSContr) Kapitel 4. Konzept Kanäle geöffnet werden, bekommt die Klasse des Best-Effort-Device die gesamte Bandbreite als rate-Parameter zugewiesen. Als Burst-Parameter wird die maximale Best-Effort-Paketgröße zugewiesen. Diese entspricht der für das Best-Effort-Device konfigurierten MTU plus Präambel, Header, CRC und IFG. Der Eltern-QDisc wird ein Filter angefügt, der die Pakete nach dem Wert im VIDFeld des VLAN-Tags klassifiziert. Entspricht die VID im Paket, der VID des BestEffort-VLANs, so wird das Paket an die Klasse 2:FFFF übergeben. Der Klasse 2:FFFF wird eine SFQ-QDisc als Blatt-QDisc zugewiesen, der ein Handle automatisch vom Betriebssystem zugewiesen bekommt. Diese sorgt für Fairness zwischen den Datenströmen, die den Best-Effort-Verkehr ausmachen. QDisc-Konfiguration für Kanäle Abbildung 4.18: QDisc-Konfiguration für die Kanäle 2 (Prio 4), 3 (Prio 1), 4 (Prio7) und 5 (Prio4) 82 Kapitel 4. Konzept 4.7. QoS-Controller (chQoSContr) Jedes mal, wenn ein Kanal vom ersten Prozess geöffnet bzw. vom letzten Prozess geschlossen wird, muss eine Konfiguration der QDiscs vorgenommen werden. Wird ein Kanal das erste mal geöffnet, wird für ihn eine eigene Klasse an eine der HTBQDiscs angefügt. Die Eltern-QDisc wird mit Hilfe des Prio-Parameters des Kanals gewählt. Die Major-Nummer des Handles entspricht des um eins inkrementierten Wert des Prio-Parameters. Um die Klasse zur späteren Löschung identifizieren zu können, erhält die Klasse ein Handle mit einer Minor-Nummer, die der Kanal-ID entspricht. In Abbildung 4.18 wird das Prinzip beispielhaft an vier Kanälen demonstriert. Die Kanäle mit den IDs 2 und 5 besitzen einen Priority-Wert von 4, während Kanal 4 einen Priority-Wert von 7 hat. Kanal 3 hat die niedrigste Priorität, was nach IEEE 801.1p die Priorität 1 ist. Die Eltern-QDisc der Klasse, die für den Kanal erzeugt wird, ist die gleiche wie die, der Klasse für den Best-Effort-Verkehr (blass eingezeichnet). Der Wert für den rate-Parameter wird, wie in 4.4 angegeben, aus dem bandwidthund dem psize-Parameter des Kanals berechnet. Jedoch muss dazu noch der Overhead durch Präambel, Start-of-Frame-Delimiter, Ethernet-Header inklusive VLANTag, CRC und IFG auf die Paketgröße addiert werden. Als cburst-Parameter dient der Parameter psize, der wiederum um den Overhead erhöht wird. Für jede der Klassen wird ein Filter an der Eltern-QDisc angelegt, der die Pakete anhand der letzten 12 Bit der Zieladresse im Ethernetheader klassifiziert. Entsprechen diese der ID des Kanals, für den die Klasse angelegt wurde, wird das Paket an die Klasse weitergegeben. Um die Filter zur späteren Löschung eindeutig identifizieren zu können, werden diese mit einem eindeutigen Handle angelegt und Priorität angelegt. Da die Kanäle, die diese Filter passieren, alle die gleiche Priorität haben, wird alle Filter die gleiche Priorität konfiguriert, wodurch, wie in Kapitel 3.4.5 erläutert, nur eine Hash-Tabelle für die Filter angelegt wird. Als Handle für den Filter wird die Kanal-ID verwendet, die jedoch nur 12-Bit breit ist. Dadurch ergibt sich die obere Grenze für den Wertebereich der Kanal-IDs Es wäre zwar möglich eine Zuordnungstabelle von Kanal-IDs zu Filterhandles zu verwalten, jedoch müsste diese dann wieder Prozess-übergreifend zugreifbar sein. Um diesen Aufwand zu umgehen wurde ein verminderter Wertebereich für die Kanal-IDs in Kauf genommen. Als Blatt-QDisc kommt eine pfifo-QDisc zum Einsatz, deren Handle automatisch zugewiesen wird, da diese beim Löschen der Klasse automatisch mit entfernt wird. 83 4.7. QoS-Controller (chQoSContr) Kapitel 4. Konzept 84 Kapitel 5 Implementierung In diesem Kapitel wird die prototypische Implementierung des im Kapitel 4 besprochenen Designs behandelt. Da das objekt-orientierte Konzept in der prozeduralen Programmiersprache C umgesetzt wurde, sind ein paar Dinge anzumerken. Alle Utility-Klassen sind als globale, statische Variablen und Funktionen in einem jeweils getrennten Modul umgesetzt worden. Die aus der Objekt-Orientierten Softwareentwicklung bekannte Vererbung von Klassen, ist in der prozeduralen Programmiersprache C nicht im klassischen Sinne umsetzbar. Die im Klassendiagramm verwendeten, ableitbaren Klassen bieten im wesentlichen die Möglichkeit, abstrakte Methoden auf verschiedene Weise zu implementieren. Diese Methoden werden in C durch Zeiger auf Funktionen realisiert. Es werden verschiedene Implementierungen einer solchen Funktion mit gleichen Parametern und Rückgabetypen erstellt. Zur Laufzeit wird ermittelt, welche Implementierung der Funktionen genutzt werden soll und deren Adresse im Zeiger gespeichert. Im Folgenden wird kurz auf die Implementierungsumgebung eingegangen. Danach werden die einzelnen Module der Bibliothek in den entsprechenden Abschnitten genauer betrachtet. Module wie Konfigurationsbibliotheken, die semantisch einem anderen Modul untergeordnet sind, werden in den Unterabschnitten der jeweiligen Hauptmodule behandelt. Abschließend wird deren Implementierungsaufwand inklusive Kommentaren aufgezeigt. 85 5.1. Implementierungsumgebung 5.1 Kapitel 5. Implementierung Implementierungsumgebung Die Implementierung wird auf einem Rechner mit Intel Celeron 2,8GHz und 512 MB RAM durchgeführt, auf dem eine Debian Linux Distribution in der Version 3.1 installiert wurde. An der Standard Kernelkonfiguration werden folgende Optionen verändert: • Die Option CONFIG_PREEMPT wird entfernt, da das Modul 8021q sonst bei atomaren Vorgängen unterbrochen wird, was zu Kernel Panics führt. • Das Linux Advanced Router Modul wird, durch Entfernen der Option CONFIG_IP -_ADVANCED_ROUTER, aus dem Kernel herausgenommen. • Um das Propagieren von IPv6-Routing-Informationen zu unterbinden, wird die CONFIG_IPV6-Option entfernt. Als Entwicklungsumgebung dient eine Kombination aus dem Editor vim-6.3.68 und dem Compiler gcc-3.3.5. Zum Auswerten der Konfigurationsdatei, werden das parser- und das tree-Modul der Bibliothek libxml2 genutzt. Bei der Konfiguration der Queueing-Disziplinen werden, zum Aufbauen und Versenden der Netlink-Messages, Teile des Quellcodes des Programms TC verwendet. Dieses ist Teil der iproute2-Utility-Suite [ipr], welche unter der GNU-Public-License2 veröffentlicht ist. Für die Messung der Ende-zu-Ende Propagationszeit, hat Dipl. Inf. Bernhard Gelling eine Messbibliothek zur Verfügung gestellt. 5.2 chConfRead Die Konfigurationsdatei wird mit Hilfe des XML-Parsers aus der Bibliothek libxml2 eingelesen und mit dem tree-Modul der Bibliothek, in einer Baumstruktur abgebildet. Danach wird der Aufbau der Datei verifiziert1 und die Position benötigter Knotenpunkte des Baums in Zeigern abgelegt. 1 Es wird darauf hingewiesen, dass der Aufbau nicht über eine DTD-Datei, sondern durch den Sourcecode selbst verifiziert wird 86 Kapitel 5. Implementierung 5.3. chContr Implementierungsaufwand Datei Zeilen (ohne Kommentare) chConfRead.h 10 chConfRead.c 256 xmlNav.h 11 xmlNav.c 83 Gesamt 360 Tabelle 5.1: Implementierungsaufwand chConfRead 5.3 chContr In der Header-Datei der Bibliothek sind eine Reihe von Rückgabewerte definiert, um aufgetretene Fehler in den Funktionen näher zu spezifizieren (Listing 5.1). Bei einem Fehler in der Bibliothek werden diese als Negativwerte zurückgeliefert. Quelltext 5.1: Fehlerrückgabewerte 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 enum { NO_ERR = 0 , ERR_NOT_SPEC , ERR_AVAIL_TABLE_FULL , ERR_AVAIL_TABLE_EMPTY , ERR_INVAL_CH_ID , ERR_INVAL_MODE , ERR_INVAL_SIZE , ERR_CH_OPEN , ERR_CH_OCCUPIED , ERR_CH_NOCCUPIED , ERR_CH_NOT_EXIST , ERR_NO_QOS_INIT , ERR_QOS_INIT_CALLED , ERR_NO_XML_FILE , ERR_INVAL_XML_FILE , ERR_INVAL_DEVICE , ERR_INVAL_ADDRTYPE , ERR_NO_LOCAL_MEM , ERR_SYSCALL , E R R _ AV A I L _ E N T R Y_ O C C }; // // // // // // // // // // // // // // // // // // // // Kein Fehler Nicht näher spezifizierter Fehler Shared - Memory ist voll Es existiert kein Eintrag im Shared - Memory Die Kanal - ID kann ( noch ) nicht eingesetzt werden Kom munikati onsmodu s existiert nicht Übergebene Payload größer als zugelassen Der Kanal ist bereits geöffnet Der Kanal ist bereit besetzt Der Kanal ist nicht besetzt Der Kanal ist nicht für den Einsatz vorgesehen Bibliothek wurde noch nicht initialisiert Bibliothek wurde bereits initialisiert Pfad zur Konfig uration sdatei ist falsch Aufbau Konf igurati onsdate i ist falsch Netzwerk - Device existiert nicht Adresstyp wird nicht unterstützt malloc () ist fehlgeschlagen Ein Systemcall ist fehlgeschlagen Es existiert schon ein Eintrag für diesen Kanal im ShM Alle Funktionen der Bibliothek werden entsprechend der Kollaborationsdiagramme aus Kapitel 4.3 umgesetzt. In der Initialisierungsfunktion initChannels() wird zunächst ermittelt, ob die Bi- 87 5.3. chContr Kapitel 5. Implementierung bliothek, und damit alle globalen Betriebsmittel, schon von einem anderen Prozess initialisiert wurde. Dies geschieht indem das Semaphor, das das Shared-MemorySegment schützen soll, mit einem festen Schlüssel und den Flags IPC_CREAT und IPC_EXCL aufgerufen wird. Kommt es dabei zum Fehler und der Inhalt der globalen Variablen errno entspricht EEXIST, wird davon ausgegangen, dass der Erstzugriff auf die Bibliothek schon durch einen anderen Prozess erfolgt ist. In diesem Fall bekommt die globale Variable first den Wert FALSE zugewiesen, die jeder init-Funktion der anderen Contr-Klassen als Parameter übergeben werden. Für die Funktion chOpen() wird ein mode-Parameter deklariert, der in zwei 16-Bit Felder geteilt ist. Die oberen 16-Bit sind für Flags reserviert. Zur Zeit der Arbeit ist nur das USE_THREADS-Flag definiert, welches signalisiert, dass es sich, bei dem auf die Bibliothek zugreifenden Prozess, um eine multi-threaded Anwendung handelt und somit der Sendezugriff auf den Kanalendpunkt wechselseitig ausgeschlossen werden muss. Die unteren 16-Bit bestimmen den Kommunikationsmodus des Kanals. Bisher sind zwei Kommunikationsmodi definiert: CH_BASIC_MODE und CH_TEST_MODE. Die beiden Modi werden in Abschnitt 5.6 und Kapitel 6.3 näher erklärt. Aufgrund des Kommunikationsmodus bekommt das chDescr-Objekt seine send()- und recv()-Methoden zugewiesen (Listing 5.2). Quelltext 5.2: Zuweisung der Kommunikationmethoden 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 switch ( mode & 0 xFFFF ) { case CH_BASIC_MODE : channel - > send = basicModeSend ; channel - > recv = basicModeRecv ; break ; # ifdef _CH_TEST_ case CH_TEST_MODE : channel - > send = testModeSend ; channel - > recv = testModeRecv ; break ; # endif default : releaseChannel ( channel ) ; un reg iste rCh ann el ( id ) ; return - ERR_INVAL_MODE ; } Bei der Registrierung des Kanalendpunkts wird der Methode registerChannel() ein syncKey-Parameter per Referenz mit übergeben. Existiert ein Schlüssel für den Kanal, wird er in den Parameter eingetragen. Mit dem Schlüssel wird eine Verbindung zum Semaphor geöffnet. Kann nur ein Prozess auf einen Kanal zugreifen, wird 0 88 Kapitel 5. Implementierung 5.3. chContr in den syncKey eingetragen, was signalisiert, dass kein Semaphor für diesen Kanal existiert. Nutzt der Prozess jedoch Threads, so muss auch hier der wechselseitige Ausschluss beim Senden gewährleistet sein. Dazu muss beim Öffnen des Kanalendpunktes das USE_THREADS-Flag gesetzt werden, welches die chOpen-Methode veranlasst, ein lokales Semaphor mit dem Schlüssel IPC_PRIVATE zu erzeugen. Ist das Flag gesetzt und ein Prozesszugriff von mehr als einem Prozess ist erlaubt, wird das Prozess-übergreifende Semaphor für den Kanalendpunkt genutzt. Der Vorgang ist in Abbildung 5.1 dargestellt. Abbildung 5.1: Semaphore für Prozess- und Threadsynchronisation Nachdem der Socket des chDescr-Objekts an das Device gebunden ist, wird ihm die für den Kanal definierte Priorität zugewiesen (Listing 5.3). Quelltext 5.3: Setzen der Socket-Priotität 1 2 3 if ( setsockopt ( channel - > sock , SOL_SOCKET , SO_PRIORITY , ( const void *) & prio , sizeof ( prio ) ) < 0) { return -1; } Die Liste der chDescr-Objekte wird in dem dafür implementierten Modul chContain eingefügt. Auf dieses Modul wird im Folgenden eingegangen. chContain Das chContain-Modul implementiert einen Container für die chDescr-Objekte. Da die Objekte für jeden Kommunikationsvorgang benötigt werden, ist ein schnellst- 89 5.3. chContr Kapitel 5. Implementierung möglicher Zugriff auf die Objekte nötig. Eine einfache Liste, die sequenziell durchsucht werden müsste, kann daher nicht eingesetzt werden. Am besten wäre die direkte Abbildung der Kanal-ID auf eine Speicheradresse, jedoch würde ein Array aus Zeigern mit 212 Einträgen alleine 4kByte Speicher belegen, ohne auch nur ein Objekt zu referenzieren. Eine Hash-Tabelle würde einen kollisionsfreien Hash-Algorithmus voraussetzen und, je nach Dimensionierung der Tabelle, wiederum zu Suchoperationen führen. Abbildung 5.2: Speichermodell für chDescr-Objekte Die implementierte Lösung sieht ein dynamisch angelegtes, zweidimensionales Array mit Zeigern auf chDescr-Objekte vor (Abbildung 5.2). Die Indizierung erfolgt über die Kanal-ID, die über einen Offset in zwei Segmente geteilt wird. In der Implementierung wird ein Offset von 5 gewählt, was eine Verhältnis von 128 auf 32 Einträgen bedeutet. Um den Speicherbedarf gering zu halten, werden nur die Arrays der zweiten Dimension erzeugt, die benötigt werden. Desweiteren wird für das erste Array nur so viel Speicher allokiert, wie zur Haltung der benötigten Arrays der zweiten 90 Kapitel 5. Implementierung 5.3. chContr Dimension erforderlich ist. So ist die optimale Speicherausnutzung abhängig von der Distanz zwischen den einzelnen Kanal-IDs. Für 32 chDescr-Objekte die von 0 bis 31 sequenziell durchnummeriert sind, würde die Tabelle 132 Byte groß sein2 . Die Größe würde jedoch auf 4224 Byte ansteigen, wenn die IDs der 32 Objekte jeweils eine Distanz von 32 aufweisen würden3 . Das ist generell nicht problematisch, sollte jedoch berücksichtigt werden, wenn der Speicheroverhead gering gehalten werden soll. In Listing 5.6 werden die Makros zur Umrechnung der Kanal-ID auf die Indizes und zur Bestimmung der Größe des Arrays der zweiten Dimension gezeigt. Quelltext 5.4: Makros zur Umrechnung der Indizes in 1 2 3 4 # define # define # define # define chContain ID_OFFSET (( unsigned short ) 5) LDSEG ( x ) (( x ) >> ID_OFFSET ) // Leading Segment TRSEG ( x ) (( x ) & ~(( unsigned short ) 0 xFFFF << ID_OFFSET ) ) // Trailing Segment TRSEGSIZ (( unsigned short ) (1 << ID_OFFSET ) ) Mit der Funktion insertChannelDescr()werden chDescr-Objekte der Tabelle hinzugefügt. Zunächst wird geprüft, ob der errechnete, obere Index größer ist, als die Anzahl der allokierten Array-Elemente. Ist das der Fall, wird das Array mit realloc() mit der erforderlichen Größe neu allokiert und die neu hinzugewonnenen Zeiger im Array mit NULL initialisiert. Quelltext 5.5: Stufenweises Allokieren in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 chContain if ( LDSEG ( channel - > id ) >= tableSize ) { if ( chDescrTable != NULL ) { memOffset = tableSize ; } tableSize = LDSEG ( channel - > id ) +1; chDescrTable = realloc (( void *) chDescrTable , tableSize * sizeof ( struct chDescr **) ) ; if ( chDescrTable == NULL ) { tableSize = memOffset ; return - ERR_NO_LOCAL_ME M ; } for (; memOffset < tableSize ; memOffset ++) { chDescrTable [ memOffset ] = NULL ; } } 2 3 32 · 4Byte + 4Byte für das erste Array der Größe 1 32 ∗ 32 ∗ 4Byte + 32 ∗ 4Byte 91 5.3. chContr Kapitel 5. Implementierung Danach wird geprüft, ob der Zeiger des oberen Index auf NULL zeigt. Ist dies der Fall, wird der Speicher für ein neues Array allokiert und dessen Felder mit NULL initialisiert. Ist die Position mit den für die ID ermittelten Indizes noch nicht belegt, wird der Speicher für ein neues chDescr-Objekt allokiert und die Daten des übergebenen Objekts kopiert. Ist die Position schon belegt wird -ERR_CH_OCCUPIED zurückgeliefert, ansonsten 0. Mit der Funktion getChannelDescr() wird der Zeiger auf ein gespeichertes Objekt angefordert. Durch die Art der Speicherung kann die Position mit nur drei Instruktionen abgefragt werden. Existiert das Objekt nicht, wird der NULL-Zeiger zurückgeliefert. Die Funktion removeChannelDescr()entfernt einen einzelnen Eintrag aus der Tabelle, wobei dessen Speicher freigegeben wird. Jedoch wird nicht geprüft, ob der Eintrag der letzte war, und somit eines der Arrays freigegeben werden könnte. Ist kein Eintrag an der Stelle vorhanden, wird -ERR_INVAL_CH_ID und ansonsten 0 zurückgeliefert. Die Funktion getNextChannelDescr()kann zum sequentiellen Abrufen der gespeicherten Elemente genutzt werden. Mit jedem Aufruf wird ein Iterator übergeben, der die Stelle markiert, ab der nach dem nachfolgenden Element gesucht werden soll. Wird ein Objekt gefunden, wird der Zeiger auf dessen Speicheradresse zurückgeliefert und der Parameter iterator auf die aktuelle Position gesetzt. Befindet sich kein Objekt mehr hinter der angegeben Position, wird NULL zurückgeliefert. Wird mit jedem neuen Aufruf der Iterator des letzten Aufrufs übergeben, wird die Liste sequenziell durchlaufen. Um die Suche am Anfang der Tabelle zu beginnen, muss dem Iterator ein Wert von -1 zugewiesen werden. Implementierungsaufwand Datei Zeilen (ohne Kommentare) channel.h 51 chContr.c 402 chContain.h 11 chContain.c 184 Gesamt 648 Tabelle 5.2: Implementierungsaufwand chContr 92 Kapitel 5. Implementierung 5.4 5.4. chDescr chDescr Quelltext 5.6: Die 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct chDescr { unsigned short int unsigned long unsigned long unsigned long unsigned short int int int unsigned int struct sockaddr socklen_t int int }; chDescr-Struktur id ; prio ; // Usr Priority delay ; // usec jitter ; // usec bandwidth ; // Pakets / s psize ; // PayloadSize [ Byte ] mode ; // Kommun ikation smodus sem ; // S y n c h ro n i s a ti o n s ob j e k t sock ; // Ko mm uni ka tio nso bj ekt seq ; // Bisher gesendete Pakete * sendAddr ; // Mcast - Addr addrLen ; // Länge von sendAddr (* send ) ( struct chDescr * channel , void * buf , size_t size ) ; (* recv ) ( struct chDescr * channel , void * buf , size_t size ) ; Die chDescr-Struktur hält alle Informationen, die mit dem Kanalendpunkt assoziiert werden. mode wird von der Funktion chOpen() des chContr-Moduls gesetzt und entspricht dem Parameter der gleichen Bezeichnung. Das Kommunikationsobjekt ist in Form des Socket-File-Deskriptors(sock), der sockaddr -Struktur und den Zeigern auf die Funktionen (*send)() und (*recv)() in die Struktur integriert. Die Einbindung des Members addrLen ist vorgesehen, da bei verschiedenen Layer-2-Protokollen unterschiedliche sockaddr-Strukturen eingesetzt werden, deren Größe variiert. Aus diesem Grund kann keine statische sockaddr-Struktur eingefügt werden, sondern ein Zeiger. Das Datenmember sem hält den Deskriptor des Semaphors, dass als Synchronisationsobjekt dient. Beim Anlegen der chDescr-Objekte werden zunächst nur die ID und die QoS-Parameter initialisiert. Solange der Kanalendpunkt geschlossen ist, bleiben sock und sem auf 0 bzw. -1. Im chDescr-Modul sind die Basis-Kommunikationsfunktionen basicModeSend() und basicModeRecv() implementiert, die den Methoden (*send)() und (*recv)() zugewiesen werden, wenn beim Öffnen des Kanals CH_BASIC_MODE übergeben wird (Listing 5.2). 93 5.5. chAvailContr Kapitel 5. Implementierung Implementierungsaufwand Datei Zeilen (ohne Kommentare) chDescr.h 36 chDescr.c 40 Gesamt 76 Tabelle 5.3: Implementierungsaufwand chDescr 5.5 chAvailContr Bei der Initialisierung des Moduls wird eine boolsche Variable im Parameter initShm übergeben. Ist diese auf TRUE gesetzt, wird das Shared-Memory-Segment erzeugt und mit 0 initialisiert. Ansonsten wird das existierende Shared-Memory-Segment in den lokalen Adressraum eingeblendet. Wird mittels der Funktion addChAvailEntry() ein neuer Eintrag dem Shared-MemorySegment hinzugefügt, wird überprüft, ob mehr als ein Prozess auf den Kanal, für den dieser Eintrag gilt, zugreifen darf. Ist das der Fall, wird für den Kanal ein Semaphor erzeugt und dessen Erzeugungsschlüssel im Eintrag abgelegt. Beim Befüllen des Shared-Memory-Segments mit chAvail-Einträgen wird eine Fehlermeldung ausgegeben, falls das Ende des Segments erreicht wird. Um festzustellen wo sich der nächste freie Eintrag befindet, macht sich das Programm zunutze, dass kein Kanal mit der ID 0 existieren kann und das Shared-Memory-Segment mit Null initialisiert wird. Listing 5.7 zeigt die dafür verwendete Suchschleife. Quelltext 5.7: Suchschleife in 1 2 3 chAvailContr for ( i = 0; ( chAvailTable [ i ]. id != 0) ||( sizeof ( struct chAvail ) > ( PAGE_SIZE - i * sizeof ( struct chAvail ) ) ; i ++) { ... } Beim Schließen der Bibliothek wird am Shared-Memory-Segment überprüft, ob der aufrufende Prozess der letzte darauf zugreifende ist. Dies geschieht durch einen Aufruf des Systemcalls shmctl() wie in Lisiting 5.8 dargestellt. Quelltext 5.8: Abruf der Shm-Statistiken 1 shmctl ( shmID , IPC_STAT , & shmParam ) ; 94 Kapitel 5. Implementierung 5.6. chDev Im shmParam.shm_nattach ist daraufhin die Anzahl der auf das Shared-MemorySegment zugreifenden Prozesse zu finden. Ist der Prozess der letzte zugreifende, so werden die Semaphoren aller Kanäle und das Shared-Memory-Segment zerstört. Implmentierungsaufwand Datei Zeilen (ohne Kommentare) chAvailContr.h 30 chAvailContr.c 245 Gesamt 275 Tabelle 5.4: Implementierungsaufwand chAvailContr 5.6 chDev Im Klassendiagramm wird gezeigt, dass die Utitltiy-Klasse chDev über zwei abstrakte Methoden verfügt, die durch Ableitung der Klasse überschrieben werden können. Im Modul chDev sind die zwei statischen, globalen Zeiger (*bindChannelP)() und (*releaseChannelP)() deklariert, die diese abstrakten Methoden repräsentieren. Bei Initialisierung wird der Adresstyp des Devices ermittelt, dessen Namen als Parameter der Funktion initDev übergeben wird. Aufgrund des Adresstyps wird ermittelt welche Implementierungen der Funktionen bindChannel() und releaseChannel() den Zeigern zugewiesen werden. vlan Handelt es sich bei dem Adresstyp um Ethernet, werden die virtuellen Devices erzeugt. Alle dafür nötigen ioctl()-Zugriffe werden von der dafür implementierten Konfigurationsbibliothek vlan gekapselt. Nach oben bietet die Bibliothek sowohl Funktionen zum Anlegen und Löschen von virtuellen Devices, als auch zum Setzen der Ingress- und Egress-Priority-Maps. Eine Besonderheit stellt die Funktion addBE_VLAN() dar, da sie ein virtuelles Device erzeugt, das die IP-Adresse des physikalischen Devices auf das virtuelle überträgt und dessen MTU setzt. Die Funktion remBE_VLAN() macht diese Konfigurationen wieder rückgängig. 95 5.6. chDev Kapitel 5. Implementierung devInfo Das devInfo-Modul definiert die Struktur devInfo, die Name, Index und Addresstyp eines Devices enthält. Zum Abrufen dieser Daten ist die Funktion initDevInfo() definiert, die diese über ioctl()-Aufrufe bezieht. chEth Dieses Modul definiert die Funktionen chEthBind()und chEthRealease(), welche die Ethernet-spezifischen Implementationen bindChannel() und releaseChannel() -Methoden sind. Man kann im objekt-orientierten Sinne davon sprechen, dass chEth eine abgeleitete Klasse von chDev ist. Die Funktion chEthBind() öffnet einen Paket-Socket und weißt den zurückgegebenen Socket-Fileskriptor den sock-Member derchDescr-Struktur zu, die als Parameter übergeben wurde. Anschließend wird eine sockaddr_ll-Struktur allokiert und dem addr-Member zugewiesen. Diese Struktur ist speziell für Packet-Sockets spezifiziert und erhält als Adressinformationen die Multicast-Adresse des Kanals und den Index des virtuellen Devices. Mit diesen Daten wird der Socket an das Device gebunden und anschließend in eine Multicast-Gruppe im Treiber eingetragen. Die Funktion chEthRelease() entfernt den Socket wieder aus der Multicast-Gruppe und schließt ihn. Anschließend gibt er den Speicher des addr-Members frei. Datei Zeilen (ohne Kommentare) chDev.h 11 chDev.c 125 chEth.h 9 chEth.c 135 devInfo.h 11 devInfo.c 125 vlan.h 11 vlan.c 295 Gesamt 722 Tabelle 5.5: Implementierungsaufwand chDev 96 Kapitel 5. Implementierung 5.7 5.7. chQoSContr chQoSContr Bei Initialisierung des chQoSContr-Moduls wird zunächst die Kommunikationsverbindung zum Kern aufgebaut. Zu diesem Zweck wird die in der iproute2-Utility-Suite beiliegende libnetlink-Bibliothek integriert. Mit der Funktionrtnl_open() wird eine globale rtnl_handle-Struktur initialisiert, die für die gesamte Kommunikation genutzt wird. Eine andere Funktion, die von iproute2 übernommen wird, ist tc_core_init(), die die in Kapitel 3.4.4 beschriebenen Timer-Werte initialisiert. Wie in Kapitel 4.7 erläutert wird, sind die Filtereinstellungen von dem verwendeten Layer-2-Protokoll abhängig. Daher werden in der initQoS() die Filterregeln für die Filter der Prio-Klassen, der Best-Effort-Klasse und der einzelnen Kanal-Klassen abhängig vom Adresstyp des verendeten Devices initialisiert. Der Wert des BestEffort-Filters ist fest (VID 3), die Werte der Prio- und Kanal-Filter jedoch sind Variabel (User Prio bzw. Kanal ID). Daher bekommen sie zu Anfang keinen Wert in für .val zugewiesen, sondern in einer eigenen Variablen einen Bit-Shift-Offset, der anzeigt wo in dem 32 Bit breiten Suchmuster, sich der zu suchende Wert befinden muss, um erkannt werden zu können. An der Funktion initQoS() wird nun exemplarisch die Konfiguration von QDiscs, Klassen und Filtern gezeigt. Für die Funktionen addChQoS() und remChQoS() sei nur erwähnt, dass diese die Konfiguration wie in Kapitel 4.7 vorgesehen durchführen. In Listing 5.9 zeigt, wie das für den Filter der Prio-QDisc umgesetzt wurde. Quelltext 5.9: Filter-Selector für Prio-QDisc 1 2 3 4 prSel . sel . nkeys = 1; // Prio - Filter ( User - Priority in VLAN - Tag ) prSel . keys [0]. mask = htonl (0 xE0000000 ) ; prSel . keys [0]. val = 0; prSelOff = 29; // Anzahl der Bit um die geshiftet werden muss Die Listings 5.10, 5.12 und 5.13 zeigen die QDisc-Basis-Konfiguration, wie sie in Kapitel 4.7 beschrieben ist. In Listing 5.10 wird die Prio-QDisc mit acht Bändern parametrisiert und dann konfiguriert. 97 5.7. chQoSContr Kapitel 5. Implementierung Quelltext 5.10: Hinzufügen der Prio-QDisc 1 2 3 4 5 prio_opt . bands = 8; if ( addQDisc (& rth , TC_H_ROOT , 0 x7FFF0000 , prio , ( void *) & prio_opt , devIndex ) < 0 ) { rtnl_close (& rth ) ; return -1; } Danach werden die HTB-QDiscs und der Filter für jede Klasse der Prio-QDisc hinzugefügt (Listing 5.12). Zeile 2 zeigt wie der User-Priority-Wert (userPrio), mit Hilfe des Arrays aus Listing 5.11, auf die Minor-Nummern der Prio-Klassen abgebildet wird. In Zeile 9 wird das Suchmuster für jeden Filter berechnet, indem der UserPriority-Wert, um die in Listing 5.9 definierten Anzahl Bits, nach links verschoben wird. Quelltext 5.11: User-Priority auf Prio-QDisc Mapping 1 2 3 4 5 6 7 8 9 10 11 static unsigned // 6, // 8, // 7, // 5, // 4, // 3, // 2, // 1 // }; char prioMap [8] = { UP Handle 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 Quelltext 5.12: Basis-QDiscs und Filter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for ( userPrio = 0; userPrio < 8; userPrio ++) { parent = 0 x7FFF0000 + prioMap [ userPrio ]; handle = ( userPrio +1) << 16; if ( addQDisc (& rth , parent , handle , htb , ( void *) & htb_opt , devIndex ) < 0) { rtnl_close (& rth ) ; return -1; } prSel . keys [0]. val = htonl ( userPrio << prSelOff ) ; flowid = parent ; if ( addFilter (& rth , 0 x7FFF0000 , 0 , flowid , ETH_P_8021Q , prioMap [ userPrio ] , u32 , & prSel , devIndex ) < 0) { rtnl_close (& rth ) ; return -1; } } Nachdem die Grundkonfiguration von QDiscs und Filtern erfolgt ist, wird noch die Klasse und der Filter für den Best-Effort-Verkehr hinzugefügt (Listing 5.13). In den 98 Kapitel 5. Implementierung 5.7. chQoSContr Zeilen 1 bis 4 wird gezeigt, wie die Parameter der HTB-Klasse nach den Vorgaben in Kapitel 4.7 zugewiesen werden. Die Parameter sind im Einzelnen in Kapitel 3.4.4 beschrieben. Der Quelltext der Funktion get_cell_log() entstammt der Funktion tc_calc_rtable(). Diese ist in der Datei tc core.c implementiert und ist Teil der iproute2-Utility-Suite. Sie wird kopiert und in die zwei einzelnen Funktionen get_cell_log() und calc_rtable() unterteilt, damit deren Funktionalitäten separat verfügbar sind. get_cell_log() generiert den binären Logarithmus der maximalen Tokengröße, in Abhängigkeit von der maximalen Paketgröße, wie in Kapitel 3.4.4.4 beschrieben. Die Funktion calc_rtable() errechnet die Rate-Table für eine HTB-Klasse. Sie wird bei der Generierung von Netlink-Messages für diese Klasse eingesetzt, was in Listing 5.18 gezeigt wird. Abschließend wird noch die SFQ-QDisc und der Filter für den Best-Effort-Verkehr hinzugefügt. Quelltext 5.13: Hinzufügen der Best-Effort-Klasse 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 htb_copt . rate . cell_log = get_cell_log ( mtu + FRM_OVRHD ) ; htb_copt . rate . mpu = mpu ; htb_copt . rate . rate = maxbw ; htb_copt . cbuffer = mtu + FRM_OVRHD ; if ( addClass (& rth , 0 x00020000 , 0 x0002FFFF , htb , ( void *) & htb_copt , devIndex ) < 0) { rtnl_close (& rth ) ; return -1; } sfq_opt . perturb_period = 10; if ( addQDisc (& rth , 0 x0002FFFF , 0 x00000000 , sfq , ( void *) & sfq_opt , devIndex ) < 0) { rtnl_close (& rth ) ; return -1; } if ( addFilter (& rth , 0 x00020000 , 0 x00000000 , 0 x0002FFFF , ETH_P_8021Q , 8 , u32 , ( void *) & beSel , devIndex ) < 0) { rtnl_close (& rth ) ; return -1; } 99 5.7. chQoSContr Kapitel 5. Implementierung qDisc und filter Für die Konfiguration der Queueing Disziplinen, sowie deren Klassen und Filter, wird eine eigene Bibliothek implementiert, die Funktionen zum Hinzufügen und Löschen bietet. Diese Funktionen sind sich im Aufbau sehr ähnlich und werden im Folgenden an der Funktion addQDisc() erläutert (Listings 5.14 und 5.15). Zunächst wird eine Request-Struktur (req) deklariert, die ein Netlink-Message inklusive Netlink-Header, tc-Message und Speicher für Attrtbute reserviert. Die Member der Struktur bekommen Werte, entsprechend der in Kapitel qdiscconf erläuterten Konfigurationsbeschreibung, zugewiesen. Quelltext 5.14: Setzen der QDisc-Basis-Parameter 1 2 3 4 5 6 7 8 9 memset (& req , 0 , sizeof ( req ) ) ; req . nlh . nlmsg_len = NLMSG_LENGTH ( sizeof ( struct tcmsg ) ) ; req . nlh . nlmsg_type = RTM_NEWQDISC ; req . nlh . nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE ; req . tcm . tcm_family req . tcm . tcm_ifindex req . tcm . tcm_handle req . tcm . tcm_parent = = = = AF_UNSPEC ; devIndex ; handle ; parent ; Welche QDisc erzeugt werden soll, wird mit dem Parameter qdisc bestimmt. Aufgrund dessen Wert wird eine QDisc spezifische Netlink-Message generiert. Quelltext 5.15: Generieren der Netlink-Message 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 switch ( qdisc ) { case prio : genPrioNLMsg (& req . nlh , ( struct tc_prio_qopt *) param ) ; break ; case htb : genHTBNLMsg (& req . nlh , ( struct tc_htb_glob *) param ) ; break ; ... case sfq : genSFQNLMsg (& req . nlh , ( struct tc_sfq_qopt *) param ) ; break ; default : return -1; } } Abschließend wird die Message über die Funktion rtnl_talk() aus der Bibliothek 100 Kapitel 5. Implementierung 5.7. chQoSContr libnetlink an den Kernel gesandt. Quelltext 5.16: Versenden der Netlink-Message 1 2 3 4 5 6 if ( rtnl_talk ( rth , & req . nlh , 0 , 0 , NULL , NULL , NULL ) < 0) { return -1; } return 0; } Die Funktionen zum Generieren von Netlink-Messages, übernehmen die für sie definierten Strukturen und fügen je nach verwendeter QDisc Attribute hinzu. Dazu wird die Funktion addattr_ll() genutzt, die ebenfalls Bestandteil der libnetlinkBibliothek ist. In Listing 5.17 wird gezeigt, wie die Attribute für die SFQ-QDisc in der Funktion genSFQNLMsg() hinzugefügt werden. Quelltext 5.17: Hinzufügen von Attributen 1 2 3 4 5 int genSFQNLMsg ( struct nlmsghdr *n , struct tc_sfq_qopt * opt ) { addattr_l (n , 1024 , TCA_KIND , " sfq " , 4) ; addattr_l (n , 1024 , TCA_OPTIONS , opt , sizeof ( struct tc_sfq_qopt ) ) ; return 0; } Die Generierung von Attributen der HTB-Klasse gestaltet sich etwas komplizierter. Für sie müssen noch die beiden Rate-Tabellen erzeugt werden und die angegebenen burst und cburst-Parameter in Sendezeiten umgewandelt werden, wie es im Kapitel 3.4.4 beschrieben wurde. Dies geschieht mit der Funktion calc_rtable(), die, wie schon vorher im Abschnitt erwähnt, aus der Funktion tc_calc_rtable ausgeschnitten wurde. Ist der Parameter buffer kleiner als der berechnete Mindestwert, wird der Mindestwert zugewiesen. Die Byte-Werte für buffer und cbuffer werden mit der Funktion tc_calc_xmittime () aus der iproute2-Utility-Suite in die Sendezeit umgerechnet. Anschließend werden alle Attribute an die Netlink-Message angehängt. Quelltext 5.18: Generieren einer Netlink-Message für eine HTB-Klasse 1 2 3 4 5 6 7 8 9 int genHTBClassNLMsg ( struct nlmsghdr *n , struct tc_htb_opt * opt ) { __u32 rtab [256] , ctab [256]; unsigned buffer =0 , cbuffer =0; struct rtattr * tail ; if (! opt - > ceil . rate ) opt - > ceil = opt - > rate ; buffer = opt - > rate . rate / get_hz () ; // min ; 101 5.7. chQoSContr 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Kapitel 5. Implementierung if ( opt - > buffer > buffer ) buffer = opt - > buffer ; cbuffer = opt - > cbuffer +50; calc_rtable (& opt - > rate , rtab ) ; opt - > buffer = tc_calc_xmittime ( opt - > rate . rate , buffer ) ; calc_rtable (& opt - > ceil , ctab ) ; opt - > cbuffer = tc_calc_xmittime ( opt - > ceil . rate , cbuffer ) ; addattr_l (n , 1024 , TCA_KIND , " htb " , 3) ; tail = ( struct rtattr *) ((( void *) n ) + NLMSG_ALIGN (n - > nlmsg_len ) ) ; addattr_l (n , 1024 , TCA_OPTIONS , NULL , 0) ; addattr_l (n , 2024 , TCA_HTB_PARMS , opt , sizeof ( struct tc_htb_opt ) ) ; addattr_l (n , 3024 , TCA_HTB_RTAB , rtab , 1024) ; addattr_l (n , 4024 , TCA_HTB_CTAB , ctab , 1024) ; tail - > rta_len = ((( void *) n ) + NLMSG_ALIGN (n - > nlmsg_len ) ) - ( void *) tail ; return 0; } Datei Zeilen (ohne Kommentare) chQosContr.h 10 chQoSContr.c 341 qDisc.h 21 qDisc.c 208 filter.h 13 filter.c 108 qFIFO.h 7 qFIFO.c 19 qSFQ.h 6 qSFQ.c 13 qPrio.h 6 qPrio.c 16 qHTB.h 7 qHTB.c 78 fU32.h 11 fU32.c 36 Gesamt 900 Tabelle 5.6: Implementierungsaufwand chQoSContr 102 Kapitel 5. Implementierung 5.8 5.8. Testapplikation Testapplikation Zum Testen der Basisfunktionalität der Bibliothek steht die menügeführte Applikation channelapp zur Verfügung. Mit ihr lassen sich die Bibliothek und Kanäle öffnen und schließen, sowie einzelne Pakete versenden und empfangen. Durch mehrere Instanzen ist der wechselseitige Ausschluss für den Kanalzugriff validierbar. Um die Konfiguration der Queueing-Disziplinen zu prüfen, kann das Tool TC eingesetzt werden. Im Folgenden sollen die wichtigsten Befehle zur Überprüfung der Konfiguration genannt werden: 1 2 3 # tc [ - s ] qdisc sh [ dev < devname >] # tc [ - s ] class sh dev < devname > # tc filter sh dev < devname > [ parent < handle >] Der erste Befehl zeigt die Liste der angelegten QDiscs an. Mit der Option ’-s’ werden noch statistische Informationen, wie die Anzahl Pakete und Bytes, die die QDisc passiert haben, angezeigt. Der zweite Befehl zeigt die erzeugten Klassen an. Hier muss darauf geachtet werden, dass der Device-Name spezifiziert wird, da sonst keine Ausgabe erfolgt. Mit dem letzten Befehl werden die angelegten Filter angezeigt. Wie bei den Klassen, muss auch hier der Device-Name angegeben werden. Der Befehl zeigt immer nur die Filter an, die an einem Knoten sitzen. Wird der Elternknoten nicht explizit angegeben, so werden die Filter an TC_H_ROOT ausgegeben. Implementierungsaufwand Datei channelapp.c Zeilen (ohne Kommentare) 127 Tabelle 5.7: Implementierungsaufwand Testapplikation 103 5.10. Kompilierung und Installation 5.9 Kapitel 5. Implementierung Gesamtaufwand Modul Zeilen (ohne Kommentare) chConfRead 360 chContr 648 chDescr 76 chAvailContr 275 chDev 722 chQoSContr 900 4 209 chMsr channelapp 127 5 msrSend 87 msrRecv6 75 Gesamt 3270 Tabelle 5.8: Implementierungsaufwand chQoSContr 5.10 Kompilierung und Installation Auf der CD ist ein tar-Archiv der Bibliothek enthalten, dem ein Makefile beiliegt. Im Makefile sind unter der Option LIBCFLAGS drei #define-Schalter, die den Umfang des zu kompilierenden Quellcodes steuern. _EMSG_ sorgt dafür, dass für jeden Fehler eine Ausgabe auf stderr erscheint, während _DEBUG_ eine Ausgabe von DebugInformationen auslöst, die zum Testen der Bibliothek genutzt werden. Der Schalter _CH_TEST_ entscheidet, ob die Instrumentierung des Quellcodes für die Bewertung mit in die Bibliothek eingebunden wird. Durch Ausführung von make wird die Bibliothek kompiliert. make clean entfernt alle ausführbaren Dateien und den Objektcode. Um die Bibliothek in ein Programm einzubinden, wird die Headerdatei channel.h und der Pfad zu den Bibliotheksdateien libchannel.so, libchannel.so.0 und libchannel.so.0.0 benötigt. Dieser muss mit dem Befehl export LD LIBRARY PATH=<path> bekannt gemacht werden. Außerdem müssen bei der Kompilierung die Optionen -L <path> und -lchannel angehängt werden. 4 s. Kapitel 6.3 s. Kapitel 6.5 6 s. Kapitel 6.5 5 104 Kapitel 5. Implementierung 5.11. Aufgetretene Probleme Bei der Verwendung der Bibliothek ist unbedingt darauf zu achten, dass vor dem Beenden der Applikation die Funktion closeChannels() aufgerufen wird. Geschieht dies nicht, können Betriebsmittel, wie Semaphoren, nicht freigegeben und die Einstellungen an den Queueing-Disziplinen und dem Netzwerk-Device nicht rückgängig gemacht werden. Aus diesem Grund sollten entsprechende Signale abgefangen und die Bibliothek geschlossen werden. 5.11 Aufgetretene Probleme Während der Implementierung sind einen Reihe von Problemen aufgetreten, die auf Eigenheiten im Betriebssystem zurückzuführen sind. Diese sollen hier mit entsprechender Lösung, falls vorhanden, vorgestellt werden. Laut der manpage packet(7) kann ein Packet-Socket, mit der Hilfe der Optionen PACKET_ADD_MEMBERSHIP und PACKET_DROP_MEMBERSHIP, explizit an eine MulticastAdresse gebunden bzw. von einer Multicast-Adresse entbunden werden. Beim Testen der Applikation werden jedoch an einem Kanalendpunkt Nachrichten eines anderen Kanals empfangen, obwohl dessen Multicast-Adresse von keinem Kanal auf der Empfängerseite konfiguriert wurde. Die Ursache liegt in der verwendeten Netzwerkkarte und deren Linux-Treiber. Die Unterstützung von Multicast-Filterung ist von Treiber zu Treiber unterschiedlich. Diesem Problem wird aus Zeitgründen und der Tatsache, dass dessen Lösung der zentralen Kernproblemstellung der Arbeit nicht zuträglich ist, nicht weiter nachgegangen. Ein weiteres Problem ist, dass bei Sendungen von Paketen in kurzen Intervallen Kernel-Panics ausgelöst werden können. Dies liegt daran, dass niederpriore Prozesse unterbrechbar sind, selbst wenn sie sich gerade, durch Ausführung eines Systemcalls, im Kernel-Adressraum aufhalten. Das 802.1Q-Modul unter Linux ist für die Unterbrechungen nicht ausgelegt. Eine Lösung wäre es, für Prozesse, die Kanäle nutzen, einen Real-Time Scheduler einzusetzen und dem Prozess eine höhere Priorität zu verleihen. Dadurch würde der Prozess im Kern-Modus nicht mehr unterbrochen werden. Da jedoch Best-Effort-Applikationen auch über VLANs versendet werden, kann diese Option nur eingesetzt werden, wenn alle Applikationen, die Netwerkressourcen benötigen, einen höhere Priorität zugeordnet bekommen. Eine andere Lösung ist die Kernel-Option CONFIG_PREEMPT, wie in Abschnitt 5.1 angegeben, auszuschalten. Somit können niederpriore Prozesse nicht mehr im Kernel- 105 5.11. Aufgetretene Probleme Kapitel 5. Implementierung Modus unterbrochen werden, was bedeutet, dass leicht erhöhte Latenzen für die Aktivierung von hochprioren Prozessen auftreten können. In [Hub03] ist angegeben, dass Filter Pakete an Klassen über mehrere Level hinweg weiterreichen können. Dies ist auch in den meisten Anleitungen im Internet angegeben. Auf den in Abschnitt 5.1 angegeben Rechnern läßt sich das nicht bestätigen. Werden Filter an einer QDisc angebracht, deren Flow-ID eine Major-Nummer aufwiest, die nicht der Major-Nummer der Eltern-QDisc des Filters entspricht, treten die Pakete, die auf die Filter-Regel zutreffen, nicht in den Statistiken der entsprechenden Blatt-QDisc auf. Erst wenn für jede klassenbehaftete QDisc eine eigene Filter-Instanz erzeugt wird, kommen die Pakete bei der Blatt-QDisc an. 106 Kapitel 6 Bewertung In diesem Abschnitt wird eine Bewertung der gewählten Konfiguration der Queueing Disziplinen anhand von Messungen vorgenommen. 6.1 Messumgebung Die Messungen werden mit den in 5.1 beschriebenen Rechnern, in einem von einem Cisco Catalyst 2950 Switch vermittelten Netz durchgeführt. Drei dieser Rechner sind in Sterntopologie um den Switch angeordnet, wobei einer davon den Datenverkehr auf dem Switch mit dem Programm Tethereal aufzeichnet. Der Switch verfügt über vier Ausgangsqueues pro Port, die über ein Strict-PriorityQueueing bedient werden. Die Konfiguration des Switches beinhaltet die Einrichtung der beteiligten Ports als Trunk-Ports für VLAN 1-3, wobei VLAN 1 alle EthernetFrames bediente, die kein VLAN-Tag enthalten. Weiterhin wurden für die MulticastAdressen, der an der Messung beteiligten Kanäle, statische Einträge in der MACAdress-Tabelle des Switches vorgenommen. Dis Konfigurationsdatei des Switches liegt im Verzeichniss Messungen/Switchkonfiguration der CD. 6.2 Messgrößen Gemessen wurden sowohl die Ende-zu-Ende-Propagationszeit zwischen zwei Rechnern, als auch die Verweilzeit eines Pakets innerhalb der Queueing Disziplinen. Um 107 6.3. Instrumentierung Kapitel 6. Bewertung eine Korrelation der Daten zu ermöglichen wird auch die Zeit gemessen, die ein Paket benötigt, um vom Eintritt in den Systemcall zur enqueue()-Funktion der QDisc zu gelangen. 6.3 Instrumentierung Die Messung wird durch eigens implementierte send()- und recv()-Methode am chDescr-Objekt des zu messenden Kanals angestoßen. Diese sind instrumentiert um zwei verschiedene Messungen vorzunehmen, die anschließend korreliert werden. Die Methoden testModeSend() und testModeRecv() sind im Modul chMsr definiert. 6.3.1 Ende-zu-Ende Propagation Um die Ende-zu-Ende Propagation und den Jitter bei Paketankunft zu messen, wurde eine Bibliothek von Dipl. Inf. Bernharg Gelling geschrieben, deren Funktion 8Bit Information aufnimmt und diese am Parallel-Port des Testrechners ausgibt. Diese Informationen wurden von einem Logic-Analyzer aufgezeichnet und anschließend in Excel weiterverarbeitet. Von den acht zur Verfügung stehenden Bit, wurden drei genutzt um die Kanal-ID zu codieren und die restlichen fünf, für die Sequenznummer der Pakete verwendet. Die send()-Methode wurde direkt vor dem Aufruf des sendto()-Systemcalls mit der Funktion zum Setzen des Parallelports instrumentiert, während die recv()-Methode direkt nach dem Aufruf des recv()-Systemcalls instrumentiert wurde. 6.3.2 Delay durch sendto-Systemcall und QDisc Für die interne Delay-Messung wurde das Time-Stamp-Register der CPU genutzt. Für diesen Zweck wird das Messmodul chMsr implementiert, mit dem verschiedene Punkte im Betriebssystem instrumentiert werden. Um den Delay innerhalb des Systemcalls und der QDisc zu messen, wird der PaketPfad an drei Punkten instrumentiert: In der send-Methode, am QDisc-Eingang und am QDisc-Ausgang. An jedem dieser Punkte wird der Wert aus dem Time Stamp Register der CPU ausgelesen und im Paket eingefügt. Dazu wird in das Paket eine Struktur eingefügt, die aus einem Header und drei 64-Bit Zeitstempel besteht (Listing 6.1). 108 Kapitel 6. Bewertung 6.3. Instrumentierung Quelltext 6.1: Die 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 msrFrm-Struktur union msrPnt { __u32 part [2]; __u64 value ; }; struct msrHdr { __u16 mark ; __u16 chID ; __u32 seq ; __u32 cpu ; __u8 wPos ; }; struct msrFrm { struct msrHdr hdr ; union msrPnt tsc [ MAX_MSR_PNTS ]; char buf [ MAX_MSR_PAYL ]; }; Der Header beginnt mit dem Erkennungsmuster 0x7FFE, über das festgestellt werden kann, ob es sich um ein Testpaket handelt. Danach folgen Informationen über die Kanal-ID, die Sequenznummer des Pakets und die Taktfrequenz der CPU. Letzteres ist nötig, da die Zeitinformationen auf einem anderen Rechner ausgewertet werden, der evtl. eine andere Taktfrequenz besitzt. Headerinformationen werden in der send()-Methode gesetzt, wo auch der erste Zeitstempel, kurz vor dem Versenden des Pakets, gesetzt wird (Listing 6.2). Quelltext 6.2: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 testModeSend()-Funktion int testModeSend ( struct chDescr * channel , void * buf , size_t size ) { struct msrFrm frame ; int sentData ; memset (& frame , ( unsigned ) 0 , sizeof ( frame ) ) ; frame . hdr . mark frame . hdr . seq frame . hdr . chID frame . hdr . cpu = = = = htons (0 x7FFE ) ; htonl ( channel - > seq ) ; htons ( channel - > id ) ; htonl ( cpukHz ) ; memcpy (( void *) & frame . buf , buf , size < MAX_MSR_PAYL ? size : MAX_MSR_PAYL ) ; addMsrPoint (& frame ) ; llaappch_write ( channel - > id , ( unsigned short ) channel - > seq ) ; sentData = sendto ( channel - > sock , ( void *) & frame , size , 0 , channel - > sendAddr , channel - > addrLen ) ; channel - > seq ++; return sentData ; } 109 6.3. Instrumentierung Kapitel 6. Bewertung Im Kern wird vor dem Aufruf der enqueue()-Funktion der QDisc ermittlet, ob ein Frame mit einem VLAN-Tag das Muster für ein Testpaket enthält. Ist das der Fall, wird ein Zeitstempel genommen und an der nächsten freien Stelle im Paket eingefügt (Listing 6.3). Quelltext 6.3: Instrumentierung an der 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 enqueue()-Funktion spin_lock_bh (& dev - > queue_lock ) ; /* Instrumetation for ChannelDelay Measurement */ if ( skb - > protocol == htons (0 x8100 ) ) { struct msrFrm * frm ; __u32 upper , lower ; frm = ( struct msrFrm *) ( skb - > data + ( sizeof ( struct ethhdr ) + 4) ) ; if ( frm - > hdr . mark == htons (0 x7FFE ) && frm - > hdr . wPos < MAX_MSR_PNTS ) { rdtsc ( lower , upper ) ; frm - > tsc [ frm - > hdr . wPos ]. part [ UPPER ] = htonl ( upper ) ; frm - > tsc [ frm - > hdr . wPos ]. part [ LOWER ] = htonl ( lower ) ; frm - > hdr . wPos ++; } } /* End of Instrumentation */ rc = q - > enqueue ( skb , q ) ; qdisc_run ( dev ) ; spin_unlock_bh (& dev - > queue_lock ) ; Der letzte Zeitstempel wird nach dem Aufruf der Funktion dequeue() an der QDisc genommen und in das zurückgelieferte Paket eingefügt, sofern es sich um ein Testpaket handelt. Die Pakete werden von dem Netzwerk-Analyzer tethereal aufgezeichnet und anschließend mit zwei Perl-Skripten in eine von Excel lesbare Form gebracht. Damit werden Diagramme zur Visualisierung des Delays erstellt. 110 Kapitel 6. Bewertung 6.4. Lastmodell Instrumentierungsaufwand Datei Zeilen (ohne Kommentare) chMsr.h 37 chMsr.c 148 dev.c 1 12 sch generic.c2 12 Gesamt 209 Tabelle 6.1: Implementierungsaufwand Instrumentierung 6.4 Lastmodell Das Lastmodell ist dahingehend konzipiert, dass die Dauerlast unterhalb der Linkkapazität des Netzwerkinterface (R = 12, 5Bytes/µs) bleibt. Damit können Stausituationen, die durch Bursts entstehen, wieder abgebaut werden und es entsteht kein dauerhaftes Aufstauen von Paketen. Exemplarisch wird hierzu die isochrone Propagation von Steuerdaten aufgegriffen, wie sie im Use-Case Steuern und Regeln beschrieben wurde. Im Folgenden werden drei Lastkonfigurationen betrachtet (Tabelle 6.2), die sich in Paketgröße3 , und Propagationsintervall unterscheiden. Jede Konfiguration besteht aus drei Kanälen, wobei zwei Kanäle eine Priorität von vier und ein Kanal eine Priorität von sieben aufweist. Der Kanal mit der hohen Priorität besitzt außerdem ein größeres Propagationsintervall. Lastkonf. Paketgröße Intervall Prio 4 Intervall Prio 7 1 200Byte 50µs 100µs 2 500Byte 100µs 500µs 3 1496Byte 300µs 900µs Tabelle 6.2: Lastkonfigurationen Zur Ermittlung der verbrauchten Bandbreite der einzelnen Kanäle, wurde die FrameGröße der Lastkonfigurationen inklusive Präambel, Header, CRC und IFG zu Grun1 Kernel-Quelltext /usr/src/linux/net/core/dev.c Kernel-Quelltext /usr/src/linux/net/sched/sch generic.c 3 Paketgröße bedeutet hier reine Payload 2 111 6.4. Lastmodell Kapitel 6. Bewertung de gelegt (Tabelle 6.3). Lastkonf. Framegröße Bandbreite Prio 4 Bandbreite Prio 7 Bandbreite gesamt 1 242Byte 4, 48Byte/µs 2, 24Byte/µs 12, 1Byte/µs 2 542Byte 5, 24Byte/µs 1, 084Byte/µs 11, 924Byte/µs 3 1538Byte 5, 125Byte/µs 1, 71Byte/µs 11, 96Byte/µs Tabelle 6.3: Genutzte Bandbreite der Lastkonfigurationen Aufgrund der gewählten Lastkonfigurationen wurden die maximal auftretenden Delays mit der Formel 3.11 aus Kapitel 3.3.3.1 voraus berechnet. Der konstante DelayParameter T der Servicekurve wurde durch eine Messung des Delays in der QDisc ermittelt. Dazu wurden 10000 Pakete in einem Intervall auf einem Kanal gesendet, der keinen Stau verursachen würde. Somit konnte der Durchschnitts-Delay in der QDisc ermittelt werden. Dieser Beträgt 0.855µs und wird als Grundlage für die Berechnung der Werte in Tabelle 6.4 verwendet. Lastkonf. Delay Prio 4 Delay Prio 7 1 58, 935µs 20, 215µs 2 130, 935µs 44, 215µs 3 369, 975 123, 895µs Tabelle 6.4: Maximaler Delay der Kanäle in der Queueing Disziplin Um Vergleichsmöglichkeiten zu haben wurde das Lastmodell außer mit der für die Implementierung vorgesehenen, noch mit zwei weiteren Queueing-Disziplin-Konfigurationen gemessen: eine Prio-QDisc ohne Bandbreitenbegrenzer und eine reine FIFO-QDisc. Ein weiteres Messzenario untersucht das Verhalten eines Kanals unter massiver Störlast durch Best-Effort-Verkehr. Hierzu wird je ein einzelner Kanal mit der Priorität 7 bzw. 1 und einem Sendeintervall von 1000µs gemessen. Jeder Kanal wird einmal mit einem je einmal mit einer Störlast und einmal ohne gemessen. Die Störlast wird durch das Kommando ping -f -s 500 ausgelöst. 112 Kapitel 6. Bewertung 6.5 6.5. Lastquelle Lastquelle Bei der Implementierung der Lastquelle, ist absolute Gleichzeitigkeit beim Senden auf den einzelnen Kanäle nicht möglich. Um eine möglichst gute Annäherung zu erreichen, werden die Sendeoperationen auf den Kanälen direkt aufeinander folgend durchgeführt. Dabei wird auf dem Kanal mit der höchsten Priorität zuletzt gesendet, um ein Zurückhalten von Paketen niedriger Priorität in der Queueing Disziplin zu provozieren. Die Lastquellenapplikation msrSend und das instrumentierte Gegenstück msrRecv wird speziell für die Messungen des in Abschnitt 6.4 beschriebenen Lastmodells implementiert. Als Argumente nimmt msrSend die Payloadgröße, das Sendeintervall, die Anzahl der Iterationen und einen Divisor entgegen. Mit dem Divisor wird angegeben, nach wieviel Iterationsschritten jeweils ein hochpriores Paket versendet werden soll. Der Implementierungsaufwand wird in Kapitel 5.9 angegeben. 6.6 Messergebnisse Die einzelnen Diagramme zu den Messungen sind in Anhang B aufgeführt. Der Grund, dafür, dass einige Diagramme kürzer sind als andere, obwohl die gleich Anzahl Pakete versendet wurden, ist teilweise bei Tethereal zu suchen, dass zeitweise zu langsam ist um alle Pakete zu empfangen und zu verarbeiten. Ein anderer Grund wird im Folgenden besprochen. Es ist deutlich zu sehen, dass die Werte stark von den Erwartungen abweichen. Außerdem ist zu erkennen, dass die Ursache für die Abweichungen in den QueueingDisziplinen zu suchen ist. Dort werden die Pakete aufgestaut, was wiederum zu Paketverlust durch Überlaufen der Blatt-QDisc, verursacht. Im Vergleich zu anderen QDisc-Konfigurationen fällt auf, dass die großen Delay-Werte ausschließlich in der Konfiguration, die HTB nutzt, auftreten. Die durchschnittlichen und maximalen Delay-Werte werden in den folgenden Tabellen zusammengefasst. Lastkonf. Delay Prio 4 Delay Prio 7 1 24940, 12µs 1549, 43µs 2 15149, 60µs 0, 098µs 3 2622, 887µs 0, 218µs 113 6.6. Messergebnisse Kapitel 6. Bewertung Tabelle 6.5: Mittlerer Delay für Prio/HTB-Konfiguration Lastkonf. Delay Prio 4 Delay Prio 7 1 85431, 54µs 30515, 08µs 2 44779, 55µs 7, 67µs 3 9054, 64µs 12, 15µs Tabelle 6.6: Konfiguration Maximaler Delay für Prio/HTB- Lastkonf. Delay Prio 4 Delay Prio 7 1 0, 105µs 0, 014µs 2 0, 097µs 0, 012µs 3 0, 094µs 0, 020µs Tabelle 6.7: Mittlerer Delay für Prio-Konfiguration Lastkonf. Delay Prio 4 Delay Prio 7 1 3, 43µs 0, 33µs 2 6, 917µs 0, 94µs 3 6, 79µs 1, 018µs Tabelle 6.8: Maximaler Delay für Prio-Konfiguration Lastkonf. Delay Prio 4 Delay Prio 7 1 0, 075µs 0, 076µs 2 0, 063µs 0, 065µs 3 0, 065µs 0, 065µs Tabelle 6.9: Mittlerer Delay für FIFO-Konfiguration Lastkonf. Delay Prio 4 Delay Prio 7 1 0, 36µs 6, 117µs 114 Kapitel 6. Bewertung 6.7. Fazit 2 0, 357µs 0, 0676µs 3 6, 793µs 1, 018µs Tabelle 6.10: Maximaler Delay für FIFO-Konfiguration Da die Ursache für den hohen Delay offensichtlich in der HTB-QDisc liegt, werden weitere Messungen mit veränderten Parametern durchgeführt. Der cbufferParameter scheint zur Variation geeignet, da er genutzt wird, um den Burst auf eine Paketgröße zu beschränken, und damit den Mindestwert für den Parameter buffer unterschreitet. Die Diagramme B.19 bis B.21 zeigen das Ergebnis der Messungen in der QDisc für einen cbuffer-Parameter, der um 1000 Byte erhöht ist oder 1000 bzw. 500 Byte unter dem Minimum liegt. Aus den Ergebnissen ist abzulesen, dass der cbufferParameter durchaus unter dem Minimum liegen kann, ohne dass ein erhöhter Delay feststellbar wäre. Jedoch scheint es davon abhängig zu sein, wie weit sich der cbuffer vom Minimum entfernt. Da der Minimalwert für buffer von der Rate und dem Timer abhängig ist (s. Kapitel 3.4.4.4), kann dem Problem nicht mit einem einfachen Modifikator abgeholfen werden. Dies wird in Diagramm B.20 gezeigt, wo ein Modifikator von -1000 keinen Einfluss auf die beiden Kanäle mit der höheren Bandbreite hat, aber auf den Kanal mit wenig Bandbreite, dessen cburst-Parameter unter die Paketgröße fällt. Eine weitere Auffälligkeit bei den Messungen ist, dass die Delay-Werte für alle anderen QDisc-Konfigurationen unter einer Microsekunde liegen. Eigentlich sollte zumindest der dritte Kanal durch den Stau, den der Burst verursacht um ein paar Microsekunden verzögert werden. Erklärbar ist dies, wenn die Hardware ihrerseits eine Queue enthält, die größer ist, als ein Paket. In diesem Fall kann es bei so kurzen Bursts, wie die im Lastmodell nicht zu messbaren Stausituationen in der QDisc kommen. 6.7 Fazit Es wurden stark von der Erwartung abweichende Werte gemessen, die jedoch daraufhin deuten, dass der Einsatz der HTB-QDisc als präzisen Bandbreitenbegrenzer, in der jetzigen Implementation nicht für den Automatisierungskontext geeignet ist. 115 6.7. Fazit Kapitel 6. Bewertung Dies ist jedoch bisher nur eine Vermutung, die mit weiteren Messungen verifiziert werden muss. Aus Zeitgründen können diese Messungen nicht im Rahmen dieser Arbeit durchgeführt werden. 116 Kapitel 7 Zusammenfassung und Ausblick In dieser Arbeit wurde gezeigt, dass eine Integration von Ethernet in die Automatisierungsumgebung ohne proprietäre Hardware durchaus möglich ist. Switches machen das Medium deterministisch, so dass keine Kollisionen mehr auftreten können. Um einen priorisierten Datenverkehr zu realisieren ist ein Modell erstellt worden, das QoS-behaftete Kanäle vorsieht. Die QoS-Eigenschaften sollten durch die vom Linux-Kernel bereitgestellten Queueing-Disziplinen durchgesetzt werden. Die Beschreibung der Queueing-Disziplinen, als Implementierungen von Paket-Scheduling-Algorithmen, deutete darauf hin, dass diese für den Einsatz in der verteilten Automation geeignet sind. Die Queueing-Disziplinen wurden entsprechend dem, in der Analyse verwandten Ankunfts- und Servicekurvenmodell, konfiguriert. Durchgeführte Messungen wichen jedoch stark von den erwarteten Ergebnissen ab. Die Ergebnisse deuten daraufhin, dass die Queueing-Disziplinen, speziell gezeigt an der HTB, in ihrer Implementierungsform im Linux-Kernel noch nicht ausgereift sind, um in so präzisen Umgebungen eingesetzt zu werden, wie sie in der verteilten Automation vorkommen. Als eine Weiterentwicklung dieser Arbeit würde sich die Implementierung präziser Paket-Scheduling-Algorithmen anbieten. Diese Implementierung könnte dann in Zusammenhang mit einer Echtzeiterweiterung, wie RTAI, erfolgen, was eine Portierung des Kanalkonzepts in eine harte Echtzeitumgebung ermöglichen würde. Eine weitere Weiterentwicklung, könnte ein Programm sein, dass die lokalen QoSParameter eines Kanals aufgrund von Wissen über Topologie, Hardware und geplanten Netzwerkverkehr berechnet. Dies könnte so weit gehen, dass auf jedem Rechner 117 Kapitel 7. Zusammenfassung und Ausblick ein Deamon läuft, der die QoS-Kapazitäten der gesamten Topologie kennt und die lokalen QoS-Parameter dynamisch errechnet und mit anderen Deamons aushandelt. 118 Kapitel 8 Literaturverzeichnis [BC02] Daniel P. Bovet and Marco Cesati. Understanding the Linux Kernel. O’Reilly, 2. edition, Dezember 2002. [BT04] Jean-Yves Le Boudec and Patrick Thiran. Network Calculus, A Theory of Deterministic Queueing Systems for the Internet. Springer Verlag, 1. edition, 2004. [eH] PROFIBUS Nutzerorganisation e.V. (Hrsg.). Profinet systembeschreibung. Technical report. [Fel00] Max Felser. Ethernet als Feldbus?, Kommunikationsmodelle für Industrielle Netzwerke. Hochschule für Technik und Informatik, Bern, Mai 2000. http://prof.hti.bfh.ch/index.php?id=fsm1. [Fur03] Frank J. Furrer. Industrieautomation mit Ethernet-TCP/IP und WebTechnologie. Hüthig Verlag Heidelberg, 3. edition, 2003. [Hal96] Fred Halsall. Data Communications, Computer Networks and Open Systems. Addison-Wesley Publishing Company Inc., 4. edition, 1996. [Hub03] Bert Hubert. Linux Advanced Routing and Traffic Control, September 2003. http://www.lartc.org. [IEE] Institute for Electrical and Electronics Engineers Inc. IEEE 1588, Standard for precision Clock Synchronization Protocol for Network Measurement and Control Systems. http://www.ieee.org. 119 Kapitel 8. Literaturverzeichnis [IEE01] Institute for Electrical and Electronics Engineers Inc. 802, IEEE Standard for Local and Metropolitan Area Networks, Overview and Architecture, März 2001. http://www.ieee.org. [IEE02] Institute for Electrical and Electronics Engineers Inc. 802.2, IEEE Standard for Information technology- Telecommunications and information exchange between systems- Local and metropolitan area networks-specific requirements, Carrier sense multiple access with collision detection (CSMA/CD) access method and physical layer specifications, März 2002. http://www.ieee.org. [IEE03] Institute for Electrical and Electronics Engineers Inc. 802.1Q, IEEE Standard for Local and Metropolitan Area Networks, Virtual Bridged Local Area Networks, Mai 2003. http://www.ieee.org. [IEE04] Institute for Electrical and Electronics Engineers Inc. 802.1D, IEEE Standard for Local and Metropolitan Area Networks, Media Access Control(MAC) Bridges, Juni 2004. http://www.ieee.org. [ipr] iproute2 utitlity suite. [Jas02] Jürgen Jaspereite. Leistungsbewertung eines Netzwerkes mit Class-ofService Unterstützung für Prozessnahe Echtzeitkommunikation. PhD thesis, Otto-von-Guericke Universität Magdeburg, Oktober 2002. [JL04] Herrmann Haertig Jork Loeser. Low-Latency Hard Real-Time Communication over Switched Ethernet. Technische Universität, Dresden, Juni 2004. http://os.inf.tu-dresden.de/papers ps/loeser ecrts2004.pdf. [Kop97] Hermann Kopetz. Real-Time Systems, Design Principles for Distributed Embedded Applications. 1997. [KS04] Jan Kiszka and Robert Schwebel. Alternative: Rtnet. A&D Newsletter, publish-industry Verlag GmbH, 2004. [Oes01] Bernd Oestereich. Objektorientierte Softwareentwicklung: Analyse und Design mit der Unified Modeling Language. 2001. [Tan02] Andrew S. Tanenbaum. Computernetzwerke. Pearson Studium, 3. edition, 2002. 120 Kapitel 8. Literaturverzeichnis [WPR+ 02] Klaus Wehrle, Frank Pählke, Hartmut Ritter, Daniel Müller, and Marc Bechler. Linux-Netzwerkarchitektur, Design und Implementierung von Netzwerkprotokollen im Linux-Kern. Addison-Wesley Verlag, 1. edition, 2002. 121 Kapitel 8. Literaturverzeichnis 122 Anhang A Prioritätentabelle Priorität User Prio Prio Class ID Filter Prio HTB Handle hoch 7 6 5 4 3 0 2 1 x:1 x:2 x:3 x:4 x:5 x:6 x:7 x:8 0 1 2 3 4 5 6 7 8:0 7:0 6:0 5:0 4:0 1:0 3:0 2:0 niedrig Tabelle A.1: Übersicht über alle prioritätsbezogenen Werte 123 Anhang A. Prioritätentabelle 124 Anhang B Diagramme B.1 HTB/Prio QDisc Abbildung B.1: Delay in QDisc für 200Byte, 2 Kanäle Prio 4 50µs und 1 Kanal Prio 7 100µs 125 B.1. HTB/Prio QDisc Anhang B. Diagramme Abbildung B.2: Delay in QDisc für 500Byte, 2 Kanäle Prio 4 100µs und 1 Kanal Prio 7 500µs Abbildung B.3: Delay in QDisc für 1496Byte, 2 Kanäle Prio 4 300µs und 1 Kanal Prio 7 900µs 126 Anhang B. Diagramme B.2 B.2. HTB/Prio End-to-End HTB/Prio End-to-End Abbildung B.4: End-to-End Propagation Time für 200Byte, 2 Kanäle Prio 4 50µs und 1 Kanal Prio 7 100µs 127 B.2. HTB/Prio End-to-End Anhang B. Diagramme Abbildung B.5: End-to-End Propagation Time für 500Byte, 2 Kanäle Prio 4 100µs und 1 Kanal Prio 7 500µs Abbildung B.6: End-to-End Propagation Time für 1496Byte, 2 Kanäle Prio 4 300µs und 1 Kanal Prio 7 900µs 128 Anhang B. Diagramme B.3 B.3. Prio QDisc Prio QDisc Abbildung B.7: Delay in QDisc für 200Byte, 2 Kanäle Prio 4 50µs und 1 Kanal Prio 7 100µs Abbildung B.8: Delay in QDisc für 500Byte, 2 Kanäle Prio 4 100µs und 1 Kanal Prio 7 500µs 129 B.4. Prio End-to-End Anhang B. Diagramme Abbildung B.9: Delay in QDisc für 1496Byte, 2 Kanäle Prio 4 300µs und 1 Kanal Prio 7 900µs B.4 Prio End-to-End Abbildung B.10: End-to-End Propagation Time für 200Byte, 2 Kanäle Prio 4 50µs und 1 Kanal Prio 7 100µs 130 Anhang B. Diagramme B.4. Prio End-to-End Abbildung B.11: End-to-End Propagation Time für 500Byte, 2 Kanäle Prio 4 100µs und 1 Kanal Prio 7 500µs Abbildung B.12: End-to-End Propagation Time für 1496Byte, 2 Kanäle Prio 4 300µs und 1 Kanal Prio 7 900µs 131 B.5. FIFO QDisc B.5 Anhang B. Diagramme FIFO QDisc Abbildung B.13: Delay in QDisc für 200Byte, 2 Kanäle Prio 4 50µs und 1 Kanal Prio 7 100µs Abbildung B.14: Delay in QDisc für 500Byte, 2 Kanäle Prio 4 100µs und 1 Kanal Prio 7 500µs 132 Anhang B. Diagramme B.6. FIFO End-to-End Abbildung B.15: Delay in QDisc für 1496Byte, 2 Kanäle Prio 4 300µs und 1 Kanal Prio 7 900µs B.6 FIFO End-to-End Abbildung B.16: End-to-End Propagation Time für 200Byte, 2 Kanäle Prio 4 50µs und 1 Kanal Prio 7 100µs 133 B.6. FIFO End-to-End Anhang B. Diagramme Abbildung B.17: End-to-End Propagation Time für 500Byte, 2 Kanäle Prio 4 100µs und 1 Kanal Prio 7 500µs Abbildung B.18: End-to-End Propagation Time für 1496Byte, 2 Kanäle Prio 4 300µs und 1 Kanal Prio 7 900µs 134 Anhang B. Diagramme B.7 B.7. HTB/Prio QDisc variierender Burst-Parameter HTB/Prio QDisc variierender Burst-Parameter Abbildung B.19: Delay in QDisc für 500Byte, 2 Kanäle Prio 4 100µs und 1 Kanal Prio 7 500µs mit cburst + 1000 Abbildung B.20: Delay in QDisc für 500Byte, 2 Kanäle Prio 4 100µs und 1 Kanal Prio 7 500µs mit Minimum Burst - 1000 135 B.8. Ein Prio 1 Kanal ohne Störlast Anhang B. Diagramme Abbildung B.21: Delay in QDisc für 500Byte, 2 Kanäle Prio 4 100µs und 1 Kanal Prio 7 500µs mit Minimum Burst - 500 B.8 Ein Prio 1 Kanal ohne Störlast Abbildung B.22: Kanal Prio 1 500Byte ohne Störlast gemessen in QDisc 136 Anhang B. Diagramme B.9. Ein Prio 1 Kanal mit Störlast Abbildung B.23: Kanal Prio 1 500Byte ohne Störlast gemessen End-to-End B.9 Ein Prio 1 Kanal mit Störlast Abbildung B.24: Kanal Prio 1 500Byte mit Ping-Flood gemessen in QDisc 137 B.10. Ein Prio 7 Kanal ohne Störlast Anhang B. Diagramme Abbildung B.25: Kanal Prio 1 500Byte mit Ping-Flood gemessen End-to-End B.10 Ein Prio 7 Kanal ohne Störlast Abbildung B.26: Kanal Prio 7 500Byte ohne Störlast gemessen in QDisc 138 Anhang B. Diagramme B.11. Ein Prio 7 Kanal mit Störlast Abbildung B.27: Kanal Prio 7 500Byte ohne Störlast gemessen End-to-End B.11 Ein Prio 7 Kanal mit Störlast Abbildung B.28: Kanal Prio 7 500Byte mit Ping Flood gemessen in QDisc 139 B.11. Ein Prio 7 Kanal mit Störlast Anhang B. Diagramme Abbildung B.29: Kanal Prio 7 500Byte mit Ping Flood gemessen End-to-End 140 Anhang C Inhalt der CD Sourcecode Dieses Verzeichnis enthält den kompletten Quellcode zum erzeugen der Bibliothek. Die xml-Dateien sind Konfigurationsdateien. msrConf.xml ist die Konfigurationsdatei, die von dem Lastgenerator msrSend eingelesen wird. Für die drei Lastkonfigurationen existiert je eine eigene Datei, die über msrConf.xml kopiert werden kann, um die Messkonfigurationen nach zu vollziehen. Das Verzeichnis llaapp enthält die Messbibliothek von Dipl. Inf. Bernhard Gelling. Im Verzeichniss Kernel-Instrumentierung befinden sich die beiden Kernel-Dateien, die zur Messung instrumentiert wurden. Messungen Dieses Verzeichnis enthält alle Messdaten in Roh- und in aufbereiteter Form als Excel-Dateien. Quellen Dieses Verzeichniss enthält alle elektronischen Quellen. 141