Bis vor kurzem war die Welt noch einfach: Alle gängigen PCs waren mit einem Prozessor der Pentium-Klasse bestückt, deren Registerbreite stets 32 Bit beträgt. Zwar unterstützt der Linux-Kernel schon seit Jahren auch andere Architekturen. Aber Hand aufs Herz: Auf der Mehrheit der Maschinen prangt ein Intel- oder ein AMD-Logo.
64 statt 32 Bit
Die Sache mit dem Logo hat sich seitdem nicht grundlegend geändert, doch bei der Registerbreite hat AMD mit der ursprünglich AMD64 genannten Technik einen Sprung getan. Intel folgte mit der kompatiblen Technik EMT64 nach, sodass neue Rechner heute problemlos mit 64 Bit rechnen.
Beim Datentyp »long« kommt der wesentliche Unterschied zwischen 32- und 64-Bit-Technik zum Tragen: Er umfasst auf einem 32-Bit-System tatsächlich 32 Bit, auf dem 64-Bit-System 64. Anders beim Datentyp »int«: Dass er auf einem 64-Bit-Rechner ebenfalls 64 Bit umfasst, ist ein weit verbreiteter Irrglaube. Auf beinahe allen Plattformen ist »int« 32 Bit breit.
Spürbar wird dieser Unterschied dann, wenn ein Programmierer sich unbedacht »unsigned int« als Datentyp für einen so genannte neutralen Parameter wählt, dessen wirklicher Typ sich erst beim Aufruf entscheidet. Die meisten Kernelprogrammierer setzen dafür entweder den Typ »unsigned long« oder »void *« ein. Der entscheidende Punkt ist, dass der Datentyp »unsigned int« mit seinen 32 Bit auf einem 64-Bit-System nicht der Breite des Adressbusses entspricht. Während auf einer 32-Bit-Maschine keine Probleme auftreten (siehe Abbildung 1), ist es fatal, unter einem 64-Bit-Linux die Warnung des Compilers zu übersehen (Abbildung 2).
Abbildung 1: Ausgabe des Programms von Listing 1 auf einer 32-Bit-Maschine. Beide Adressen sind wie erwartet identisch.
Abbildung 2: Ausgabe des Programms von Listing 1 auf einer 64-Bit-Maschine. Der Compiler warnt bereits bei der Übersetzung vor Problemen, die beim Ablauf auch prompt auftreten.
Listing 1 zeigt beispielhaft, wie ein derartiger, nicht portierbarer Code aussehen kann. Auf einem 64-Bit-System gehen beim Casting einer 8 Byte belegenden Adresse (»&var«) auf einen »int« (Zeile 14) die oberen 4 Bytes verloren. Die Abbildungen 1 und 2 zeigen die zugehörigen Ausgaben auf einer 32- und auf einer 64-Bit-Maschine. Während bei einer Portierung von 32 Bit auf 64 Bit das geschilderte Problem virulent ist, kann es im umgekehrten Fall zu Bereichsüberschreitungen kommen. Ein Beispiel ist der Jiffies-Zähler vom Typ »unsigned long«: Während die knapp 200 Tage bis zum Wrap-Around auf dem 32-Bit-System innerhalb der gesetzlichen Gewährleistungspflicht liegen, brauchen Besitzer von 64-Bit-Maschinen sich erst nach gut 2000 Milliarden Jahren Sorgen zu machen.
01 #include <stdio.h>
02
03 void print_para_adr( unsigned int neutraler_parameter )
04 {
05 printf("Adresse 2: %pn", (char *)neutraler_parameter);
06 }
07
08 int main( int argc, char **argv )
09 {
10 int var;
11
12 printf("Adresse 1: %pn", &var );
13 print_para_adr( (int)&var );
14 return 0;
15 }
|
Ein anderer Problemfall sind Signale: Auf der 32-Bit-Maschine braucht man zur Verarbeitung von 64 Signalen zwei Wörter, auf einem 64-Bit-System dagegen nur eines. Man sollte sich als Programmierer also lieber zweimal überlegen, ob der Wertebereich der definierten Variablen auch auf Maschinen unterschiedlicher Wortbreite ausreicht. Der geschickte Einsatz des Makros »BITS_PER_LONG« hilft im Übrigen - wie bei den Signalen - dabei, in der Länge begrenzte Datenfelder portabel zu beherrschen.
Portable Datentypen
Wer eine Speicherzelle mit definierter Wortbreite braucht, kann auf entsprechende Typedefs zurückgreifen. Die in der Headerdatei »asm/types.h« definierten Datentypen »u8«, »u16«, »u32«, »u64« sowie »s8«, »s16«, »s32« und »s64« stellen entsprechende vorzeichenfreie und vorzeichenbehaftete Speicherzellen zur Verfügung. Tauscht der Kernel solche Daten mit einer Applikation aus, zum Beispiel im Rahmen eines IO-Control, sollte man auch die im Userspace bekannten Versionen mit den zwei Unterstrichen verwenden: »__u8«, »__u16«, »__u32«, »__u64« und »__s8«, »__s16«, »__s32« und »__s64« (siehe Tabelle 1).
|
|
|
Format
|
Datentypnamen
|
|
Host-Format
|
__u8, __u16, __u32, __u64
|
|
Little-Endian-Format
|
__le16, __le32, __le64
|
|
Big-Endian-Format (Netzformat)
|
__be16, __be32, __be64
|