 So, dann herzlich willkommen hier im ZKM Vortragssaal zum Talk von Paul, Paul Emmerich, über User Space Treiber für Netzwerkarten. Wer von euch hat denn schon mal einen Talk von Paul gehört? Zufällig? Ah, okay. Und wer von euch hat schon Erfahrungen mit Treiberentwicklung? Ja, okay, cool. Also ganz viel zu lernen hier heute. Dann ganz viel Spaß mit dem Talk von Paul und bis später. Ja, danke. Dann wäre es noch toll, wenn der Beamer das Bild anzeigen würde. Das sieht doch gut aus. Moment, ich stell den Bild schon kurz auf Spiegel, damit ich hier meine Präsentationsnotizen habe oder auf Nichtspielen. Das kriegen wir hin. So, jetzt vertauschen wir die und ziehen diese Leiste rüber. Voll super. Und jetzt sehe ich alles. Ja, dann geht es auch schon los. Und zwar rede ich mal wieder über User Space Netzwerkarten. Es gibt in dieser Reihe ein paar Mehrtalks, wenn sich da jemand noch ein bisschen mehr für interessiert. Das basiert auf einem Talk vom 34-C3 im Groben mit ein bisschen Sachen noch reingepasted, von dem Talk vom 35-C3 und ein paar Sachen vom Easter-Hack dieses Jahr noch reingepastet. Also wenn sich jemand noch weiter interessiert, einfach mal auf media.ccc.de meinen Namen suchen und da gibt es noch ein paar Sachen. Ich habe das hier ganze zusammen mit zwei Studenten von mir gemacht. Ich fügt da immer alle ein, die irgendetwas zu diesem Talk beigetragen haben. In dem Fall waren das zwei Studenten, die hier im Rahmen von Abschluss arbeiten, was daran gemacht haben. Und es geht um Netzwerkkarten. Und zwar Treiber für Netzwerkkarten. Ist mal allgemein Netzwerkkarten. Warum Netzwerkkarten? Ja, Netzwerkkarten einfach nur, weil ich mich mit Netzwerkkartenbeschäftiger an der Uni, ich bin da als Doktorand und meine Thema, meine Dissertation, ist eben Performance von irgendwelchen Sachen, die man in Software mit Netzwerkkarten macht. Da habe ich schrecklich viel Ahnung von Netzwerkkarten und Low Level Treibern dafür. Aber viele von den Sachen, über die ich rede, lassen sich auch auf andere PCIe-Geräte übertragen. Also nur, weil jetzt sehen wir die Beispiele Netzwerkkarten sind, funktioniert das meiste analog dafür, wie auch zum Beispiel für eine NVMe-Festplatte oder so was. Weil am Ende sind die Geräte doch irgendwie sich sehr ähnlich auf der tiefen Ebene. Das ist jetzt irgendwie der alte Netzwerkkarte. Die ist irgendwie nicht so cool, weil die hat nur ein Gigabitlink. Das ist irgendwie langweilig. Es gibt dann irgendwie größere Netzwerkkarten. Die haben dann zweimal zehn Gigabitlinks. Das ist in dem Fall jetzt eine von Intel. Die hat zwei SFP Plus Ports, zweimal zehn Gigabitlinks. Dann gibt es noch dickere Netzwerkkarten. Die haben jetzt hier zwei QSFP Plus Links. Da könnte jetzt 40 oder 100 Gigabit dran sein. Das ist dann schon mehr spaßiger. Und was ich mir hier eben an diesen Netzwerkkarten anschaue, sind nur immer die unteren Layer. Wenn man jetzt so ein übliches anschaut, wie sieht der ganze Spaß aus? Was interessiert mich davon? Die meisten Leute, wenn sie Netzwerk hören, denken irgendwie an sowas mit TCP, IP oder gar HTTP und so weiter. Aber Anwendungen, die ich mir anschaue, laufen eigentlich immer auf den niedrigeren Layern, weil es ist halt eben nicht so, dass man alles auf HTTP macht, auch wenn manche Hipster vielleicht glauben, dass JSON irgendwie das einzige Protokoll ist. Aber es gibt eine Menge Sachen, die irgendwie in Netzwerken rumliegen und wichtige Sachen übernehmen, die auf niedrigeren Layern arbeiten. Das ist sowas wie irgendwelche Router, die irgendwo sind, irgendwelche Switche oder virtuelle Switche, die dann irgendwie die VMs dranbinden, Firewalls oder irgendwelche Middleboxes, die das Internet kaputt machen. Aber auch die gibt es überall. Ein paar Buzzwords, die da relevant sind, sowas wie Network Function Virtualization. Das ist eben der Trend, dass ich heute mehr hergehe und nicht irgendwie so eine Hardwarebox hinstelle, um irgendwas zu machen, sondern mir eine normale Softwareboxen oder normalen Server nehme. Da ein paar Netzwerkkarten reinbauen und dann den ganzen Spaß in Software mache und dadurch ein bisschen flexibler bin, idealerweise auch billiger bin oder weil es vielleicht die einzige Wahl ist, weil die Sachen, die dranhängen, keine echten Kabel sind, sondern weil da irgendwelche VMs dranhängen oder mehrere VMs. Ja, wenn man sich so eine Anwendung jetzt mal anschaut, dann sieht die irgendwie so im Groben so aus. Wenn ich jetzt mir hervorstelle, ich habe jetzt hier mein Firewall, Router, Switch, Middlebox, sonst was, die läuft irgendwie in meiner dicken Serverbox, darunter liegt irgendwie so ein Betriebssystem und darunter liegt irgendwie ein Treiber, der mit den Netzwerkkarten redet. Das ist jetzt mal so unsere Beispielanwendung anhand von der wir uns ein paar Treiberkonzepte anschauen. Nun, wenn ich das jetzt einfach hergehe und schaue irgendwie bei Google Eintippe, wie schreibe ich ein Programm, das irgendwie das mit Netzwerk macht, dann finde ich irgendwas mit Sockets und dann sieht das in dem High-Level-View etwa so aus, dass ich irgendwie auf meinem Server eine Trennung habe zwischen User Space und Kernel Space. Im User Space schreibt meine Anwendung, die redet über die uralte POSIX Socket API mit dem Betriebssystem, schaufelt da irgendwie die Daten rein oder bekommt Daten raus und das Betriebssystem handelt all diese ganzen nervigen Sachen mit dem Treiber und so. Das ist irgendwie ein schönes Bild, aber so sieht das in Wahrheit gar nicht aus. In Wahrheit sieht es eher so aus, weil da ist so ein riesiges Loch dazwischen und dieses Loch ist vor allem bezüglich Performance, weil immer wenn man zwischen diese Grenze User Space und Kernel Space drüber muss, muss man irgendwie durch einmal seine Daten kopieren oder so teure Syskalls machen und so und das ist irgendwie voll blöd, wenn ich was schreiben will, was am Ende auch schnell sein soll. Und nun mal so ein paar Zahlen, wenn ich mir anschaue Sachen in Pakete pro Sekunde. Es werden jetzt die meisten Zahlen bezüglich Performance, werde ich in Pakete pro Sekunde angeben, weil der Haupt Overhead, wenn ich irgendwas mache mit Paketen, ist eigentlich der Overhead, den ich habe, dafür, dass ich ein Paket habe, was ich verarbeiten muss, nicht wie groß das Paket ist. Ich brauche selten deutlich länger, nur weil ein Paket doppelt so groß ist. Ich brauche einfach sehr lang, weil ich den Header von dem Paket verarbeiten muss und selbst wenn ich irgendwie einen VPN-Gateway bin und das halbe Paket oder das ganze Paket verschlüsseln muss, ist meistens sogar die Verschlüsselung teurer, das zu initialisieren und so weiter, als ob das Paket jetzt 64 Beit oder 1000 Beit groß ist. Und wenn ich mir jetzt überlege, wie viele Links oder wie viele, wie viele Pakete, bekomme ich so ein 10 Gigabit Link rein. Wenn die Pakete so klein sind wie möglich, komme ich auf 14,88 Millionen Pakete pro Sekunde, die man dort reinschieben kann. Jetzt sind 10 Gigabit Links schon gar nicht mehr so das aller modernste. Es gibt so was mit 100 Gigabit Links und so. Da bekomme ich dann schon etwa 150 Millionen Pakete pro Sekunde rein, was eine Menge ist. Weil, nehme ich an, mein Server hat eine CPU mit 3 Gigahertz und ich habe einen 10 Gigabit Link dran. Dann habe ich also, wenn ich das ganze auf nur einem CPU Kern mache und nur eine Netzwerkarte habe, nur 200 Tag Zyklenzeit, um so ein Paket zu empfangen, verarbeiten und wieder rauszuschicken. Und das ist echt nicht viel, wenn ich irgendwas Sinnvolles mit dem Paket machen will. Und was ich so als Ziel habe, was man üblicherweise als Ziel hat, wenn man so eine Anwendung schreibt, dass man eben 10 bis 15 Millionen Pakete pro Sekunde pro CPU Kern verarbeiten will und verarbeiten. Hier heißt nur, ich möchte das Paket empfangen und weiterleiten. Da habe ich jetzt noch nichts gemacht, da habe ich noch keine Firewall-Regeln nachgeschaut, da habe ich noch keine Routing-Tabelle nachgeschaut. Das heißt, das ist einfach das, was man irgendwie schaffen muss. Wenn ich mir das umrechne, in wie viele Cycles habe ich dafür Zeit für 10 bis 15 Millionen, komme ich irgendwie bei so 200 bis 300 etwa raus. Und das ist echt nicht viel, wenn ich das vergleiche mit, wie lange brauche ich denn, um meinem Socket-Interface irgendwie so ein Paket da reinzustecken. Die Antwort ist sehr, sehr, sehr lange. Und wenn ich jetzt einfach eine einfache Anwendung schreibe, die irgendwie nur ein Socket aufmacht, ein Paket rausnimmt, aus einem Socket in den anderen wieder reinsteckt und noch keine Verarbeitung macht, komme ich bei etwa 300.000 Paketen pro Sekunde und CPU Kern raus, was jetzt mal so ein paar Größenordnungen daneben liegt. Und auch das, wenn ich jetzt hergehe und das Verbessere und irgendwie Lippicap oder sowas nehme, dann ja, komme ich bei einer Million Pakete pro Sekunde raus. Also, was haben die Leute, die das früher mal gemerkt haben vor langer, langer Zeit? Das ist jetzt ja alles nichts Neues, dass das langsam ist. Was haben die gemacht? Die haben gesagt, okay, wir nehmen jetzt die Anwendung und schieben sie mit in den Kernel rein, einfach um diese Grenze loszuwerden. Da hat man sowas wie, dass der Router in Linux im Kernel läuft, dass natürlich irgendwie IP-Tablesfeierwoll im Kernel läuft. Und manche Leute sind da auch so ein Schritt weitergegangen und da läuft dann der ganze Webserver im Kernel oder zumindest Teile davon. Microsoft's EES-Webserver, da hatten großes Kernelmodul aus Performance-Grunden, machen Leute ja auch andere moderne virtuelle Switche wie Open-V-Switch, wird als Kernelmodul erst mal entwickelt, einfach um Performance zu haben. Nun, jetzt habe ich zwar das Performance-Problem größtenteils gelöst oder ich bin in großen Bottlenack losgeworden, dafür habe ich jetzt aber spannende neue Probleme, weil erstens ist es plötzlich deutlich schwieriger zu entwickeln. Und ich muss so ein paar, habe so ein paar Einschränkungen im Kernel, so zum Beispiel wird es sehr schwer sein, eine andere Programmiersprache als C zu verwenden, zumindest wenn man es irgendwann mal upstream haben will. Und wenn ich irgendein dummen Fehler in meinem C-Programm mache und man macht dumme Fehler in C-Programmen, dann kriegt man irgendwie eine Kernelpanik und das ist irgendwie nicht so cool. Ja, es gibt aber inzwischen seit ein paar Jahren ist das ein heißes Thema. XDP, das würde ich ja noch kurz erwähnen. XDP ist im Kernel ein neues Firmwork, womit ich meine Anwendung jetzt nach eBPF-Kompile, das heißt ich schreibe meine Anwendung üblicherweise in irgendeinem Subset aus C, habe spannende neue Einschränkungen, wie zum Beispiel, dass es da so einen Bytecode-Warefire gibt, der mir den Code sich anschaut und sagt, okay, der cursed mir garantiert nicht den Kernel. Wie macht er das? Ja, der sagt, ist da eine Schleife drin? Ja, dann nein, dieser Code wird nicht ausgeführt, gehen die Leute natürlich her und deaktivieren den, weil sonst ist ja irgendwie blöd, wenn man keine Schleifen haben kann. Das ist ein ziemlich nettes Zeug, das XDP, das löst eine Menge Probleme und das ist vor allem für so Firewall-Anwendungen ziemlich gut, weil man das Paket dann irgendwie auch über diese Interfaces relativ gut in den normalen Netzwerksteck wieder rein bekommt. Ja, aber was ist mit der Performance? Ja, wenn ich jetzt hier irgendwie mal so eine Performance-Analyse mache und in dem Fall haben wir uns Open-V-Switch angeschaut und irgendwie eine statische Open-Flow-Regel rein gesagt, Pakete, die auf Port 1 reinkommen, auf Port 2 raus schicken, nicht mehr machen und so weiter, dann Profiling gestartet, schauen, wo wird die CPU-Zeit verschwendet? Ja, Paket empfangen, 500 Taktzyklen. Ups, schon mal zu viel. Paket wieder raus schicken, 440 Taktzyklen, auch zu viel. Dann noch so allgemeines Memory-Management-Overhead von so einem SK-Buff, nochmal 400 Taktzyklen und am Ende kommt man dann bei 2 Millionen Paketen pro Sekunde raus, pro CPU-Core mit zum Open-V-Switch, was immer noch Faktor 5 bis 8 oder so daneben ist, was ich eigentlich wollte. Nun, da gibt es jetzt das XDP, was ich gerade gesagt hatte, das löst einige der Probleme hauptsächlich, was XDP sehr gut macht ist, das basiert darauf, dass da die Speicherverwaltung für die Treiber komplett neu gemacht wurde, dass da nicht mehr diese SK-Buffs immer alluziert werden müssen, die einfach grausam langsam sind und dementsprechend funktioniert das auch noch nicht mit allen Treibern und damit kommt man dann auf etwa 10 Millionen Pakete pro Sekunde, hat man zumindest ein bisschen die Performance-Probleme schon gelöst, aber ich habe eben diese ganzen anderen Probleme wie, dass ich irgendeine Sprache brauche, die mir nach eBPF compile und by God very Fire und so weiter, alles nervig. Also haben Sie schon lange davor, Leute, gedacht, okay, im Körner schreiben ist irgendwie blöd und auch nicht so richtig schnell, kann man das irgendwie im Userspace machen und gleichzeitig schnell sein. Da gab es so ein paar Frameworks, die so vor 10 Jahren etwa aufgekommen sind, die einige von den Problemen gelöst haben und zwar haben die so funktioniert, dass die ein kleines Kernelmodul hatten, dass ein Speicher per Memory, sich ein Speicher irgendwie memory mapped, wo die ganzen Datenstrukturen von der Netzwerkarte direkt drin liegen und dann eine kleine Userspace-Liber wie dabei haben, die denselben Speicher in einem Userspace-Prozess memory mapped und dadurch konnte man einfach über ein Shared-Memory-Interface direkt den ganzen schmaren Umgehen mit umständlich und langsam und man musste dann über so eine Control-API nur noch regelmäßig sagen, okay, hier neue Pakete und so weiter und dann hat es eigentlich auch ganz gut funktioniert. Von den Sachen gibt es, das sind alles so die die Bekanterinnen, Netmap aus FreeBSD, dann gibt es PF-Ring, was relativ weit verbreitet mal war früher und PFQ für Leute, die funktionale Programmierung mögen, aber das hat ein paar neue Probleme gemacht, nämlich ist das jetzt irgendwie eine absolute Nicht-Standard-API im Vergleich zu den POSIX-Sockets. Man hat ja immer so ein Custom-Curl-Modul nachladen müssen, manchmal hat man die Treiberpatchen müssen, manchmal hat man sich irgendwie den Kernel neu kompilen müssen und das war irgendwie alles schlimm, die APIs sind mehr oder weniger gut zu benutzen, aber meistens eher nicht so toll zu benutzen und auch dann die Anwendung, die läuft, hat dann exklusiven Zugriff auf eine Netzwerkarte, weil die direkt die Buffer von der Netzwerkarte benutzt und dann kann keine zweite Anwendung, die auch gleichzeitig mehr benutzen, was ein bisschen nervig ist. Dann man verliert eine Menge Features, die man normalerweise im Kernel hat, wenn ich normalerweise denke, hey, ich habe jetzt was in meinen Treiber und ich habe jetzt übrigens im Kernelfeature, ich möchte jetzt hier mal kurz eine IP-Table-Regel dazwischen machen. Ja, das geht dann halt auch nicht mehr, weil das wird davor schon abgegriffen und geht exklusiv an eine Anwendung. Das nächste ist, dass irgendwie so Hardware-Support nicht so toll war, weil es ist in letzter Zeit vor allem immer mehr in Hardware-Netzwerkarten gewandert, dass die immer mehr Features haben in Hardware für irgendwelche Offloading-Sachen und die sind meistens nicht so schrecklich gut exposed, zum Beispiel Netmap hat kein Support für Checksam-Offloading überhaupt und das fanden Leute auch nicht so toll. Das nächste ist, man muss natürlich jedes einzelne Netzwerkartenmodell, was es so gibt, explizit nochmal dort drinnen zu porten und ja, das heißt, meistens hat man Einschränkungen auf so ein paar Netzwerkarten, die man damit nur benutzen kann, auch nicht so toll. Man kann aber ein paar Probleme davon lösen, dass man noch mehr im User-Space macht, indem ich den Kernel jetzt ganz raus nehme und sage, ich nehme jetzt den ganzen Treiber, schiebe ihn in den User-Space und mache mir einfach nur noch ein Memory-Map, nehm mir alles in den User-Space und händel es komplett dort und der Kernel weiß eigentlich gar nicht mehr, dass dieses Gerät existiert oder benutzt wird. Das war dann was relativ Neueres, das kam dann so vor vier, fünf Jahren, das erste Mal auf, dass das genutzt wird, die bekanntesten oder die größten Beispiele im Netzwerkbereich, dafür sind DPDK und Snap. Ja, das löst nicht alle Probleme natürlich, weil ich habe immer noch eine Nicht-Standard-API, ich habe immer noch eine Anwendung, die diesen Treiber direkt im eigenen Prozess hat und dadurch exklusiv die Netzwerkarte benutzt und auch hier brauche ich wieder Treiber in jedem Formwerk drinnen, da die explizit sagen, hier ich habe Support für diese und diese Das Schöne ist, dass man dank DPDK heutzutage, es ist ein Linux Foundation Projekt, was mal von Intel gestartet wurde, da sind Treiber für alle Netzwerkarten, für alle relevanten Netzwerkarten drinnen, wobei relevant das ist, was halt 10 Gigabit oder mehr hat, einfach, weil heutzutage kriegt man keine Netzwerkarte mehr verkauft im Serverbereich, wenn die nicht DPDK unterstützt, weil das einfach zu weit verbreitet schon ist. Das Problem, was ich immer noch habe, ich verliere den Zugriff auf die üblichen Kernelfeatures, die ich habe, ich kann jetzt nicht einfach meine IP-Tables-Regel dazwischenkleben. Und es ist die Frage, ob man da wirklich auch viel verzichtet und kann sich fragen, was hatten der Kernel jemals für uns getan eigentlich im Netzwerkbereich und ja, was hatten der Kernel getan? Naja, es gibt also Protokoll-Implementierungen von TCP und so und vor allem Implementierungen von TCP, die tatsächlich funktionieren auch in allen Edge-Cases und so weiter. Okay, aber was hat der Kernel denn sonst für uns getan in Netzwerken? Ja, er hat ziemlich gute APIs zum User Space hin, die auch tatsächlich irgendwie stabil sind und sich nicht alle drei Monate mal ändern, weil jemand eine neue API sich ausgedacht hat. Gut, aber was hat der Kernel? Außer Protokoll-Implementierungen, die funktionieren, gute Interfaces dafür und sonst was getan. Ja, man kann irgendwie super einfach mehrere Anwendungen gleichzeitig laufen lassen, was schon irgendwie ein cooles Feature ist. Gut, aber außerdem, ja, das ganze Firewalling Zeug, das Routing Zeug, haben EBPF, haben das XDP Zeug und viel mehr. Das heißt, da ist eine Menge Features, auf die man hier verzichtet, wenn man sich einen User Space-Triber schreibt oder einen User Space-Triber benutzt. Da sollte man sich immer gut überlegen, ob man das wirklich machen will, ob man das wirklich braucht für seine Anwendung. Falls man irgendeine von diesen Features braucht, dann sollte man sich überlegen, vielleicht XDP zu benutzen statt einem User Space-Triber. Das betrifft vor allem Firewall Anwendungen. Nun, aber wir haben jetzt eine Menge Features aufgegeben. Jetzt ist die Frage, ist es denn dafür jetzt endlich schnell, also so richtig schnell? Ja, wenn man jetzt in DPDK mal anschaut, wie lange braucht man ein Paket zu empfangen? 50 Tagzübbeln etwa, muss ich wieder rauszuschicken, nochmal 50 Tagzübbeln und andere sind da etwa gleich schnell, wobei DPDK das schnellste ist. Und wenn ich jetzt ein Open-Viswitch nehme, was standardmäßig mit einem DPDK-Backend heutzutage kommt, optional, dann gehe ich plötzlich von 2 Millionen Pakete auf 14 Millionen Pakete pro Sekunde los. Faktor 7, nur dadurch, dass ich das Backend von DPDK getauscht habe, das habe ich aufgegeben, eine Menge Features, die ich für den Anwendungsfall nie gebraucht habe, weil ich habe ja Open-Viswitch genommen, weil ich mir einen Switch bauen wollte. Und wenn ich mir diesen Switch mit Open-Viswitch baue, dann brauche ich nicht, dass da mehrere Anwendungen gleichzeitig drauf zugreifen. Ich habe da einfach kein Interesse dran, dass da irgendwie ein zweiter Switch noch läuft, weil ich habe mir meinen Server gekauft, habe mir da 10 Netzwerkarten reingetan und sage diese 10 Ports, die machen jetzt hier irgendwie Switching oder Routing. Und dann gehen die an diese eine Routing-Anwendung und alles andere interessiert mich dann auch nicht mehr. Das heißt, ich habe da eigentlich keine Features verloren, sondern ich habe da nur Performance gewonnen am Ende. Ja, in was für Anwendung ist das drin? Ich habe schon gesagt, Open-Viswitch ist so das größte, wo DPDK Support standardmäßig dabei ist. Gibt auch noch ein paar andere. Vor allem das VPP von Cisco ist jetzt so seit zwei, drei Jahren das das neueste Große. Das ist quasi so ein Router von Cisco, den die intern in ihren Routern verbauen. Das heißt, wenn ich mir irgendwie von Cisco so ein Switch oder sowas kaufe, dann kann es sein, dass da einfach nur eine x86 CPU drin ist und irgendwie ein bisschen Software mit DPDK unten drunter, weil es einfach flexibler billiger ist. Und die Hauptgründe, warum sowas eingebaut wird, ist eben Performance und dass ich sehr viel Kontrolle über Hardware-Features bekomme. Das heißt, wenn ich jetzt hier eins von diesen Treibern im User Space habe, dann kann ich mir jedes noch so obskure Hardware-Feature, was meine Hardware kann, irgendwie aktivieren und nutzen. Und zwar oft noch lange, bevor es irgendwie in einem Linux-Könneltreiber oder sowas drinnen ist oder sinnvoll nutzbar ist. Okay, also das war so die Einleitung. Was gibt es da? Und jetzt ist die Frage, wie funktionierten das Ganze? Wie kann man sich selber sowas bauen? Ja, ist natürlich die Frage, warum soll man das tun? Ja, ich fand es einfach sehr lustig, das mal zu schreiben. Und dann habe ich mich vor anderthalb oder zwei Jahren mal hingesetzt und einfach so Netzwerkarten-Treiber geschrieben. Ich wollte einfach mal verstehen, wie diese Sachen funktionieren, wie komplex die eigentlich sind und wie eigentlich diese ganzen Magic Blackbox-Frameworks funktionieren, die andere Leute einfach so hinnehmen und einfach benutzen. Und vor allem schauen sich Leute das oft an und sehen, so einen DPDK-Treiber, die sind so im Schnitt 20.000 bis 100.000 Zeilen Code drin. Und da fragt man sich, oh je, kann man sowas wirklich selber schreiben? Wie schwer ist denn das, wenn man wirklich sich nur auf das minimale Featureset beschränkt? Aber stellt sich raus, das ist ganz einfach, weil ich habe dann am Ende irgendwie weniger als 1000 Zeilen gebraucht, um irgendwie Code zu haben. Der kann so ein bisschen Multicore, der kann so Pakete empfangen, Pakete rausschicken, das irgendwie mit mehreren zuerst gleichzeitig. Aber das war es auch so im Groben schon. Aber damit ist er vom Featureset eigentlich relativ gut für das, was man für viele Dummy-Anwendungen oder für viele einfache Anwendungen braucht, weil oft ist die ganze andere Logik eh in Software. Und wenn ich irgendein komisches Hardware-Feature noch brauche, dann kann man das meistens auch recht einfach implementieren. Paarchecks haben offloading Sachen habe ich eingebaut, aber die meisten Sachen werden auch nicht viel einzubauen. Da muss man nur in der richtigen Stelle das Flex setzen und dem sagen, hier an Offset-10 ist irgendwie so eine IP-Check-Summe bitte mal berechnen. Hält sich also eigentlich in Grenzen. Nun für was habe ich das geschrieben? Ich habe das für die Intel XGBE-Netzwerkarten geschrieben. Das sind von Intel, die gibt es auch, wird als X520 verkauft. Der Chip darauf heißt 82599, X540, X550 und die Embedded-Netzwerkarten in Intel, die basieren auch, die laufen auch mit dem Treiber ohne irgendwelche Änderungen. Außerdem sieht man die Teile echt oft als On-Bord-Netzwerkarten auf irgendwelchen Server-Mainboards, irgendwelchen Mainboards für ein Server kauft und da ist irgendwie On-Bord 2 mal 10 Gigabit, dann ist das oft so eine Intel 82599, die da einfach drauf ist und das Ding hat einfach ein richtig gutes Datenblatt. Das macht Intel als einer der einzigen Hersteller von Netzwerk-Karten-Hardware eigentlich sehr gut, dass die wirklich so komplette Datenblätter irgendwie 1500 Seiten PDF, wo wirklich jedes Register mal einzeln erklärt ist, damit man sich da auch selber ein Treiber schreiben kann. Das andere Schöne an der Karte ist, dass da in dieser, das ist da nicht so eine Blackbox Magic Firmenbeer drauf ist, die mir irgendwas abnimmt. Bei vielen, vor allem neueren Karten und schnelleren Karten hat man oft so eine große Blackbox, die in der Netzwerkkarte läuft und irgendwelche komischen Dinge tut, die man vielleicht nicht will oder vielleicht nicht versteht und dann sieht der Treiber größtenteils so aus, dass man irgendwie der Firmenbeer sagt, hey, mach doch mal bitte, la für mich. Hier ist es so, dass diese ganzen Hardware-Features noch sehr direkt sichtbar sind im Treiber und wenn ich mir hier zum Beispiel irgendwelche Filtersachen implementiere, dann steht da im Datenblatt noch wie unten drunter die Hashtable in der Hardware implementiert ist und wo ich jetzt welche Bit setzen muss, damit das funktioniert. Das ist bei einer neueren Karte muss man irgendwie den Treiber sagen, hey, diesen Filter bitte. Und das wäre irgendwie langweilig, wenn wir nur mit so einer Blackbox Firmenbeer reden. Das hat hier diese Karte, wo man den ganzen Schmarrn noch selber machen muss. Nun, wenn ich jetzt so eine Karte habe, mir einen Server einbauen und mit der irgendwie reden will aus meinem Userspace-Prozess, wie mache ich das dann? Ja, relativ einfach. Schritt oder es gibt drei Möglichkeiten, wie man mit PCI Expressgeräten oder mit modernen PCI Expressgeräten in X86-Systemen reden kann. Das erste ist MemoryMap.io. Das heißt, da gibt es einfach ein Speicherbereich, der ist geteilt zwischen dem Device und dem User. Einfach, oder das ist jetzt grob vereinfacht gewesen, prinzipiell ist es so, ich schreibe irgendwo was eine Speicheradresse und dann wird das an das Gerät weitergeleitet und das Gerät kann das irgendwie beantworten. Und wie das oder der häufigste Anwendungsfall dafür ist, das Gerät hat irgendwelche Register, die tauchen unter irgendwelche Speicheradresse auf, da kann ich hinschreiben, davon kann ich lesen und dann rede ich mit dem Device. Für Userspace-Treiber hat Linux zum Glück das UI of Framework, was das sehr einfach macht, da kann ich M-Map auf so eine magische Datei aufrufen und dann habe ich den Speicherbereich, da kann ich da lesen und schreiben und ziemlich easy. Dann DMA, Direct Memory Access, das ist, wenn das Gerät mit mir reden will beziehungsweise mit dem System reden will. Das Gerät kann dadurch einfach über PCI Express sagen, okay, hier bitte an diese Speicheradresse lesen und zwar an diese physikalische Adresse. Das umgeht so ziemlich alles, was man eigentlich aus modernen Systemen kennt, so Paging und so ein Kram oder Speicherschutz wird einfach ignoriert. Das schreibt einfach irgendwo hin und liest von irgendwo. Das heißt, ich muss eigentlich nur herausfinden, wo meine Datenstrukturen so im Speicher an physikalischen Adressen liegen und dann dem Gerät sagen, bitte dorthin schreiben, von dort lesen. Relativ easy eigentlich, weil unter Linux kann ich mir das als rot zumindest über Proxler-Spelch-Map ausgeben lassen. Ja, zu IOMMU und wie man verhindern kann, dass da irgendwie dann an das System abstürzt, wenn man an Adresse 0 schreibt, nachher noch mehr. Und die dritte Möglichkeit ist Interupt. Interupt ist, wenn das Device sagen will, hey, ich habe hier ein voll wichtiges Event, wäre super, wenn du mal schnell darauf reagieren würdest. Da hört man oft oder liest man oft, dass irgendwie das in User Space-Triber nicht gehen würde oder dass das ein riesiger Nachteil von DPTK oder so wäre, dass das keine Interupt könnte. Als alles Quatsch, man kann problemlos Interupt in User Space-Tribern benutzen. Auch DPTK hat Interupt-Support. Unser Treiber hat Interupt-Support, ist noch nicht gemirrt, aber hat es schon in irgendeinem Brunch. Und zwar gibt es unter Linux, macht uns das relativ einfach, da gibt es das VIF-IO-Subsystem. Und da kann ich mir sagen lassen, hey, ich habe hier irgendwie ein Gerät, was irgendwie ein Interupt schickt, das schickt diesen Interupt und bitte map mir den doch mal auf diesen Event-FD und dann habe ich so einen File-Diskupter, da kann ich E-Poll drauf machen und dann wacht das Ding auf oder wacht mein Prozess auf, wenn das Gerät ein Interupt gefeuert hat. Ich kann das Gerät fragen, was waren das, was ist da los. Das braucht man vor allem für Strom-Sparmechanismen. Nun, wenn wir uns jetzt so ein User Space-Triber schreiben wollen, basierend auf dem, was wir wissen, dann ist es eigentlich relativ easy. Schritt eins, wir werfen den Kernel-Triber raus, weil den brauchen wir nicht mehr. Schritt zwei, wir machen uns einen M-Map auf die richtige magische Datei und dadurch haben wir Zugriff auf das Device. Da müssen wir nur noch rausfinden, in welchen physikalischen Adressen unser Speicher so liegt und da müssen wir eigentlich nur noch den Treiber schreiben, was auch gar nicht so schwierig war. Und ja, dafür müssen wir erstmal rausfinden, was wir gerade verbenutzen müssen. Machen wir irgendwie LSPCI drauf, bekommen so ein Output, wo irgendwie unsere Netzwerk-Karten drin stehen. Die haben mir links jetzt in Fett gehighlightet, diese Adresse. Das ist eine PCI-Express-Adresse. Das ist die Adresse, die ich verwende, wenn ich auf dem Bus damit reden will. Und das SysFS unter Linux unter der Adresse, finde ich dann auch das Gerät wieder, kann das kontrollieren und sage hier irgendwie, okay, ich möchte jetzt erstmal den Treiber rauswerfen, dann bin ich den Treiber los und dann kann ich hergehen, sage, mach mir hier ein Open auf so eine Datei, die herumliegt, sagt, möchte ich lesen und schreiben können, als Ruth darf ich das und schau, wie groß ist das Ding und dann mache ich ein M-Bab drauf und dann habe ich irgendwie diese Resource 0, das ist das Base Adress Register 0. Da stehen so die Basisinformationenregister vom Gerät drum. Jetzt mache ich mal 1.500 Seiten PDF auf und finde hunderte von Seiten, die so aussehen, wo irgendwo drin steht. An Adresse 0 steht das Device Control Register, da kann ich lesen und schreiben und auf Seite 543 ist es im Detail erklärt. Danach steht das Status Register, das darf ich nur lesen und so weiter. Und dann muss ich eigentlich nur das Datenblatt lesen und kann an die richtigen Stellen, hier links steht dann der Offset, zum Beispiel an diesem kann ich irgendwas mit Wlan Isar Types anstellen, also könnte ich mir da zum Beispiel reinschreiben, dass er irgendwie ein Wlan Filter installieren soll oder so. Aber ein spannenderes Beispiel als sowas ist eigentlich, wenn wir uns so ein LED-Ctrl-Register anschauen, weil da hat man schneller ein Erfolgserlebnis, wenn man irgendwie eine LED zum Leuchten bringt und im Datenblatt steht, das ist an Adresse Hexadecimal 200 und an Offset 7 steht so ein Fleck für macht, dass die LED blinkt. Also gehe ich her, nehme mir meine Registers, die ich vorher hier, das war das Ergebnis von dem M-App, dann rechne ich mir da den Offset von dem LED-Ctrl drauf und caste das auf einen Waller-Teil U in 32, weil die Register sind alle 32 Bit groß, steht auch im Datenblatt. Das Waller-Teil ist hier übrigens einer der wenigen Fälle, wo man tatsächlich in 10 mal Waller-Teil richtig benutzen darf, weil das ist Speicherbereich, wenn ich den zweimal lese, kann vollkommen unterschiedliche Sachen rauskommen, obwohl sich nichts geändert hat und da darf der Compiler bitte nicht wegoptimieren, nur weil ich zweimal lese, da darf er auch nicht wegoptimieren, nur weil ich schreibe und das dann nie wieder lese, weil das geht alles über das Gerät, das wird für jeden Schreibtzugreffett, dem gesagt, hey, hier an der Adresse hat jemand was gemacht. Genau, dann lese ich mir das aus, dann kann ich es mir auch wieder mit diesem schönen bisschen Pointer-Magic hier, tu ich das draufordieren, dann ein bisschen Casten, Pointer de-referenzieren und dann mache ich mir einfach mit einem Ohr und einem Schiff hier das eine Fleck rein und dann habe ich blinkende LEDs. Eigentlich super easy, ich habe das eigentlich nur zehn Zahlen kurz geschrieben und da habe ich schon die ersten nützlichen Features genutzt und, ja, sagt man, LEDs blinken lassen ist nicht nützlich, aber damit kann ich schon die ersten praktischen Anwendungen schreiben, weil wenn ich den Schritt einfach mir spare, wo ich den Kernetreiber rauswerfe und dann kann ich mir hier irgendwas implementieren, dass zum Beispiel der Kernetreiber gar nicht kann. Der Kernetreiber kann weiterlaufen, auch wenn ich mir das Internet dann mache. Ich kann einfach irgendein Register setzen und das haben wir zum Beispiel genutzt, um Hardware-Timestamping zu implementieren, weil wir haben irgendwie so ein Testzett abgehakt, wo wir irgendwie so Latenzen im Nanosekundenbereich haben messen müssen und da muss man sich dann entweder in teures Gerät kaufen, was irgendwie an die Pakete timestamped oder man liest genug Datenblätter von Intel, findet irgendwelche Netzwerkarten von denen, die das können, wo aber im Linux-Körner kein Support dafür drin ist, lädt sich den Treiber, setzt da das richtige Fleck und plötzlich stehen in den Paketen hinten noch die Timestamps drin, die die Netzwerkarte gesehen hat und ich habe eigentlich, kann den bestehenden Treiber weiter benutzen, lasst mir ein TCP-Damp laufen, habe plötzlich Timestamps in den Paketen noch drin, das heißt, das ist schon mal nützlich an der Stelle. Ja, aber wir wollen jetzt einen ganzen Treiber schreiben und nicht nur so eine Spielerei machen und wir haben ja auch den Kernetreiber rausgeworfen, das heißt, das nächste ist, so wollen irgendwie ein Paket empfangen oder senden. Nun, Pakete werden per DML gehandelt und der DMA-Transfer wird immer von der Netzwerkarte aus ausgelöst und dafür gibt es so ein Q-Interface, das nennt sich, nennt sich auf oft Ring und so eine Netzwerkarte bietet einem mehrere Receive und Transmit Queues an und üblicherweise hat man so 128 bis 1500, haben so die neueren Geräte dann auch schon mal, das ist für die Multi-Core Skalierung oft, weil das ist eben, dass ich mir die Netzwerkarte effektiv aufteile in mehrere kleine Dinger und dann sage ich habe zwei Transmit Queues, habe ich zwei CPU-Cores oder zwei Prozesse oder sonst was, jeder nutzt eine Queue, die Hardware Merged mir das dann oder beim Empfangen kann ich irgendwelche Filter festlegen, einfachsten Fall sage ich eben hash bitte über Paketheader und teile mir die Pakete nach hash zu. Das ist jetzt nicht spezifisch für Netzwerkkarten, irgendwelche Grafikkarten oder NVMe Platten funktionieren genauso, dass ich da irgendwelche Queues habe, wo ich Daten reinschreib, das Gerät wieder rausliest und umgekehrt um schnell was zu machen. Wenn wir uns das mal so ein bisschen spezieller für jetzt diese XGB-Netzwerkkarten hier anschauen, dann sind das irgendwelche Ringbuffer, wo dem A-Deskriptoren drin stehen, in dem A-Deskriptor, in dem Fall steht im Datenblatt, sind 16 Beit groß, 8 Beit Punkte auf den physikalische Adresse und 8 Beit zu ein paar Metadaten, wie lange ist das, hat es irgendwelche Checksam-Offloading Sachen, die da noch gemacht werden sollen, waren da irgendwelche Fehler und so weiter, das heißt ich muss mal irgendwo ein Speicher alluzieren, übersetzen, wo das physikalisch ist, in den Ringbuffer reinschreiben, hier ist die physikalische Adresse und ich würde es gerne raus schicken und ich hätte gerne irgendwie noch ein Checksam-Offloading an. Ja und dieser Ringbuffer, der irgendwie rum liegt, der wird dann wieder um ein paar DMA zugegriffen und da gibt es so Pointer drauf, da gibt es einen Head-Pointer und einen Tail-Pointer, einer wird von der Netzwerkkarte immer weitergeschoben und einen schieben wir weiter, wenn wir ein neues Paket reingeschoben haben. Im Speicher sieht das dann etwa so aus, wir haben hier einmal im Speicher diesen Descriptor-Ring und da stehen nur Pointer drin und dann haben wir irgendwo ein Memory-Pool und das Wichtige ist eigentlich, dass der Memory-Pool nicht zwangsweise in der gleichen Reihenfolge ist, dadurch habe ich irgendwie keine Probleme, wenn ich mehr Dynamischspeicher dazu allokiere oder Dynamischspeicher wegnehme und da habe ich irgendein Paketbuffer, die standardmäßig zwei Kilobyte groß sind, wenn ich jetzt ein Paket habe, das größer als zwei Kilobyte groß ist, gibt es so Optionen, um mehrere Buffer zusammen zu chainen und so was und das, diese Datenstruktur muss ich mir so nur in den Speicher schreiben und an der richtigen Stelle das Register setzen, um der Netzwerkart zu sagen, jetzt von hier lesen, dann funktioniert das. Geht so, ich sage der Netzwerkarte über so ein Register oder Memory-Map.io, dass ich mir diese Datenstruktur jetzt angelegt habe an an physikalischer Speicheradresse X und ich habe irgendwie 512 Einträge genommen, dann schreibe ich mir Sachen dort rein, dann sage ich der Netzwerkarte los und zum Empfangen schreibe die Netzwerkarte nießt den Pointer aus dem Ringbuffer aus, also Netzwerkarte geht hierhin, schaut den Pointer an, nimmt sich das andere, schreibe dort in den Speichereien und dann geht sie zurück und schreibe mir in den ADescriptor und die Metadaten nochmal rein, okay, fertig und schiebt ein internes Register weiter, das Statusfleck sagt mir fertig und dann schaue ich einfach regelmäßig, hey, ist dieses Statusfleck gesetzt, dann habe ich hier wohl ein Paket empfangen oder ich aktiviere Interrupt und warte, bis ein Interrupt kommt und wenn ein Interrupt kommt, schaue ich, ist das Statusfleck gesetzt? Ja, oder im einfachsten Fall, ich polle das Ding einfach, weil ich das Paket hab, dann resette ich den DMADescriptor, das heißt, ich allokiere mir ein neues Paket oder recyceln Paket, was ich vorher mal hatte, schreibe den Pointer, den ich jetzt wieder hab, in den DMADescriptor zurück und sage, das Statusfleck ist jetzt wieder, dieses Paket ist irgendwie jetzt noch nicht ready und dann nimmt sich die Netzwerkkarte das wieder und wenn sie einmal im Ring bei herum ist und sagt, okay, hier neue Daten, schreibe was rein, Statusfleck wieder gesetzen und ich pass den Tell Pointer bei mir regelmäßig mal an, um einfach Bescheid zu geben, dass das weitergegangen ist. Nun Paket empfangen, schön und gut, was sonst muss man machen? Naja, man muss Pakete noch verschicken, das funktioniert genauso, also man muss jetzt nicht drüber gehen, weil das geht prinzipiell das gleiche, nur dass ich sage, ich setze das Statusfleck auf, ist fertig und dann kommt die Netzwerkkarte irgendwann an und kliert mir das Statusfleck wieder. Außerdem gibt es eine Menge so langweiligen Initialisierungskode, da kann man einfach im Datenblatt so ein paar Seiten lesen, wo so Liste von Schritten sind, dass ich irgendwie sage, hey, setze dieses Register auf das, dann setze das Register, dann lösche das Register und dann aktiviere dem A und wenn man die Reihenfolge falsch macht, geht es irgendwie kaputt, aber solange man sich da relativ genau das Datenblatt hält, was meistens auch größtenteils korrekt ist, dann funktioniert das auch so gut. Und ja, da man irgendwie das so schlecht testen kann, wenn man nur echte, echte Hardware hat oder nur echte Hardware zum testen hat, habe ich dann irgendwann einem Studenten nochmal gesagt, okay, ist irgendwie blöd, wenn das jetzt hier irgendwie außer uns keiner ausführen kann, bitte mal so ein Wirt-AiO-Treiber schreiben, es war dann irgendwann Abschlussarbeit, gab es dann Wirt-AiO-Treiber, haben wir auch immer noch drin, damit kann ich das einfach in der VM-Mahl starten und Vagantest-Setup haben wir, kann ich einfach sagen starten und Wirt-AiO ist dieses Standard virtuelle Netzwerk-Interface, das funktioniert ein bisschen anders, ist aber im Groben auch ähnlich, da gibt es auch wieder Ringe und DMA-Deskriptoren, die da reingeschrieben werden, dadurch kann man sich das einfach mal ausführen in der echten Test-Setup. Eine Frage, ja. Ja, die Frage war PCIe Passu, ja, da gibt es mehrere Sachen dazu, einiges davon unterstützen wir anderes nicht. Zum Testen, ja, so werden wir unser Test-Setup bauen, aktuell haben wir nur ein Test-Setup für Wirt-AiO, hier ging es eher darum, dass andere Leute das auch ausführen können, weil nicht jeder hat 10 Gigabit-Netzwerkarten rumliegen. Ja, ist unser Ding eigentlich schnell, ja, wir sind so schnell wie ältere Versionen von DPDK, neuere Versionen von DPDK, der ist allein irgendwie der Code für Transmit 2000 Zeilen lang, weil da für jede mögliche Kombination aus wie viele Pakete reinkommen, welchen AVX Instruktion Z hat man kann, irgendeine gigantische lange Liste an irgendwelchen AVX in Twin Six drin ist, dadurch ist das irgendwie 30% schneller, dafür kann den Code keiner mehr lesen, weil es nur noch aus AVX in Twin Six besteht. Wir sind auch ähnlich schnell wie so eine übliche XDP basierende App, die im Körnel läuft und wir sind damit etwa siebenmal so schnell, wie wenn ich das komplett im Körnel machen würde mit dem alten Legacy Interface und ja, also eigentlich alles cool. Ein Problem besteht noch, weil ich habe ja jetzt die ganze Zeit so ein paar Sachen benutzt, so ein paar Features genutzt, wo es immer hieß, hey, das braucht irgendwie Routerechte, was irgendwie blöd ist potenziell, aber warum braucht das irgendwie Routerechte? Ja, erst mal dieses M-Map am Anfang braucht irgendwie Routerechte, weil klar, ich tu totales Low Level Device zugreifen, dann aus komischen Implementierungsdetails brauchen wir nicht transparente Huge Pages, das ist ein Quirk von Linux an der Stelle, man braucht auf jeden Fall Rout, um sich die zu allokieren, transparente Huge Pages gehen nicht, dann wir müssen uns Speicher locken, weil wenn das Gerät dahinschreiben will, dann sollte der Speicher auch noch da sein und nicht irgendwie rausgeswoppt sein oder so, weil das wäre blöd, wenn da was anderes steht, das braucht auch Rout oder Juli mit irgendwo anpassen. Die erste Idee ist natürlich, wenn man sowas sieht, ist klar, das ist alles nur Initialisationssache, ich muss beim Starten das mapen, ich muss beim Starten den Speicher allotieren und die Idee ist also, ich schreibe mir irgendwie ein kleines Programm, das das handelt und dann irgendwie über Shared Memory oder irgendso ein Unix Socket mir die Point darüber schiebt, kann man machen, funktioniert aber nicht. Warum das nicht funktioniert, müssen wir uns anschauen, wie Speicherzugriff auf so einen modernen System aussieht. Wir haben hier oben so eine schematisch dargestellte CPU und links unser PCR Express Gerät, unten rechts unser Arbeitsspeicher und wenn ich als Anwendung oben jetzt beim Arbeitsspeicher reden will, dann sieht das so aus. Die Anwendung muss über die MMU gehen. Die MMU ist die Memory Management Unit und die macht sowas wie, dass sie meine virtuelle Adresse in die physikalische Adresse übersetzt, dass sie schaut, darf dieser Prozess hier überhaupt lesen schreiben, ist da gibt es überhaupt Napping. Also alles gut, weil ich als Anwendung kann nichts in der MMU sagen, was sie machen soll, das kann nur der Kernel. Das heißt, die Anwendung ist schön isoliert in den eigenen Adressbereich. Soweit auch noch gut, wenn ich jetzt hier hergehe und mir irgendwie vom Kernel das Memory Map IO abgeben lassen für das Space Null Register, dann ist das so ein Special Mapping in der Memory Management Unit, die dann über PCI weitergeleitet wird zu dem Gerät runter, also auch noch alles gut. Aber das Problem kommt, wenn ich jetzt dem Gerät sage, hey, ich habe Zugriff auf dich, ich darf jetzt oder ich sage dir jetzt hier diese physikalische Adresse da lesen schreiben. Ja, weil das Gerät, das liegt dann leider so aus, da ist dann irgendwie die MMU nicht mehr im Spiel und dadurch kann das Gerät wirklich alles lesen schreiben, selbst wenn da irgendwie so ein, das ist weed only gesetzt ist. Was ziemlich blöd ist, weil als ich den Treiber irgendwie entwickelt habe und Bugs drinnen hatte und dann war irgendwie das Dateisystem plötzlich kaputt mehrfach und das war nicht so gut, gibt aber eine Lösung für, weil jeder große, dicke neue Server-CPU oder alles wo Hardware-Virtualisation Offloading drin ist, habe ich ein Ding, das nennt sich die IOMMU, das Ding tut genau das, was man erwartet, es hängt hier zwischen dem PCI-Gerät und dem Speicher und dadurch wird hier validiert, dass dort auch nur das zugreift, was die IOMMU sagt, dass es darf, jetzt muss ich mir irgendwie die IOMMU konfigurieren, wäre sinnlos, wenn ich das dürfte, so direkt ohne Rout, aber als Rout darf ich das, beziehungsweise das Betriebssystem darf das und wenn ich jetzt so ein System hab und vorbereiten will, dafür dass da ein User ohne, oder ein Treiber im User Space ohne Routrechte läuft, ist Schritt 1, ich bereite mein System als Rout vor, nehme das Device, statt den Treiber rauszuwerfen, bind ich den Treiber jetzt an so ne MagicViv-IO-Treiber, der einfach Linux sagt, das wird jetzt hier für IOMMU Zeugs genutzt. Dieses Teil kann ich dann mit CHO'n an den User weitergeben, dann sage ich dem User noch ein bisschen Speicher darf, was ich locken, irgendwie sage ich U-Limit, Lockmemory, maximal irgendwie 512 Megabyte, soll ich ja wohl reichen. Und dann der Treiber kann jetzt M-Map auf dieses VIV-IO- Device machen und darauf gibt es ne Menge IO-Control Befehle, die man aufrufen kann und da ist eben sowas drin, wie dass ich als unprivilegierter User sagen kann, hey, Colonel, mach mir ein IOMMU Mapping für Speicher, den ich hier hab, damit das Gerät auch drauf zugreifen kann. Der Colonel prüft gegen, dass ich mir hier jetzt keinen Scheiß mapen kann. Ich kann mir jetzt hier nur Sachen mapen, auf die ich Zugriff hab. Das handelt alles der Linux Colonel, das VIV-IO-Subsystem für mich, aber als Anwendung interessiert mich das danach eigentlich nicht mehr. Der Unterschied ist nur, wie ich meinen Speicher allokiere, weil das geht jetzt über so ein IO-Control-Score, dass ich mir den Speicher so allokiere und danach interessiert mich eigentlich nicht mehr. Ich muss mir nur meine, die Adresseübersetzung kann ich mir auch sparen, weil ich kann dem Gerät einfach sagen, selbe virtuellen Adressen benutzen wie mein Prozess, was so das üblichste ist, dann spare ich mir dann noch was, was Urtrechte gebraucht hätte und danach kann ich das weiß benutzen. Nur es wird jetzt alles überprüft von der IOMMU, ohne dass ich mich viel hab drum kümmern müssen. Es ist nur ein sehr frecklicher Code, das zu schreiben. Das war eine Masterarbeit der Student, der das implementiert hat. Der hat ziemlich geflucht über die ganzen schlecht dokumentierten Sachen und ja. Nun, Frage ist, wir haben jetzt irgendwie so eine Komponente eingeführt, ob das jetzt hier irgendwie Performance kaputt gemacht hat. Die Antwort ist ja und nein. Das hier ist irgendwie unsere vorwording Performance auf einem 1,6 Gigahertz System. Deshalb ist es irgendwo bei 15 Millionen auch schon so früh zu Ende. Prinzipiell hier ist so ein Standard Test. Man setzt die Batch Size hoch, weil wenn ich seltener der Netzwerkkarte sage, dass es mehr Pakete gibt, dann habe ich eine leicht höhere Latente, aber dafür bessere Performance. Da ich das nur machen muss, wenn ich e-viele Pakete habe, ist der Latentsverlust nicht so hoch. Das ist einfach nur, was sich an der X-Achse ändert. Aber das Spannende ist die drei Grafen, die wir sehen. Und die Baseline ist quasi das ohne IOMU, die schwarze Linie. Wir haben das für 2 Megabyte Pages und 4 Kilobyte Pages implementiert. Ich habe ja vorher schon gesagt, man braucht diese 2 Megabyte Pages mit ein bisschen Fingerspitzengefühl, vielen Hex und kann man es auch mit 4 Kilobyte Pages machen, ist aber nicht schön und kann unter Umständen, wenn das System falsch konfiguriert ist, das System auch zu schießen. Aber da gibt es auf jeden Fall keinen Performance-Unterschied, ob ich 4 Kilobyte oder 2 Megabyte Pages habe. Auch wenn man oft im Internet liest, die User-Space-Triber müssen 2 Megabyte Pages machen, weil Performance ist Quatsch. Es macht keinen Unterschied für einen normalen User-Space-Triber. Aber in dem Moment, in dem ich die IOMU aktiviere, habe ich da plötzlich einen Performance-Unterschied, ob ich 2 Megabyte Pages oder 4 Kilobyte Pages habe. Hier bei IOMU kann ich beides nutzen. Da gibt es nicht mal diese komischen Einschränkungen, die ich da ohne habe. Und der Grund ist einfach, warum ich das hier habe, ist, dass in der IOMU ist ein TLB auch wieder drin, wie in der IOMU. In der IOMU ist der TLB, das ist ein Cash für die ganzen Übersetzungen. Der hat 4096 Einträge üblicherweise in der IOMU. In der IOMU hat Intel das Vorsichtshalber mal nicht dokumentiert, wie viele Einträge da drin sind. Und Vorsichtshalber haben sie auch mal keine Performance-Counter dazu geschrieben, für wie viele Missles und so da gab. Grund ist, also nach unseren Erkenntnissen und Tests sind nämlich nur 64 Einträge drin. Und sobald ich mehr als 64 Pages irgendwie permanent zugreifen, bricht mir einfach die Performance ein. Deshalb muss ich da auf jeden Fall jetzt diese huge Pages benutzen, wenn ich das mache. Aber wenn ich das mache, habe ich einen Treiber, der super sicher ist im Sinne von, dass er mir nicht meinen Systemen zu schießt, wenn ich irgendwas falsch mache und dass ich einen unprivilegierten Prozess habe, der vollständig einen Treiber implementiert und meine Anwendung als normaler User laufen kann. Gut. Dann bleibt nur als Frage übrig, warum soll ich das den ganzen Spaß machen? Habe ich ja vorher alles schon eigentlich gesagt. Ja, warum nicht? Das ist schon spaßig, so was mal auszuprobieren und zu machen. Oder vielleicht braucht man einfach mal so ein Quicken-Dirty-Treiber für irgendein komisches Device. Vielleicht hat man sich grad irgendwie einen FPGA-Board zusammengelötet und braucht irgendwie einen Treiber für. Viele fangen dann an, mit Kernel-Modulen sich rum zu ärgern. Das muss einfach nicht sein, weil ich kann einfach hergehen und das das im User-Space machen, wenn es eben mein eigenes Device ist, was ich gerade teste. Und das erlaubt es einem wirklich schnell zu iterieren, weil ich kann den Code einfach starten. Ich kann mir eine kurze Shell schreiben in irgendwie Python oder so, die an irgendwelche Register hinstupst oder Register kurz dampen zum Debuggen ist deutlich weniger nervig. Und vielleicht habe ich irgendwelche komischen Features, die ich im original Kernel-Treiber nicht habe und die da erst später reinkommen. Anderes Beispiel für so ein Feature war diese Internet-Sverkarten, die wir da genutzt haben. Die haben zum Beispiel IPsec offloading, das haben wir irgendwann mal in dpdk reingepatched, noch lange bevor es irgendwo implementiert war. So einfach so als Prototyp zu schauen, funktioniert der Spaß. Keine Ahnung, der Intel-Treiber implementiert irgendwie dieses Feature nicht, was in der Netzwerkarte im Datenblatt als groß. Kann super IPsec offloading, total tolles Feature. Keiner von den Intel-Treibern implementiert es irgendwie. Keine Ahnung. Dann, das wurde dann später auch in dpdk so implementiert. Aber wir konnten das schnellen Prototyp machen und inzwischen wird es, aber es ist glaube ich inzwischen auch schon im Linux-Colonel-Mainline drin, das IPsec offloading da. Das war dann auch basierend auf unserer Implementierung, hatten wir ein bisschen Kontakt mit dem Entwickler, der das da rangefreckelt hat. Ja, also was kann man irgendwie sagen? Unser Treiber heißt irgendwie Xe, der ist auf GitHub. Man kann hier diesen QR-Codes kennen oder einfach Xe und GitHub googeln. Ganz Spaß sind nur mehr als in etwa 1000 Zahlen C-Code, je nachdem, wie man zählt. Der Code ist eigentlich größtenteils darauf getrimmt, dass er gut lesbar ist. Das heißt, da sind überall Kommentare drin, da sind so Links drin auf, das implementiert hier oder Kapitel 7, Abschnitt 5 von dem Datenblatt und so weiter, auch für Wirt.io sind überall die Referenzen drinnen. Und ich habe ja vorher die ganze Zeit schon gesagt, dass man irgendwie diese blöde Einschränkung mit man muss da ein Zeichen C programmieren hat. Wir haben auch Implementierungen in anderen Sprachen. Also wir haben den gesamten Treiber noch mal implementiert. Da habe ich mir so viele Studenten gesucht und wir haben denselben Code in Rust, Go, C-Sharp, O'Cammell, Haskell, Peisen und Zwift. Das sind jeweils so 1000 bis 3000 Zeilen Code. Es sind vollständige Treiber Implementierungen in diesen Sprachen. Wen das mir interessiert, da gibt es ein Talk auf dem Easter Hack von mir und einem Studenten, der sich mit dem Rust Treiber rum geärgert hat. Es gibt ein Talk auf dem 35 C3, wo ich mit zwei Studenten da auftauche, wo wir nur über diesen Programmiersprachen kramreden und was irgendwie Performance und was es für Probleme gibt, wenn ich eben nicht C nehme. Wir haben ja vorher so komische Sachen mit Wurlertailpoint an gesehen, die ich jetzt in manchen Sprachen zum Beispiel gar nicht habe. Ja, dann die Idee ist prinzipiell, Treiber sind eigentlich viel einfacher, als sie aussehen. Man sollte da jetzt nicht zu viel Angst vorhaben und man kann hier relativ easy schreiben. Man muss nicht irgendwie Colonel Code schreiben. Und ja, ich glaube, wir haben noch irgendwie 10 Minuten für Fragen etwa. Also Danke für die Aufmerksamkeit. Zeit für Fragen. Ja, dann schon mal vielen Dank, Paul. Hat denn jemand eine Frage dazu? Moment. Ich wollte nur fragen, ob mir dich nachher irgendwo findet, weil sonst sprengt das den Rahmen, glaube ich. Dekt 80 80. Ich wollte nur fragen, wie sieht es mit Echtzeit aus? Also Echtzeit 3 beim User Space, wenn es nicht um den Paketdurchsatz geht, sondern dass ich halt gar keinen Buffer mehr dazwischen habe. Ja, wir haben Histogramme von, also wir haben irgendwo so HDR-Histogramme von Latentsverteilungen. Wie viel da Sachen dazwischen sind, wie viel Latents wir haben. Da ist eigentlich kein Unterschied zum User, zum Colonel-Treiber. Man muss hauptsächlich oder was man machen kann, ist man kann im Genuss Colonel sagen, diese CPU bitte nichts anderes drauf schedulen als das. Und auch, man kriegt leider nicht alle Interrupts los, aber es ist zuverlässiger als den Spaß im Colonel zu haben, wenn man die richtigen Optionen beim Buten setzt. Okay, noch Fragen? Ja. Wie schlimm sehen die Erraterlisten in den Datenblättern aus? Und das eine Seite, Nullseiten, Sehenseiten? Es ist eigentlich relativ harmlos. Also die die Intelsachen, da findet man eigentlich keine schlimmeren oder die älter die Netzwerkarte, desto weniger fehler, je neuer die Netzwerkarte, desto längere Erraterlisten. Vor allem, wo ich mich sehr geärgert habe auf den XL7 10-Karten, haben sie mir irgendwann mal mein geliebtes Timestamping zerstört. Und da stand da so ein Errater Schied, ach, in der Firmware-Version geht das Timestamping übrigens nicht ups. Aber die die alten oder alt was halt. Die älteren Modelle von den 10-Gigabit-Karten, die sind eigentlich sehr zuverlässig. Das sind die schlimmsten Fehler, die man mal findet, dass irgendwie eine Registeradresse falsch ist oder dass der Initialwert in einem Register falsch ist oder dass ein Register fehlt bei einem Modeller, was trotzdem da ist und so was. Gut, noch Fragen? Wie sieht es mit der Performance auf 40-Gear bei unserer 100-Gear-Karten aus? Ja, das ist so eine Frickelsache immer, weil da die bottlenecks irgendwo in der Hardware immer hängen. Bei den 40-Gear-Bit-Karten geht das los, nervig zu sein. Da muss man dann schon mehrere Queues benutzen, obwohl man nicht an der CPU limitiert ist. Und die K, also diese ganzen Karten werden auf das Dual-Port verkauft. Da steht da irgendwo ein ganz kleiner Ball. Ja, aber nicht beide Ports gleichzeitig benutzen, weil erstens ist der PCIe-Bus nicht so dick, wie wir die beiden Ports gemacht haben. Und zweitens ist da intern so ein Switch drauf, der nur maximal X-Gigabit drauf bekommt. Da läuft man eigentlich meistens in irgendwelche komischen Hardware-Limits. Aber auf so einer 100-Gigabit-Karte kriegt man eigentlich je nach Karte, Mellanox-Karten so um die 100-Millionen Pakete pro Sekunde durch von den theoretischen 150-Millionen, bevor man in irgendwelche komischen Hardware-Limits läuft. Auch andere spannende Hardware-Limits, in die man läuft, sind einfach, dass auf dem PCIe-Express-Bus selber ja noch so ein Protokoll gesprochen wird, was so ähnlich wie TCP ist und so ein bisschen Flow-Control macht und Congestion-Windows hat und die können volllaufen und ekelhaft. Gut. Noch immer eine Frage. Alle Fragen erklärt. Dann nochmal Applaus für Paul. Vielen Dank. Danke.