Optimale C-Bibliotheken für Embedded Linux

© Glen Jones, Fotolia.com

Embedded-Entwickler zirkeln um jedes Byte. Da kommen abgespeckte C-Bibliotheken wie UC-Libc und Dietlibc, schlankere Standard Template Libraries für C++ sowie GNU-Tool-kompatible Zwerge genau recht.

Die C-Bibliothek ist nach dem Kernel die maßgebliche Komponente jedes Server-, Desktop- oder Embedded-Linux-Systems. Sie bestimmt das Systemverhalten, den Funktionsumfang, die Stabilität und auch die Gesamtgröße. Die C-Bibliothek setzt auf dem Kernel auf und implementiert grundlegende Features, um Informationen mit dem Kernel auszutauschen und so Userspace-Programmen dessen Funktionalität bereitzustellen.

Dies sind zum einen Ansi-C-Funktionen wie »fopen()«, »exit()«, »free()«, »fread()«, »fwrite()« oder »malloc()«. Zum anderen etabliert die Bibliothek Funktionen, die zur C-Runtime gehören und nichts mit dem Kernel zu tun haben, etwa die String- und andere Speicher-Funktionen wie »strcpy()«, »strcmp()«, »memcpy()« und »memcmp«. Posix fordert für Unix-artige Systeme zudem Funktionen wie »open()«, »read()«, »write()«, »join()«, »fork()« oder »kill()«.

Nachdem der Kernel den Code eines Userprogramms in den Speicher geladen hat, beginnt die Arbeit der C-Bibliothek. Sie ist dafür zuständig, Datenstrukturen zu initialisieren, benötigte dynamische Bibliotheken zu laden und schließlich die »main()«-Funktion zu starten. Äquivalent dazu ruft sie beim Beenden eines Programms mit »exit()« die durch »atexit()« registrierten Funktionen auf. Ist C++ im Spiel, gilt es auch, die in C++ definierten statischen Konstruktoren und Destruktoren anzuspringen.

Kernelroutinen nutzen

Um eine Funktion des nackten Kernels in Anspruch zu nehmen, muss das System die Nummer des entsprechenden Systemaufrufs in vorgegebene CPU-Register laden und die Argumente in andere dafür vorgesehene Register beziehungsweise in den Speicher schreiben. Eine Architektur-spezifische Funktion übergibt danach die Kontrolle an den Kernel. Auf Intel-386-Systemen ist das der Software-Interrupt 0x80.

Neuere Intel-, AMD- und kompatible Prozessoren besitzen für den gleichen Zweck auch spezielle Sysenter-/Sysexit- beziehungsweise Syscall-/Sysret-Befehlskombination. Wie bei einem Software-Interrupt überantworten sie die weitere Befehlsausführung an den dafür registrierten Kernelcode. Der Vorteil der dafür dedizierten Instruktion liegt im geringeren Overhead im Vergleich zum klassischen Software-Interrupt – das spart pro Kontextswitch CPU-Zyklen. Als Äquivalent dazu verfügen Risc-CPUs über Trap-Instruktionen. Jede C-Bibliothek implementiert solche Hardware-spezifischen Details. Wie beim Startup- und Exit-Code gelingt das nur in Assembler.

Die auf Desktop- und Server-Systemen für Linux eingesetzte Glibc [1] hat den größten Funktionsumfang und wartet mit allerlei Performance-Optimierungen auf. Neben Linux unterstützt die Bibliothek auch Hurd, BSD- sowie klassische Unix-Systeme. Die Glibc hat jedoch mit über 1 MByte (auf I-386) eine stattliche Größe und disqualifiziert sich somit von vornherein für Systeme mit wenig RAM. Einen Ausweg aus dieser Misere bieten UC-Libc [2] und Dietlibc [3]. Beide freien Implementierungen sind extra für Linux-Embedded-Systeme entstanden.

UC-Libc

Die etwas ältere UC-Libc entstand anfangs speziell für den Dragonball-Prozessor aus der 6800er Familie von Motorola sowie den ARM-Coldfire-Mikrocontroller und besaß darum keinen Code für Memory Management Units (MMU). Heute unterstützt UC-Libc auch reguläre Linux-Systeme mit Speicherverwaltungseinheit, nach wie vor aber auch solche ohne – ein Vorteil.

Teilweise basiert die kleine C-Bibliothek auf dem Quellcode der Glibc, teilweise auf anderen Bibliotheken. So erreichte die UC-Libc schnell einen großen Funktionsumfang und bedient CPU-Architekturen wie AVR32, Blackfin und viele andere mehr. Mit weniger als 300 KByte (für I-386) ist die UC-Libc deutlich kleiner als die Glibc.

Die Von-Leitner-Diät

Einen anderen Weg beschritt Felix von Leitner mit seiner Dietlibc. Er entwickelte die Bibliothek von Grund auf und mit möglichst wenig Code. Heraus kamen entsprechend kleine Binärdaten. Im Gegenzug bietet die Dietlibc (absichtlich) weniger als Glibc und UC-Libc, so fehlen einige nicht standardisierte Funktionen. Vor allem im »wide«-Charakter und beim Locale-Support klaffen Lücken.

