This is an old revision of the document!
This is a test-application that shows the effects of mlockall() on stack memory.
/* gcc -o check_stack_page_faults check_stack_page_faults.c -Wall -O3 -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> #include <limits.h> /* struct thread_data communicates data to our threads */ struct thread_data { char *name; sem_t semId; size_t stack_filler_size; }; /* Lock the application in memory to avoid page faults */ static void lock_application(void) { if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) { perror("mlockall() failed"); } } /* Dump minor and major page faults that occurred since the previous call */ static int dump_page_faults(void) { int page_faults_detected = 0; static int init = 0; static struct rusage rusage_prev; struct rusage rusage; int new_minor_page_faults, new_major_page_faults; getrusage(RUSAGE_SELF, &rusage); new_minor_page_faults = rusage.ru_minflt - rusage_prev.ru_minflt; new_major_page_faults = rusage.ru_majflt - rusage_prev.ru_majflt; rusage_prev.ru_minflt = rusage.ru_minflt; rusage_prev.ru_majflt = rusage.ru_majflt; if (init) { if ((new_minor_page_faults > 0) || (new_major_page_faults > 0)) { printf("New minor/major page faults: %d/%d\n", new_minor_page_faults, new_major_page_faults); page_faults_detected = 1; } } init = 1; return page_faults_detected; } /* 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; } /* Define a stack size for our threads */ static const int stack_size = 1024 * 1024 * 8; /* Our thread routine */ static void *check_stack_thread(void *pArg) { struct thread_data *pthread_data = (struct thread_data *)pArg; int i; /* Tell the world we are alive */ printf("%s started\n", pthread_data->name); /* Wait until we are told to fill the stack */ sem_wait(&(pthread_data->semId)); { /* Limit the scope */ char stack_filler[pthread_data->stack_filler_size]; printf("%s performing checks\n", pthread_data->name); for (i = 0; i < (int)pthread_data->stack_filler_size; i += sysconf(_SC_PAGESIZE)) { stack_filler[i] = 42; } printf("%s done\n", pthread_data->name); } /* Wait until we are told to terminate */ sem_wait(&(pthread_data->semId)); printf("%s terminating\n", pthread_data->name); return 0; } int main(int argc, char *argv[]) { int lock_memory = 1; int i; const int page_size = sysconf(_SC_PAGESIZE); int page_faults_detected_in_thread1; int page_faults_detected_in_thread2; /* Thread data */ pthread_t thread_ids[2]; struct thread_data thread_data[2]; /* Start a thread prior to locking memory. */ thread_data[0].name = "Thread 1: "; (void)sem_init(&(thread_data[0].semId), 0, 0); thread_data[0].stack_filler_size = stack_size - PTHREAD_STACK_MIN; thread_ids[0] = startThread(0, stack_size, check_stack_thread, (void *)&thread_data[0]); /* Give it 1 second to start */ sleep(1); for (i = 1; i < argc; i++) { if (strncmp(argv[i], "-nolockmem", 10) == 0) { lock_memory = 0; } } if (lock_memory) { lock_application(); 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. */ printf("Page size = %d\n", page_size); (void)dump_page_faults(); /* 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(&(thread_data[0].semId)); /* Give it 1 second to complete. */ sleep(1); page_faults_detected_in_thread1 = dump_page_faults(); if (lock_memory) { if (!page_faults_detected_in_thread1) 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. */ thread_data[1].name = "Thread 2: "; (void)sem_init(&(thread_data[1].semId), 0, 0); thread_data[1].stack_filler_size = stack_size - PTHREAD_STACK_MIN; thread_ids[1] = startThread(0, stack_size, check_stack_thread, (void *)&thread_data[1]); /* Give it 1 second to start */ sleep(1); if (lock_memory) { if (dump_page_faults()) /* 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)dump_page_faults(); /* Reset the baseline */ } sem_post(&(thread_data[1].semId)); /* Give it 1 second to complete. */ sleep(1); page_faults_detected_in_thread2 = dump_page_faults(); /* Check whether page faults have been detected. */ int exit_code = 0; if ((page_faults_detected_in_thread1) || (page_faults_detected_in_thread2)) { exit_code = 1; } /* Make the threads terminate */ sem_post(&(thread_data[0].semId)); sem_post(&(thread_data[1].semId)); (void)pthread_join(thread_ids[0], 0); (void)pthread_join(thread_ids[1], 0); printf("Done, result: %s\n", (exit_code != 0) ? "failed" : "success"); return exit_code; }