/* 19Jun24: Negativ-Beispiel (III) einer Stack Implementierung.
 *
 * Anstatt selbst den Speicher zu verwalten, übergeben wir die Aufgabe
 * hier an das Betriebssystem.  Hierzu benutzen wir den Systemaufruf
 * mmap(2), welches den logischen Adressraum erweitert und eine neue
 * Adressenbereich gültig macht.  ACHTUNG: In der Aufgabe 5 soll
 * mmap(2) nicht so benutzt werden, sondern um den Speicherbereicht
 * EINMAL am ANFANG zu besorgen, der dann intern verwaltet wird.
 *
 * Vorteile sind, dass wir beliebig viel Speicher anfordern können,
 * insofern unser Adressraum noch erweitert werden kann.
 *
 * Nachteile sind, dass mmap(2), wie alle Systemaufrufe relativ teuer
 * sind, und dass der Speicher nicht so fein Vergeben werden kann, wie
 * wenn wir die Speicherverwaltung im Userspace umsetzen (wie es ja in
 * der Aufgabe gedacht ist).  Daher, wie in der TÜ gesehen wurde,
 * läuft uns sehr schnell der Speicher aus. */

/* -> Siehe Kommentare in halde-stack.c */
#define _GNU_SOURCE
#include "halde.h"

#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>

#define SIZE (1024*1024*1)

/* Wir merken uns am Anfang von einem Speicherbereich, wie viel groß
 * das vergebene Segment ist.  Dieses stellen wir in einem struct dar,
 * welches ein "Flexible-Array-Member" einsetzt.  Dieses ist
 * Syntax-Zucker, um auf den Speicher /hinter/ dem Stack zu verweisen.
 * Damit können wir die administrativen Daten direkt vor dem
 * vergebenem Speicher sichern (daher ist es ja Gefährlich außerhalb
 * vom vergebenem Puffer zu schreiben). */
struct mblock {
     size_t size;
     char memory[];
};

void *malloc(size_t size) {
     struct mblock *mblock;

     /* Hier die Reduktion, auf das grobe mmap(2).  Wenn man die
      * vergebenen Zeiger anschaut, sieht man dass auf einem x86_64
      * System immer alle Zeiger 4k weit weg voneinander sind, weil
      * immer eine neue Seite angelegt wird.  Die Argumente (aus den
      * Folien) sind ansonsten für uns nicht wichtig, man kann auch
      * diesen Aufruf mehr oder weniger direkt in der Aufgabe
      * benutzen. */
     mblock = mmap(NULL, sizeof (struct mblock) + size,
                   PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS,
                   -1, 0);

     /* Hier ist darauf zu achten, dass die Fehlerbehandlung von
      * mmap(2) NICHT das gleiche macht wie malloc(3)! */
     if (mblock == MAP_FAILED) {
          return NULL;
     }

     /* Wir merken uns wie viel Speicher in mblock->memory gültig
      * sind, damit wir auch später free(3) implementieren können. */
     mblock->size = size;

     return mblock->memory;     /* Alternativ: ((char*) mblock) + sizeof mblock */
}

void free(void *ptr) {
     /* Free(3) bekommt als Argument einen Wert der von malloc(3)
      * zurückgeben wurde (oder NULL), ... */
     if (ptr == NULL) return;

     /* Um aus `ptr' den Block zu berechnen, müssen wir /hinter/ dem
      * Zeiger schauen, und hoffen da ein "struct mblock" zu
      * finden.  Wir bestimmen diese Speicherstelle, mit Pointer-Arithmetik:
      *
      *      ((struct mblock*)ptr) - 1
      *      ((char*)ptr) - sizeof (struct mblock)
      *      |
      *      |               ptr
      *      |               |
      *      v               v
      *    +---+---+---+---+---+---+---+---+---+---+---+...
      *    |   |   |   |   |   |   |   |   |   |   |   |      interpretiert als char*
      *    +---+---+---+---+---+---+---+---+---+---+---+...
      *
      *    +---------------+----------------------------...
      *    | size          | memory                           interpretiert als struct mblock*
      *    +---------------+----------------------------...
      *    \_______________/
      *     sizeof (struct mblock), merke dass memory nicht dazu zählt!
      *
      * Wenn wir also ein "struct mblock" nach hinten "schauen" können
      * wir `size' herauslesen.  Hier ist aber keine Garantie, dass es
      * wirklich ein gültiger "struct mblock" ist.  Stattdessen prüfen
      * wir ob es sich wirklich von uns vergebenem Speicher handelt,
      * indem wir den Rückgabewert von munmap(2) überprüfen. */
     struct mblock *mblock = ptr;
     mblock--;

     /* Das geht über SP hinaus: Damit wir den logischen Adressraum
      * verkleinern, können wir munmap(2) benutzen.  Das braucht eben
      * die Größe angeforderten Speichers, was wir aus dem "struct
      * mblock" herauslesen.  Hier erwähne ich es nur aus
      * Vollständigkeit, es hat keinen weiteren Einfluss auf die
      * Aufgabe. */
     if (-1 == munmap(ptr, mblock->size)) {
          abort();
     }
}

/**********************************************************************/
/* ACHTUNG: Diese Datei enthält keine Implementierung von calloc(3)   */
/* und realloc(3).  Diese Funktionen sind jedoch für die Aufgabe 5 zu */
/* implementieren.  Programme welche man gegen dieses Modul bindet    */
/* könnten sich möglicherweise Falsch verhalten, wenn es unsere               */
/* `malloc' und realloc(3) kombinierend!                              */
/**********************************************************************/