Tag-Archiv für 'Programmierung'

Speicheroptimierung mit pahole

Bei der Nutzung der Programmiersprache C ist der Softwareentwickler neben den gängigen Optimierungen durch den Compiler zum Großteil selbst für die Optimierung des Laufzeitverhaltens eines Programms und des Speicherbedarfs der Datenstrukturen zuständig. Im folgenden Artikel wird zur Unterstützung bei der Speicheroptimierung das Linux-Tool pahole vorgestellt.

Werden bei der Programmierung in C zusammengesetzten Datentypen wie struct verwendet ist es möglich, dass aus Gründen des Alignments zwischen den Teilelementen der Struktur nicht genutzte Füllbytes eingefügt werden. Zurückzuführen ist das Einfügen dieser Füllbytes auf die Rechnerarchitektur und die Art und Weise wie ein Prozessor auf den Hauptspeicher zugreift. Wenn ein Prozessor Daten aus dem Hauptspeicher liest, dann erfolgt der Zugriff mit der Verarbeitungsbreite des Prozessors. Ist der Prozessor ein 32-bit Prozessor, dann werden in einem Schritt 4 Byte gelesen. Diese Annahme gilt für alle weiteren Beispiele.

Ein Datenelement ist dann richtig im Speicher angeordnet, wenn seine Speicheradresse einem ganzzahligen Vielfachen seiner Größe entspricht. Für diesen Fall kann der Prozessor das Datenelement optimal einlesen (ohne Überlappung und notwendige Verschiebung) und das Datenelement wird als aligned bezeichnet. Werden nun Elemente mit unterschiedlichen Speichergrößen wie unsigned short (2 Byte), unsigned char (1 Byte) oder unsigned int (4 Byte) in einem Verbunddatentyp gemischt verwendet ist es möglich, dass Füllbytes eingefügt werden um das Alignment zu gewährleisten.

Zur weiteren Erklärung soll nun folgende Datenstruktur betrachtet werden:

struct test_data {
        unsigned short alpha;
        unsigned char beta;
        unsigned int gamma;
        unsigned char delta;
};

In dieser Datenstruktur wird zwischen den Elementen beta und gamma ein Füllbyte eingefügt um gamma entsprechend den Alignment-Anforderungen auszurichten. Das Füllbyte ist notwendig, da alpha und beta zusammen 3 Byte, also beispielhaft die Adressen 0 bis 2 belegenen und gamma als Datentyp mit 4 Byte Speicherbedarf auf Adresse 4 ausgerichtet wird. Die Adresse 3 bleibt dann ungenutzt und geht als verfügbarer Speicher verloren. Diese Löcher in den Strukturen sind nicht optimal und können teilweise durch Umordnen der Elemente entfernt werden. Das manuelle Berechnen dieser Löcher ist zwar möglich, durch geeignete Werkzeuge allerdings auch automatisierbar.

Das Linux-Werkzeug pahole (Poke-a-Hole) ist ein Hilfsmittel um Datenstrukturen zu analysieren, Alignement-Löcher zu identifizieren und Umordnungsvorschläge anzugeben. Ziel ist die Reduzierung von Strukturgrößen im Speicher und dadurch die Reduzierung des Speicherbedarfs von Programmen während der Laufzeit. Ein wichtiger Effekt ist außerdem die bessere Ausnutzung von Caches und dadurch eine Beschleunigung des Programms bei Speicheroperationen. Durch die Entfernung der Füllbytes entsteht eine geringere Belastung des Speicherbusses.

Da pahole kein Tool zur statischen Quellcodeanalyse ist, sondern den mit Debug-Informationen ausgestatteten Object-Code untersucht, sind neben dem Build-Werkzeug cmake zum Kompilieren noch weitere Bibliotheken zur Unterstützung des DWARF2-Debug-Formates notwendig. Die Installation erfolgt unter Ubuntu mit:

sudo aptitude install libdw-dev libelf-dev cmake

Der Programmcode von pahole ist aufgrund des Ursprungs in der Linux-Kernel-Entwicklergemeinde im Kernel-Git-Repository verfügbar, man erhält den Quellcode mit folgendem Befehl:

