Mi chiedo cosa stai cercando di ottenere. Se il tuo processo è deterministico, allora il modello di allocazione/deallocazione dovrebbe essere lo stesso.
L'unica possibile differenza potrebbe essere l'indirizzo restituito da malloc
. Ma probabilmente non dovresti dipendere da loro (il modo più semplice è non usare i puntatori come mappa chiave o altra struttura dati). E anche allora, dovrebbe esserci differenza solo se l'allocazione non viene effettuata tramite sbrk
(le glibc usano mmap
anonimo per grandi allocazioni), o se stai usando mmap
(poiché per impostazione predefinita l'indirizzo è selezionato dal kernel).
Se vuoi davvero avere esattamente lo stesso indirizzo, un'opzione è avere un buffer statico di grandi dimensioni e scrivere un allocatore personalizzato che utilizzi la memoria da questo buffer. Questo ha lo svantaggio di costringerti a conoscere in anticipo la quantità massima di memoria di cui avrai mai bisogno. In un eseguibile non PIE (gcc -fno-pie -no-pie
), un buffer statico avrà sempre lo stesso indirizzo. Per un eseguibile PIE è possibile disabilitare la randomizzazione del layout dello spazio degli indirizzi del kernel per il caricamento dei programmi. In una libreria condivisa, disabilitare ASLR ed eseguire lo stesso programma due volte dovrebbe portare alle stesse scelte da parte del linker dinamico su dove mappare le librerie.
Se non conosci prima la dimensione massima della memoria che vuoi utilizzare, o se non vuoi ricompilare ogni volta che questa dimensione aumenta, puoi anche usare mmap
per mappare un grande buffer anonimo a un indirizzo fisso. Passa semplicemente la dimensione del buffer e l'indirizzo da usare come parametro al tuo processo e usa la memoria restituita per implementare il tuo malloc
sopra di esso.
static void* malloc_buffer = NULL;
static size_t malloc_buffer_len = 0;
void* malloc(size_t size) {
// Use malloc_buffer & malloc_buffer_len to implement your
// own allocator. If you don't read uninitialized memory,
// it can be deterministic.
return memory;
}
int main(int argc, char** argv) {
size_t buf_size = 0;
uintptr_t buf_addr = 0;
for (int i = 0; i < argv; ++i) {
if (strcmp(argv[i], "--malloc-size") == 0) {
buf_size = atoi(argv[++i]);
}
if (strcmp(argv[i], "--malloc-addr") == 0) {
buf_addr = atoi(argv[++i]);
}
}
malloc_buffer = mmap((void*)buf_addr, buf_size, PROT_WRITE|PROT_READ,
MAP_FIXED|MAP_PRIVATE, 0, 0);
// editor's note: omit MAP_FIXED since you're checking the result anyway
if (malloc_buffer == MAP_FAILED || malloc_buffer != (void*)but_addr) {
// Could not get requested memory block, fail.
exit(1);
}
malloc_size = buf_size;
}
Usando MAP_FIXED
, stiamo dicendo al kernel di sostituire tutte le mappature esistenti che si sovrappongono a questa nuova in buf_addr
.
(Nota dell'editore:MAP_FIXED
probabilmente non è quello che vuoi . Specificando buf_addr
come suggerimento invece di NULL
richiede già quell'indirizzo, se possibile. Con MAP_FIXED
, mmap
restituirà un errore o l'indirizzo che gli hai fornito. Il malloc_buffer != (void*)but_addr
check ha senso per il non-FIXED
case, che non sostituirà una mappatura esistente del tuo codice o una libreria condivisa o altro. Linux 4.17 ha introdotto MAP_FIXED_NOREPLACE
che puoi usare per fare in modo che mmap restituisca un errore invece della memoria all'indirizzo sbagliato che non vuoi usare. Ma lascia comunque il check-in in modo che il tuo codice funzioni su kernel più vecchi.)
Se usi questo blocco per implementare il tuo malloc e non usi altre operazioni non deterministiche nel tuo codice, puoi avere il controllo completo dei valori del puntatore.
Ciò suppone che il tuo utilizzo del modello di malloc / free sia deterministico. E che non utilizzi librerie non deterministiche.
Tuttavia, penso che una soluzione più semplice sia mantenere i tuoi algoritmi deterministici e non dipendere dagli indirizzi. Questo è possibile. Ho lavorato a un progetto su larga scala in cui più computer dovevano aggiornare lo stato in modo deterministico (in modo che ogni programma avesse lo stesso stato, trasmettendo solo input). Se non usi il puntatore per cose diverse dal riferimento agli oggetti (la cosa più importante è non usare mai il valore del puntatore per niente, non come hash, non come chiave in una mappa, ...), allora il tuo stato rimarrà deterministico .
A meno che ciò che si desidera fare sia essere in grado di eseguire un'istantanea dell'intera memoria del processo ed eseguire una differenza binaria per individuare la divergenza. Penso che sia una cattiva idea, perché come fai a sapere che entrambi hanno raggiunto lo stesso punto nel loro calcolo? È molto più semplice confrontare l'output o fare in modo che il processo sia in grado di calcolare un hash dello stato e usarlo per verificare che siano sincronizzati perché puoi controllare quando questo è fatto (e quindi diventa anche deterministico, altrimenti la tua misurazione non è deterministica).
Ciò che non è deterministico non è solo malloc
ma mmap (la syscall di base per ottenere più spazio di memoria; non è una funzione, è una chiamata di sistema quindi è elementare o atomica dal punto di vista dell'applicazione; quindi non puoi riscriverla all'interno dell'applicazione) a causa della randomizzazione del layout dello spazio degli indirizzi su Linux.
Puoi disabilitarlo con
echo 0 > /proc/sys/kernel/randomize_va_space
come root o tramite sysctl.
Se non disabiliti la randomizzazione del layout dello spazio degli indirizzi sei bloccato.
E hai fatto una domanda simile in precedenza, dove ho spiegato che il tuo malloc
-s non sarà sempre deterministico.
Penso ancora che per alcune applicazioni pratiche, malloc
non può essere deterministico. Immagina per esempio un programma con una tabella hash codificata dal pid
-s dei processi figlio che sta avviando. La collisione in quella tabella non sarà la stessa in tutti i tuoi processi, ecc.
Quindi credo che non riuscirai a fare malloc
deterministico nel tuo senso, qualunque cosa tu provi (a meno che non ti limiti a una classe molto ristretta di applicazioni al checkpoint, così ristretta che il tuo software non sarà molto utile).
In poche parole, come altri hanno affermato:se l'esecuzione delle istruzioni del tuo programma è deterministica, la memoria viene restituita da malloc()
sarà deterministico. Ciò presuppone che l'implementazione del tuo sistema non abbia alcuna chiamata a random()
o qualcosa del genere. Se non sei sicuro, leggi il codice o la documentazione per malloc
del tuo sistema .
Questo è con la possibile eccezione di ASLR, come hanno affermato anche altri. Se non disponi dei privilegi di root, puoi disabilitarlo per processo tramite personality(2)
syscall e il parametro ADDR_NO_RANDOMIZE. Vedi qui per maggiori informazioni sulle personalità.
Modifica:dovrei anche dire, se non lo sai:quello che stai facendo si chiama bisimulazione ed è una tecnica ben studiata. Se non conosci la terminologia, potrebbe essere utile avere quella parola chiave per la ricerca.