====== mlockall() effects on stack ====== 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 #include #include #include #include #include #include #include #include #include /* 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"); exit(-1); } } /* Dump minor and major page faults that occurred since the previous call */ static int dump_page_faults(void) { int new_minor_page_faults, new_major_page_faults; int page_faults_detected = 0; static struct rusage rusage_prev; struct rusage rusage; static int init; 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) { int min_stack_size = 16384; struct sched_param sched; const char *failure; pthread_attr_t attr; int priority_min; int priority_max; pthread_t tid; int policy; for (;;) { policy = sched_getscheduler(0); if (policy == -1) { failure = "sched_getscheduler"; break; } priority_min = sched_get_priority_min(policy); if (priority_min == -1) { failure = "sched_get_priority_min"; break; } if (prio < priority_min) prio = priority_min; priority_max = sched_get_priority_max(policy); if (priority_max == -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; } printf("FAILURE while %s in startThread\n", failure); return -1; } /* 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; } /* dummy read of stack_filler[52] to prevent unused warning */ printf("%s done %d\n", pthread_data->name, stack_filler[52]); } /* 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[]) { const int page_size = sysconf(_SC_PAGESIZE); int page_faults_detected_in_thread1; int page_faults_detected_in_thread2; int lock_memory = 1; struct thread_data thread_data[2]; pthread_t thread_ids[2]; int i; /* 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]); if (thread_ids[0] == -1) { printf("Exiting - Unexpected failure while creating thread1\n"); exit(-1); } /* 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"); } (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]); if (thread_ids[1] == -1) { printf("Exiting - Unexpected failure while creating thread2\n"); exit(-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; }