git clone git://git.kernel.org/pub/scm/linux/kernel/git/acme/pahole.git

Unter Ubuntu 9.10 Karmic Koala ist unter Umständen wie in diesem Bugreport beschrieben vor dem Kompilieren noch ein Patch notwendig. Dabei ist der find_library Ausdruck für EBL_LIBRARY in cmake/modules/FindDWARF.cmake durch set (EBL_LIBRARY -ldw) zu ersetzen.

Das Kompilieren und die Installation erfolgt dann im Verzeichnis pahole mit den Befehlen:

mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
sudo make install

Das zu untersuchende Programm hello oder Object-File hello.o ist dann mit dem Compiler im Debug-Modus, also gcc -g zu kompilieren. Der Befehl

pahole hello

beziehungsweise

pahole hello.o

gibt dann einen Bericht in folgender Art für alle verwendeten, nicht-anonymen Strukturen aus:

struct test_data {
	short unsigned int         alpha;        /*     0     2 */
	unsigned char              beta;         /*     2     1 */
 
	/* XXX 1 byte hole, try to pack */
 
	unsigned int               gamma;        /*     4     4 */
	unsigned char              delta;        /*     8     1 */
 
	/* size: 12, cachelines: 1, members: 4 */
	/* sum members: 8, holes: 1, sum holes: 1 */
	/* padding: 3 */
	/* last cacheline: 12 bytes */
};

Der hinzugefügte Kommentar enthält für jedes Element die Adresse in Bezug auf den Anfang der Struktur und die Größe des Elementes. Zusätzlich werden Löcher in den Strukturen und die Gesamtgröße der Struktur angegeben.

Neben der Identifikation der ungenutzten Speicherbereiche kann mit dem Befehl

pahole -RC test_data hello

auch ein Hinweis für die Reorganisation der Elemente ausgegeben werden. Im vorhandenen Beispiel wäre dies die Anordnung des Elementes delta nach beta und vor gamma um das Loch von einem Byte zu füllen.

Abschließend bleibt anzumerken, dass die Relevanz dieser Optimierungen auf modernen 64-bit Architekturen höher ist, da durch die größere Verarbeitungsbreite Löcher in den Strukturen häufiger auftreten und diese auch größer sind.

Diplomarbeit

Seit heute schreibe ich offiziell an meiner Diplomarbeit. In den nächsten vier Monaten werde ich das Thema “Entwicklung einer Prüfsoftware auf einer embedded i.MX31-Plattform mit Betriebssystem Linux” bearbeiten. Es geht darum, für ein Embedded-Board mit i.MX31-Mikrocontroller und sehr vielen unterschiedlichen Schnittstellen (CAN, Ethernet, USB als Host und als Client, mehrere Displayschnittstellen) und Onboard-Peripherie (verschiedene Flashspeicher, RTC und produktspezifische Schaltkreise) eine Software zu schreiben, die alle Komponenten ansteuert und auf Funktionalität testet. Das Ganze passiert in der Nähe von Ravensburg, deswegen wohne ich auch seit sechs Wochen zeitweise in Weingarten.

Chumby

Chumby (Bilder hier), ein kompaktes Gerät das sich wie ein Wecker verhalten kann, ist ein Eingebettetes System mit WLAN-Anschluss. Das Hardware-Layout für das Gerät ist frei und sollte bei Anmeldung zugänglich sein. Leider wird in den Bedingungen der Anmeldung zu viel von Payment geredet, so das ich mich nicht angemeldet habe.

Hauptzweck des Gerätes soll sein, kleine Applikationen, die in Adobe Flash entwickelt, sind anzuzeigen. Beispielapplikationen sind ein Wecker, Wetteranzeige, Flickr-Bilder, Google-News usw.

Technisch setzt das Gerät auf einen Freescale iMX21 266MHz ARM9 controller, hat 32MB SDRAM, 64 MB NAND FLASH, einen 320×240 3.5-inch Touchscreen mit 12Hz, Stereo 2W Lautsprecher, einen Audio-Ausgang und Mikrophone-Eingang, einen USB-Anschluss und eine WLAN-Karte.

