Verifying mlockall() effects on stack memory proof
From RTwiki
Verifying mlockall() effects on stack memory proof
This is a test-application that shows the effects of mlockall() on stack memory.
// g++ -o checkStackPageFaults -Wall -O3 checkStackPageFaults.cpp -lpthread // This application checks whether mlockall() forces all pages of a // stack into RAM. #include <stdio.h> #include <string.h> #include <pthread.h> #include <semaphore.h> #include <sched.h> #include <unistd.h> #include <sys/mman.h> #include <sys/resource.h> // Lock the application in memory to avoid page faults static void lockApplication(void) { if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0 ) { perror( "mlockall() failed" ); } } // Dump minor and major page faults that occurred since the previous call static bool dumpPageFaults(void) { bool l_PageFaultsDetected = false; static bool ls_Init = false; static struct rusage ls_RusagePrevious; struct rusage l_Rusage; getrusage(RUSAGE_SELF, &l_Rusage); int a_NewMinorPageFaults = l_Rusage.ru_minflt - ls_RusagePrevious.ru_minflt; int a_NewMajorPageFaults = l_Rusage.ru_majflt - ls_RusagePrevious.ru_majflt; ls_RusagePrevious.ru_minflt = l_Rusage.ru_minflt; ls_RusagePrevious.ru_majflt = l_Rusage.ru_majflt; if (ls_Init) { if ((a_NewMinorPageFaults > 0) || (a_NewMajorPageFaults > 0)) { printf ("New minor/major page faults: %d/%d\n", a_NewMinorPageFaults, a_NewMajorPageFaults); l_PageFaultsDetected = true; } } ls_Init = true; return l_PageFaultsDetected; } // Start a thread static pthread_t startThread(int prio, int stack_size, void *(*thread_run)(void*), void *args) { const char * failure; pthread_attr_t attr; pthread_t tid; struct sched_param sched; int policy; int priority_min; int priority_max; int min_stack_size = 16384; for(;;) { if( (policy = sched_getscheduler( 0 )) == -1 ) { failure = "sched_getscheduler"; break; } if( (priority_min = sched_get_priority_min( policy )) == -1 ) { failure = "sched_get_priority_min"; break; } if( prio < priority_min ) prio = priority_min; if( (priority_max = sched_get_priority_max( policy )) == -1 ) { failure = "sched_get_priority_max"; break; } if( prio > priority_max ) prio = priority_max; if( sched_getparam( 0, &sched ) != 0 ) { failure = "sched_getparam"; break; } if( pthread_attr_init( &attr ) != 0 ) { failure = "pthread_attr_init"; break; } if( pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ) != 0 ) { failure = "pthread_attr_setdetachstate"; break; } if( pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM ) != 0 ) { failure = "pthread_attr_setscope"; break; } if( stack_size < min_stack_size ) stack_size = min_stack_size; if( pthread_attr_setstacksize( &attr, stack_size ) != 0 ) { failure = "pthread_attr_setstacksize"; break; } if( pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED ) != 0 ) { failure = "pthread_attr_setinheritsched"; break; } if( pthread_attr_setschedpolicy( &attr, policy ) != 0 ) { failure = "pthread_attr_setschedpolicy"; break; } sched.sched_priority = prio; if( pthread_attr_setschedparam( &attr, &sched ) != 0 ) { failure = "pthread_attr_setschedparam"; break; } if( pthread_create( &tid, &attr, thread_run, args ) != 0 ) { failure = "pthread_create"; break; } if( pthread_attr_destroy( &attr ) != 0 ) { failure = "pthread_attr_destroy"; break; } return tid; } return tid; } // threadData_t communicates data to our threads typedef struct { char *name; sem_t semId; size_t stackFillerSize; } threadData_t; // Define a stack size for our threads static const int gs_StackSize = 1024 * 1024 * 8; // Our thread routine static void *checkStackThread(void *pArg) { threadData_t *lp_ThreadData = static_cast<threadData_t*>(pArg); int i; // Tell the world we are alive printf ("%s started\n", lp_ThreadData->name); // Wait until we are told to fill the stack sem_wait(&(lp_ThreadData->semId)); char stackFiller[lp_ThreadData->stackFillerSize]; printf ("%s performing checks\n", lp_ThreadData->name); for (i=0; i<(int)lp_ThreadData->stackFillerSize; i+=sysconf(_SC_PAGESIZE)) { stackFiller[i] = 42; } printf ("%s done\n", lp_ThreadData->name); // Wait until we are told to terminate sem_wait(&(lp_ThreadData->semId)); printf ("%s terminating\n", lp_ThreadData->name); return 0; } int main(int argc, char *argv[]) { // Thread data pthread_t l_ThreadIds[2]; threadData_t l_ThreadData[2]; // Start a thread prior to locking memory. l_ThreadData[0].name = "Thread 1: "; (void)sem_init(&(l_ThreadData[0].semId), 0, 0); l_ThreadData[0].stackFillerSize = gs_StackSize - (1024*16); l_ThreadIds[0] = startThread(0, gs_StackSize, checkStackThread, static_cast<void*>(&(l_ThreadData[0]))); // Give it 1 second to start sleep(1); bool l_LockMemory = true; int i; for (i=1; i<argc; i++) { if (strncmp (argv[i], "-nolockmem", 10) == 0) { l_LockMemory = false; } } if (l_LockMemory) { lockApplication(); printf ("Current and future memory locked in RAM\n"); } // printf something so we avoid introducing a page fault simply by performing // the potentially first printf call. const int l_PageSize = sysconf(_SC_PAGESIZE); printf ("Page size = %d\n", l_PageSize); (void)dumpPageFaults(); // Set the baseline // From this point onwards we no longer expect to have any // page faults for currently allocated memory. // Make the first thread fill the stack. sem_post(&(l_ThreadData[0].semId)); // Give it 1 second to complete. sleep(1); bool l_PageFaultsDetectedInThread1 = dumpPageFaults(); if (l_LockMemory) { if (l_PageFaultsDetectedInThread1 == false) { printf ("After locking memory, no new page faults have been generated during stack access of thread 1. This is expected.\n"); } else { printf ("After locking memory, new page faults have been generated during during stack access of thread 1. This is unexpected!\n"); } } // Thread 1 has not yet terminated, meaning that thread 2 will not // reuse the same (locked) stack region. // Start the second thread and make it fill the stack. l_ThreadData[1].name = "Thread 2: "; (void)sem_init(&(l_ThreadData[1].semId), 0, 0); l_ThreadData[1].stackFillerSize = gs_StackSize - (1024*16); l_ThreadIds[1] = startThread(0, gs_StackSize, checkStackThread, static_cast<void*>(&(l_ThreadData[1]))); // Give it 1 second to start sleep(1); if (l_LockMemory) { if (dumpPageFaults()) // Reset the baseline { printf ("New page faults have occurred as a result of starting thread 2. This is expected.\n"); } else { printf ("Did not observe new page faults as a result of starting thread 2. This is unexpected!\n"); } } else { (void)dumpPageFaults(); // Reset the baseline } sem_post(&(l_ThreadData[1].semId)); // Give it 1 second to complete. sleep(1); bool l_PageFaultsDetectedInThread2 = dumpPageFaults(); // Check whether page faults have been detected. int l_ExitCode = 0; if ((l_PageFaultsDetectedInThread1 == true) || (l_PageFaultsDetectedInThread2 == true)) { l_ExitCode = 1; } // Make the threads terminate sem_post(&(l_ThreadData[0].semId)); sem_post(&(l_ThreadData[1].semId)); (void)pthread_join(l_ThreadIds[0], 0); (void)pthread_join(l_ThreadIds[1], 0); printf ("Done, result: %s\n", (l_ExitCode != 0) ? "failed":"success"); return l_ExitCode; }