...someplace, where there isn't any trouble? Do you suppose there is such a place, Toto?

64 bit Windows - Teil 1

Irgendwie habe ich in Mission Control bei bestimmten Themen immer wieder den Eindruck, als müßte ich Dinge doppelt und dreifach erklären, weil immer wieder verschiedene Leute vorbeischauen, aber alle dieselben Fragen stellen. Und 64 bit Windows ist so ein Thema, weshalb ich mir vorgenommen habe, eine Serie von Postings zu diesem Thema zu schreiben. Dies ist also Teil 1 und ich habe noch keine Ahnung wieviele Teile das dann geben wird - schau'n 'mer 'mal.

Zunächst einmal muß gesagt werden, daß 64-bittige Betriebssysteme nun nichts wirklich fundamental Neues sind. Bereits in den 90er Jahren gab es von HP das Betriebssystem Tru64 als 64-bittiges Unix und von DEC gab's den Alpha Prozessor (der heute über den Umweg über Compaq sein Leben bei HP aushaucht), der 64 bit breite Register hatte. Aber der Weg in den Mainstream war lange Zeit nicht nötig und die Verbreitung von 32-bittigen Betriebssystemen war wohl auch ein Grund, warum 64-bit Betriebssysteme lange auf sich warten liessen. Aber es ist sicherlich nicht die doppelte Registerbreite von 64-bit Prozessoren, die 64-bit Betriebssysteme jetzt allmählich in den Mainstream Einzug halten lassen, sondern der immens viel größere Adreßraum. Bei 32-bit Prozessoren kann man pro Prozeß einen virtuellen Adreßraum von 2^32 Byte adressieren, also genau 4GB. Bei NT-basierten Betriebssystemen kommt hinzu, daß diese 4GB virtueller Adreßraum partitioniert sind in 2GB für den user mode und in 2GB für den kernel mode, so daß so ohne weiteres kein Prozeß mehr als 2GB an Speicher allozieren kann. Darum wurde IIRC auch mit NT4 SP3 die Möglichkeit eingeführt, dem user mode 3GB zuzuteilen und dem kernel mode nur 1 GB. Das Ganze geht aber nur unter Windows XP, Windows Server 2003, Windows NT 4 Enterprise Edition, Windows 2000 Advanced Server oder Datacenter Server mit dem /3GB switch in der boot.ini und die Applikation muß mit einem speziellen Linkerflag gelinkt sein, um das auszunutzen. Um die Sache mit dem knappen Adreßraum etwas weiter zu entschärfen, hat Intel dann noch die AWE (address window extensions) eingeführt, um auf 36 bit Adreßraum zu kommen (jedoch muß der Speicher auch wirklich physikalisch vorhanden sein, mit virtual memory und demand paging geht dann nichts mehr). Das Ganze funktioniert offenbar irgendwie mit dem Einblenden von Speicherseiten ähnlich dem LIMS EMS Standard von weiland Anfang der 90er Jahre mit dem Windows 3.1 Real mode. Sei es wie es will, das alles ist ein übler Hack und die 4GB Grenze, unter der speicherintensive Applikationen wie etwa große Datenbanken leiden, kann man nur mit einer Vergrößerung des Adreßraums knacken.

Der erste Prozessor, der als 64-bitter Einzug in den Mainstream halten sollte war eine gemeinsame Entwicklung von Intel und HP, der Itanium 1. Mittlerweile beim Itanium 2 angekommen, scheint dieser Prozessor aber nicht wirklich abheben zu wollen, obwohl es auch von MS eine dazu passende Windows 2000 Server Edition gibt. Der Grund dafür sind die exorbitanten Kosten für solch einen Prozessor, die dafür sorgen, daß ein IA64 (so heisst die Prozessorfamilie) wohl auf absehbare Zeit in keinem Aldi-PC zu finden sein wird. Ein weiteres Hindernis ist, daß ein IA64 die x86 Binaries, die für Windows auf der x86 Plattform gebuildet wurden, nicht nativ ausführen kann, sondern emulieren muß und dabei nicht unbedingt seine Stärken ausspielen kann. Mittlerweile hat sich HP aus der Itanium-Entwicklung zurückgezogen und MS hat die IA64-Versionen von Windows XP und Windows Vista abgekündigt. Für die weiteren Überlegungen in dieser Serie wird daher der IA64 keine Rolle mehr spielen.

Zwischenzeitlich hat nämlich AMD eine behutsame Erweiterung des x86-Befehlssatzes entwickelt, die Registerbreite verdoppelt und das Ganze als AMD64 auf den Markt gebracht. Das Ergebnis ist ein Prozessor, der in einem x86-Mode betrieben werden kann (man kann also ein herkömmliches NT/W2K/XP für x86 auf so einem Rechner installieren) oder in einem 64 bit Modus. Intel hat natürlich Patentaustauschabkommen mit AMD und darf daher einen befehlssatzkompatiblen Pentium 4 Prozessor mit der "EM64T" extension anbieten, was im wesentlichen dasselbe ist wie die AMD64 Prozessoren, zumindest für das Betriebssystem und darauf laufende Applikationen. MS war mit AMD schon früh zugange, ihre NT-basierten Betriebssysteme auch auf den AMD64 zu portieren, so daß seit Mai 2005 ein "Windows XP x64 Edition" zu kaufen ist, das auf einem AMD64 oder einem Intel EM64T im native 64 bit Mode läuft. Das Schöne ist, daß diese Prozessoren relativ günstig sind und ein Windows XP oder ein W2K3 Server x64 Edition (wurde erst kürzlich releast) die x86 Binaries in der Regel problemlos ausführt und zwar direkt auf dem Prozessor und nicht über einen Emulator. Das bedeutet, daß vorhandene Investitionen in x86 Software erhalten bleiben. Zusätzlich fällt die oben erwähnte Partitionierung in 2GB user mode und 2GB kernel mode weg, wenn ein x86 Binary auf einer x64 Edition von Windows betrieben wird. Dadurch hat man bei vielen Aplikationen schon ohne erneutes und natives Übersetzen für AMD64 bereits einen Vorteil, wenn man sie auf einer x64 Edition ausführt.