Dafür kann man einige BSD- und GNU-Erweiterungen beispielsweise mit Defines aktivieren, denn Leitner ist es wichtig, mit Standardeinstellungen die Entwicklung portabler Software zu fördern. Das explizite Aktivieren dieser Erweiterungen soll verhindern, dass der Entwickler versehentlich unportable Features verwendet, die nur auf BSD- oder GNU-Systemen verfügbar sind.

Mit weniger als 150 KByte bei I-386 braucht die Dietlibc am wenigsten Speicher. Die Bibliothek spart beispielsweise an der »SYSCALL«-Implementierung, realisiert über vereinheitlichte (unified) Funktionen, und ist auf statisches Linken optimiert. Der Overhead pro Programm gerät so besonders gering, bei der Umsetzung hat von Leitner darauf geachtet, nur tatsächlich verwendete Bibliotheksbestandteile in das resultierende Programm zu linken. Der Fokus auf statische Programme bringt jedoch den Nachteil mit sich, dass dynamisches Linken nur für die Architekturen I-386, x86-64 und ARM funktioniert.

Als Vorteil der Dietlibc dagegen gilt, dass es sehr einfach ist, sie zur Evaluierung und Entwicklung parallel zu einer anderen C-Bibliothek zu installieren. Anders als die UC-Libc, die der Embedded-Entwickler am besten als Teil einer Toolchain betreibt [4], kann er die Dietlibc einfach mit »make« bauen und sofort Programme für sein Entwicklungssystem übersetzen und crosskompilieren:

./bin-i386/diet gcc hello.c

Das Wrapper-Programm »diet« übergibt dabei Dietlibc-Optionen sowie Startup- und Bibliotheksdateien an den Compiler.

Das Gleiche für C++

Für die Standard Template Library (STL, [5]) existieren ähnlich Speicher sparende Implementierungen. In der Hauptsache sind dies die zur UC-Libc passende UC-Libc++ [6] sowie die Bibliothek Embedded STL [7], die aber noch im Alphastadium steckt. Als Außenseiter gesellt sich die U-STL [8] hinzu, eine kleine Implementierung, die teils nicht standardkonforme APIs definiert und deswegen den Namensraum U-STL statt STD verwendet. Der resultierende Code ist dann leider nicht portabel. Damit eignet sich die U-STL primär für neu zu implementierende reine Embedded-Projekte.

Kleine Helfer

Im Kampf gegen überflüssige KBytes bietet es sich weiterhin an, die Shell und Standard-Unix-Tools wie »awk«, »find«, »grep« oder »sed«, die in vielen Skripten vorkommen, durch Minimal-Implementierungen zu ersetzen. An die Stelle ausgewachsener GNU-Programme treten speziell auf geringe Größe optimierte Varianten, entweder aus dem Busybox-Projekt (Abbildung 1, [9]) oder Solisten wie Embutils [10] und Minised [11].

Abbildung 1: Das Busybox-Projekt liefert größenoptimierte Klone der meisten wichtigen Linux-Tools.

Abbildung 1: Das Busybox-Projekt liefert größenoptimierte Klone der meisten wichtigen Linux-Tools.

Diese Tools besitzen zwar nicht alle GNU-Erweiterungen und können nicht mit Unicode umgehen, bereiten dem Entwickler für seine Arbeit aber eine gewohnte Unix-Umgebung und bieten Wartungsskripten auf Ressourcen-armer Hardware eine oft willkommene Grundlage. Ganz auf Tools verzichten kann aber manchmal ebenfalls sinnvoll sein, beispielsweise um Angreifern das Modifizieren des Zielsystems zu erschweren.

Schlankmacher

Eine kleine C-Bibliothek auf einem eingebetteten System spart wertvollen Speicher. Erst die UC-Libc oder die Dietlibc machen Linux-Systeme mit deutlich unter 8 MByte möglich. Sortiert der Entwickler gewissenhaft nicht benötigte Funktionen aus Kernel und Bibliotheken aus, sind auch Minimalsysteme unter 2 MByte für Kernel und Anwendungen kein Problem. (Heike Jurzik, jk)

Infos

[1] Glibc: [http://www.gnu.org/software/libc/]

[2] UC-Libc: [http://www.uclibc.org]

[3] Dietlibc: [http://www.fefe.de/dietlibc/]

[4] René Rebe, “Workshop: Crosscompiling für Embedded-Systeme”: Linux-Magazin 04/08, S. 122

[5] STL: [http://de.wikipedia.org/wiki/Standard_Template_Library]

[6] UC-Libc++: [http://www.gnu.org/software/libc/]

[7] Embedded STL: [http://www.exactcode.de/site/open_source/embeddedstl/]

[8] U-STL: [http://ustl.sourceforge.net]

[9] Busybox: [http://www.busybox.net]

[10] Embutils: [http://www.fefe.de/embutils/]

[11] Minised: [http://www.exactcode.de/site/open_source/minised/]

Der Autor

René Rebe ist einer der Geschäftsführer der Exact Code GmbH in Berlin und durch die tägliche Arbeit mit Linux in diverse Open-Source-Projekte involviert.

E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben