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 <limits.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <unistd.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");
		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;
}