Auf dem Gerät läuft Linux 2.4.20, eine Toolchain und die Kernelquellen sind auch verfügbar. Weitere Experimente habe ich noch nicht durchgeführt, da ich keine Hardware besitze. Chumby Industries scheint aber Probeexemplare unter das Volk zu bringen oder bringen zu wollen, auf der Seite gibt es ein entsprechendes Angebot für “alpha-geek hacker”

Was mir bei Analyse der Chumby-Seiten fraglich geblieben ist, ist das Format der Images für Chumby und das integrierte System zum Digital-Rights-Management. Ziel ist wohl, einen Abomechanismus mit dem Gerät zu verkaufen.

Irgendwann im März soll es das dann auch zu kaufen geben. Das könnte durchaus ein Hype werden, wenn der angestrebte Preis von 150$ erreicht wird und es auch ohne Abo geht.

Serielle Schnittstelle am AVR Butterfly

Um die serielle Schnittstelle am AVR Butterfly nutzen zu können ist nicht viel Arbeit erforderlich. Ein serieller Pegelwandler von Low-Voltage-Seriell auf den höheren Pegel an der seriellen Schnittstelle eines PCs ist bereits auf dem Butterfly integriert.
Es bietet sich an den Butterfly mit Stiftleisten zu bestücken um Kabel einfach anschließen zu können. Ich habe ein paar günstige Bauteile von Reichelt genutzt, dieses Material gibt es aber auch bei anderen Elektronik-Händlern.

  • 3 Stifte einer 36-poligen, einreihigen geraden Stiftleiste mit Rastermaß 2,54 (SL 1X36G 2,54)
  • eine 3-poligen Platinensteckverbinder gerade, weiss (PS 25/3G WS)
  • D-SUB-Buchse, 9-polig, Lötkelch zum PC-Anschluss (D-SUB BU 09)

Das ganze ist schnell verlötet, die PIN-Belegung ist aus der Grafik ersichtlich und ist im User-Manual des Butterfly dokumentiert. (RXD an Pin 3, TXD an Pin 2 und GND an Pin 5)

butterfly_serial_1

butterfly_serial_2

Das Ganze kann man dann in der Beispielanwendung bei der Namenseingabe mit dem Terminalprogramm Minicom testen. Die genaue Vorgehensweise ist unter Punkt 2.2.2 des Usermanual beschrieben. Zur Kommunikation unter Linux stellt man am besten als Supernutzer den Default von Minicom mit

minicom -s

unter “Serial Port Default” auf Serial Device /dev/ttyS0, Communication Parameter auf 19200 8N1 und keine Hardware- und Software-Flusskontrolle. Das ganze speichert man als Default dfl. Nun kann man entsprechend der Anleitung einen ersten Test der Kommunikation durchführen, der eingegebene Name sollte auch wenn kein Echo im Terminal eingestellt ist oder vom Butterfly zurückgegeben wird nach Bestätigen mit Enter auf dem Display erscheinen.

Asus WL-500g Premium mit OpenWrt Kamikaze

Ich habe zu Testzwecken einen WLAN-Router von Asus erworben. Es handelt sich um das Modell Asus WL-500g Premium mit 8 MB Flash-Speicher und 32 MB RAM sowie zwei USB-Ports. Preislich liegt das Gerät bei ungefähr 70 Euro bei Ebay. Der WLAN-Router lässt sich mit OpenWRT unter Linux betreiben.

asus_wl_500g_premium

Betrieben wird das Gerät von mir unter der neuen Entwicklungsversion von OpenWrt mit dem Namen Kamikaze.
Die Quellen dafür bekommt man durch einen Checkout aus dem Subversion-Repository.

svn co https://svn.openwrt.org/openwrt/trunk/

Anschließend führt man im trunk/-Verzeichnis die Konfiguration des Firmware-Build-Prozesses mit

make menuconfig

aus und baut die Firmware mit

make

Um den Router zu Flashen benötigt man einen tftp-client, es wird atftp empfohlen. Unter Gentoo installiert man

emerge atftp

Nun muss man als Erstes den Router in den Diagnose-Modus versetzen:

  1. Entfernen der Versorgungsspannung.
  2. Drücken des RESET-Tasters (nicht der rote EZSETUP) mit einem Stift/Feinschraubenzieher während des Einschaltens für ein paar Sekunden.
  3. Loslassen des RESET-Tasters.

Man erkennt das der Router im Diagnosemodus ist daran, dass die POWER-LED langsam blinkt. Der Router befindet sich nun im Bootloader CFE (Common Firmware Environment). Nun führt man folgendende Eingaben in dem Verzeichnis durch, in dem das Image der Firmware liegt ( z.B. trunk/bin )

# atftp
connect 192.168.1.1
mode octet
trace
timeout 1
put openwrt-brcm-2.4-squashfs.trx

Das Image wird jetzt auf den Router übertragen.

Achtung: Nach Übertragung muss der Router noch mindestens 6 Minuten eingeschaltet bleiben, da das Image erst nach dem übertragen in den Flash geschrieben wird. Der Router startet nach Beenden des Schreibvorgangs nicht automatisch neu: Man wartet also lieber ein wenig länger und führt dann einen Reset durch.

Nun sollte man sich mit telnet auf 192.168.1.1 verbinden können und mit passwd ein root-Passwort für die Nutzung von ssh setzen können. Das Default-Passwort für root ist nicht gesetzt, der Zugang über ssh funktioniert aber erst nach dieser Aktion.
Wenn alles funktioniert bekommt man einen Prompt und ein kleines eingebettes Linux-System für wenig Geld:

# uname -a
Linux OpenWrt 2.4.34 #2 Thu Feb 15 20:47:24 CET 2007 mips unknown

Um den vollständigen Leistungsumfang des Gerätes nutzen zu können muss man noch ein paar spezielle Aktionen durchführen, zum Beispiel muss noch die volle Menge des Speichers nutzbar gemacht werden.

Arithmetik ohne Begrenzungen

Selbst wenn man nur einfache Probleme in Theoretischer Informatik, Zahlentheorie oder Diskreter Mathematik bearbeitet und durch Programme in C zu erschließen versucht, gelangt man schnell an die Grenzen der darstellbaren Wertebereiche für ganze Zahlen. Das Implementieren einer eigenen Bibliothek für große Zahlen ist zwar möglich, mit GMP (GNU Multi Precision Arithmic Library) gibt es aber bereits eine freie und gut gepflegte Implementierung.

Eine einfache, experimentelle Erkundung des Syracuse-Problems könnte so aussehen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
 
int main()
{
    int a;
 
    scanf("%d", &a);
 
    while (a != 1) {
	printf("%d ", a);
	a = syracuse(a);
    }
 
    printf("1\n");
 
    return 0;
}
 
int syracuse(int n)
{
    if (n % 2) {
	return 3 * n + 1;
    } else {
	return n / 2;
    }
}

Mit der GNU MP Bignum Library wird daraus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <gmp.h>
 
void mpz_syracuse(mpz_t n);
 
int main()
{
    mpz_t a;
    mpz_init(a);
 
    mpz_inp_str (a, NULL, 0);
 
    while ( mpz_cmp_si(a,1) ) {
	mpz_out_str (NULL, 0, a);
	printf("\n");
	mpz_syracuse(a);
    }
 
    printf("1\n");
 
    return 0;
}
 
void mpz_syracuse(mpz_t n)
{
    if (mpz_divisible_ui_p(n,2)) {
	mpz_divexact_ui (n, n, 2);
    } else {
	mpz_mul_ui (n, n,3);
	mpz_add_ui (n, n, 1);
    }
}

Damit kann man dann auch für sehr große natürliche Zahlen die Zahlenfolge bestimmen, kompiliert wird wie gewohnt, allerdings muss man mit -lgmp linken.

syracuse

AVR Butterfly

Der AVR Butterfly ist eine Evaluationsumgebung für den Mikroprozessor ATmega169 von Atmel.

avrbutterfly_front

