2016-04-14 10 views
4

Ich möchte wirklich verstehen, was auf diesen beiden Linien geschiehtC++ - Ausrichten Speicher

const int PAGES = 8 * 1024; 

// PAGES + extra 4KiB for alignment 
uint8_t * mem = new uint8_t [ PAGES * CCPU::PAGE_SIZE + CCPU::PAGE_SIZE ]; 

// align to a mutiple of 4KiB 
uint8_t * memAligned = (uint8_t *) ((((uintptr_t) mem) + CCPU::PAGE_SIZE - 1) & ~(uintptr_t) ~CCPU::ADDR_MASK); 

besonders die letzte Zeile, ich verstehe nicht alles ...

+0

TL; DR: Es verschwendet Speicher und Sie wollen keinen solchen Code :) –

+1

@KubaOber: Es gibt einige begrenzte Umstände, in denen Seite ausgerichteten Speicher den Abfall wert ist; Einige Betriebssysteme können Optimierungen durchführen, um Speicherkopien zu reduzieren, wenn Daten in den Seitenspeicher gelesen/geschrieben werden. Wenn es Sie eine Seite des Speichers kostet, um Nullkopie-Datenübertragungen auf einem Hochlast-Webserver zu erhalten, ist es das wert. – ShadowRanger

+0

@ShadowRanger Ich sage nicht, dass Sie Ihren Speicher nicht ausrichten sollten, nur dass Sie es tun sollten, indem Sie explizit nach seitenausgerichtetem Speicher fragen. Immerhin, da "PAGES" "groß" ist, wird der dem "neuen" zugrundeliegende Zuordner das Betriebssystem nach dem Speicher fragen. Sie können das also auch selbst tun und auf diese Weise page-aligned storage erhalten. In vielen oder sogar den meisten Fällen ist 'mem' bereits ausgerichtet und dann könntest du' PAGES' vielleicht erhöhen, wenn es nicht const war, und Verschwendung vermeiden, aber ich denke nicht, dass solche Hacks gefördert werden sollten. –

Antwort

6

Es Zuweisung einen Zeiger auf ein Block mit Seitenspeicher, das heißt PAGES Anzahl der Seiten, die C++ - Zuordnungen anstelle von weiteren OS-spezifischen zugewiesenen zugewiesenen Zuordnungsfunktionen verwenden (z. B. POSIX's posix_memalign or C11's aligned_alloc).

Zuerst weist es PAGES + 1 Seiten des Speichers zu (die Seiten ausgerichtet sein können oder nicht), dann passt es den resultierenden Zeiger vorwärts an, so dass es auf das erste ausgerichtete Byte in dem Ergebnis zeigt. Wenn Sie eine zusätzliche Seite überladen, weiß sie, dass sie sicher groß genug ist, um PAGES verwendbare Seiten über diesen Punkt hinaus zu haben. Das Programm muss nur sicher sein, dass es mem s mem wenn es fertig ist, nicht memAligned (Löschen der letzteren würde wahrscheinlich entweder das Programm jetzt abstürzen, später wegen Heap Korruption, oder nur Leckspeicher; es ist undefiniertes Verhalten, so schmilzt Ihren Computer zu Schlacke ist ein legales Verhalten).

Diese letzte Zeile entspricht numerisch dem Aufrunden auf das nächste Vielfache der Seitengröße; es fügt dem Zeiger PAGE_SIZE - 1 hinzu (wenn also der Zeiger bereits Seite ausgerichtet war, befindet er sich immer noch auf derselben Seite, ansonsten wird auf die nächste Seite verschoben), dann werden die unteren Bits der Adresse ausgeblendet (wodurch die Addition in "bereits" rückgängig gemacht wird) page aligned "case, und in allen anderen Fällen wird der Zeiger auf den Anfang der ersten Seite zurückgesetzt, die auf den nicht ausgerichteten Zeiger in mem folgt.

Die Details: ~ ist bitweise invertieren, so ADDR_MASK, die wahrscheinlich Byte Seiten so etwas wie 0x00000FFF für 4096 ist, wird 0xFFFFF000 (alle Bits Spiegeln). Wenn & ein Wert eingegeben wird, werden nur die in beiden Operanden gesetzten Bits beibehalten. Um Beispiele zu nennen: Für einen 32-Bit-Zeiger gehen wir davon aus, dass new0xDEADBEEF angibt und PAGE_SIZE 4096 ist. Das Hinzufügen auf 4095 (0xFFF) bedeutet, dass wir "0xDEADCEEE" haben. Wir maskieren dann mit 0xFFFFF000, die die niedrigen Bits beseitigt und uns 0xDEADC000 gibt, die erste Seite ausgerichtete Adresse, die 0xDEADBEEF folgt. Das Gleiche würde mit jeder nicht von Seiten ausgerichteten Adresse passieren, die von new zurückgegeben wird.

Wenn der Wert bereits Seite obwohl ausgerichtet ist, sagen wir, 0xDEADB000 und fügte hinzu, auf 4095/0xFFF bringt uns 0xDEADBFFF (beachten Sie, wie keine Bits in 0xDEADB geändert), so dass, wenn wir die ausgerichtete Adresse erhalten Maske, bekommen wir 0xDEADB000 wieder , da wir bereits Seiten ausgerichtet waren. Die Umwandlung in uintptr_t soll sicherstellen, dass wir die Adresse mit mathematischen Operatoren manipulieren können, und sicherstellen, dass bitweise Invertierung alle Bits füllt, die für die Anpassung eines Zeigers erforderlich sind (wenn sie nicht angemessen dimensioniert ist, können Sie invertieren, dann hochkonvertieren und plötzlich hätten Sie links eine Reihe von Nullen, nicht nur die rechte, und Sie würden damit enden, wichtige Bits im Zeiger abzudecken, so dass es auf einen völlig anderen und falschen Ort zeigt).

+0

Danke, aber ich dachte eher an '((uintptr_t) mem) + CCPU :: PAGE_SIZE - 1) & ~ (uintptr_t) ~ CCPU :: ADDR_MASK'. Was sind diese wilden '~' und warum tippen wir auf 'uintptr_t' zurück? – Charlestone

+0

@Charlestone: Ich habe nur diese Erklärung hinzugefügt. :-) Jetzt fertig, mit Beispielen. – ShadowRanger

+0

oh, richtig, ich verstehe. das uintptr_t nimmt grundsätzlich die adresse und wirft es wie ein uint32_t (64_t) oder? noch eine Sache '~ (uintptr_t) ~ CCPU :: ADDR_MASK' - warum die doppelte Verneinung hier? – Charlestone