So meine Mittagspause ist jetzt vorbei. Das nächste Mal erzähle ich was über Entwicklungswerkzeuge für die x64 Editions von Windows.

(Edited: Paar Typos beseitigt und das mit dem physikalisch vorhandenen Speicher korrekterweise den AWE zugeordnet, mit dem /3GB switch hat das nichts zu tun, Links ergänzt, Standard Mode(das war nämlich daselbe wie der 286 Protected Mode) ersetzt durch Real Mode)

Funny Code - Die Auflösung des Rätsels

Letzte Woche habe ich in einer Email aufgefordert, die Fehler in folgendem Codeabschnitt zu finden:

CString GetComputerName ()
{
  static CString szComputerName;
  if (szComputerName.IsEmpty())
  {
    DWORD dwDataSize = MAX_PATH;

    ::GetComputerName( szComputerName.GetBuffer( MAX_PATH ), 
      &dwDataSize );
    szComputerName.ReleaseBuffer();
  }
  return szComputerName;
}

Der absolute Gewinner war Olli, denn er hat kurz und knapp die drei wichtigsten Probleme gefunden:

"- Anstatt MAX_PATH muss es "MAX_COMPUTERNAME_LENGTH + 1" heissen
- GetBuffer kann eine Exception werfen.
- Nicht threadsafe"

Warum ist das Ganze nicht threadsafe? Weil ein nichttrivialer Typ als globale Variable in szComputerName verwendet wird. Vom Crash bis zum Speicherleck kann alles mögliche passieren wenn zwei threads dumm preempted werden, wenn sie parallel durch diesen Code laufen. Und tatsächlich kann CString::GetBuffer eine CMemoryException werfen. Ob wohl jeder Aufrufer auf sowas vorbereitet ist? Besser wäre es, die Exception in der Funktion aufzufangen und den Fehler mit einem return value anzuzeigen oder mit Setzen eines last error für den aufrufenden Thread, so daß man die Funktion nach "Fire and Forget"-Manier aufrufen kann und sich um Exceptions nicht kümmern muß. Und last but not least ist die Größe des Puffers mit 260 Zeichen für MAX_PATH auch etwas übertrieben, gemessen an den schlappen 15 für MAX_COMPUTERNAME_LENGTH. Was Olli aber nicht gefunden hat, sind vier weitere Probleme:

  • <Korinthenkacker>Wenn schon ungarische Notation (App's Hungarian, um genau zu sein), dann richtig. Es darf dann nicht szComputerName heißen, sondern muß strComputerName heißen.</Korinthenkacker>
  • Selbst wenn die Funktion intern alle Exceptions abfängt, so kann immer noch eine Exception auftreten, wenn der Rückgabewert der Funktion einer CString-Variablen im Scope des Aufrufers zugewiesen wird. Der Aufrufer kommt also in keinem Fall um einen try...catch herum, es sei denn man gibt den Computer-Namen nicht als Rückgabewert zurück sondern hat eine Referenz auf einen CString als formalen [out]-Parameter. Wow, und wenn man dann das tut, hat man plötzlich wieder unverhofft einen Rückgabewert frei, wo man FALSE im Fehlerfall zurückgeben kann. Und weil dann auch kein lokales Objekt mehr da ist, ist sogar ein SetLastError im Fehlerfall bombensicher.
  • Anstatt dem GetBuffer die Konstante MAX_PATH zu übergeben, hätte man durchaus die Variable dwDataSize übergeben können, dann hätte man eine einzige Stelle im Code, wo man den MAX_PATH auf (MAX_COMPUTERNAME_LENGTH+1) hätte abändern müssen.
  • Statt CString::GetBuffer wäre es angebrachter, CString::GetBufferSetLength einzusetzen, da diese Methode den String garantiert erstmal nullterminiert. CString::GetBufferSetLength einzusetzen ist eine gute Angewohnheit, da diese Methode auch in denjenigen Fällen den String nullterminiert, wo die Funktion, die den String dann beschreibt, dies nicht tut.

Ich persönlich finde die fehlende Exception-Awareness nicht so suupertragisch, weil MFC-Code ist normalerweise UI code und was macht es schon, wenn solcher Code dann mal die Grätsche macht? Viel tragischer fände ich es, wenn solcher Code in Services eingesetzt wird, wo eigentlich 24/7 gefragt sein sollte. Das Bestürzende ist allerdings meiner Meinung nach, daß dieser Code in jedem Thread aufgerufen wird, der Logfiles schreibt und zwar ziemlich genau am Anfang, wenn die Logfile-Präambel geschrieben wird. Wenn zwei Threads also ziemlich zeitgleich starten, ist also die Gefahr einer Race-Condition in dieser Funktion durchaus gegeben.

Wie spricht man eigentlich "Pracujemy nad rewoluja. Przylaczysz sie?" ?

Eine immerwiederkehrende Frage von Besuchern in επτ€σ Mission Control. Hier ist die schnelle Antwort und hier ist die etwas langsamere Antwort

<< 1 ... 36 37 38 39 40 41 42 43 44 45 46