Die Ausstattung an Schnittstellen und Sensor ist für so ein kleines und günstiges System recht beachtlicht. Für um die 20 Euro bekommt man ein 6-stelliges LCD, einen 4-Wege-Taster mit Druckknopf, 512 kByte Flash-Speicher, Licht- und Temperatursensor, einen Piezo-Schallwandler und einen Zugang über serielle Schnittstelle (mit Pegelwandler auf dem Board). Als weitere Kommunikationspfade mit dem System kann man JTAG, SPI und I2C nutzen. Zudem arbeitet der Butterfly sehr stromsparend und ist mit einer Lithium-Batterie autark spannungsversorgt.

Das System ist in C programmierbar, einen entsprechenden Compiler und Werkzeuge zur Datenübertragung auf das System gibt es auch für Linux.

Statische Codeanalyse mit Uno

Statische Codeanalyse ist eine Art von Softwaretest, bei der Quellcode bestimmten formalen und algorithmischen Prüfungen unterzogen wird um Fehler in Software zu finden. Die Überprüfung erfolgt hier im Gegensatz zur dynamischen Analyse nicht zur Laufzeit und wird deswegen als statische Analyse bezeichnet.

Uno ist ein Tool, mit dem man nach drei der häufigsten Fehlerarten in C-Quellcode suchen kann:

  • Nutzung uninitialisierter Variablen
  • Dereferenzierung von NULL-Pointern
  • Bereichsüberschreitung bei der Array-Adressierung

Entsprechende Beispiele und ein Download des Werkzeuges sind auf der Seite von Uno zu finden. Eine kurze Probe hier hat gezeigt, daß sich sowohl einfache Beispiele konstruieren lassen bei dem Dereferenzierungsfehler gefunden werden, als auch Beispiele, bei dem das Werkzeug die Fehler übersieht.

Hier wird der Fehler gefunden:

1
2
3
4
5
6
7
8
9
int *ptr;
 
int main() {
    if (ptr)
        *ptr = 123;
    else
        *ptr = 345;
    return 0; 
}
uno:#1: possible global use or deref before def:
[R ptr global_nullptr_deref.c 9]
in fct main global_nullptr_deref.c 4

und hier nicht:

1
2
3
4
5
6
7
8
9
10
11
int main() {
 
    int *ptr;
    ptr = NULL;
 
    if (ptr)
        *ptr = 123;
    else
        *ptr = 345;
    return 0;
}

Generell ist die quelloffene Verfügbarkeit von leistungsfähigen Werkzeugen zur Codeanalyse nicht gegeben, die Werkzeuge übersehen Fehler oder liefern zuviele Falschpositive. Professionelle Tools wie Coverity stehen wiederum nicht zur freien Verfügung. Wer sich mehr mit diesem Thema beschäftigen will, findet allerdings auf der Seite von Dawson Engler, dem Gründer von Coverity, einen guten Einstiegspunkt mit ein paar interessanten Veröffentlichungen, auch zur probalistischen Analyse von Quellcode.

Microcontroller-Bastelei

Hardware-Bastelei und Programmierung liegt ja zur Zeit total im Trend. Die einen basteln Fnordlichter, manche spielen mit dem Butterfly oder der halvedDisc, wieder andere bauen Mini-Roboter wie den Asuro.

atmega8

Allem gemeinsam ist, dass die Intelligenz der Geräte durch Microcontroller verwirklich wird. Bei mikrocontroller.net findet man gute Dokumentation, unter anderem ein umfassendes Tutorial zur Atmel-Programmierung in C.

Unmaintainable Code

#include <stdio.h>
float o=0.075,h=1.5,T,r,O,l,I;int _,L=80,s=3200;main(){for(;s%L||
(h-=o,T= -2),s;4 -(r=O*O)<(l=I*I)|++_==L&&write(1,(--s%L?_<L?--_
%6:6:7)+"123456 \n",1)&&(O=I=l=_=r=0,T+=o/2))O=I*2*O+h,I=l+T-r;}

Wem so etwas Spaß macht, sollte einfach mal das Unmaintainable Code Howto von Roedy Green anschauen.