4    Memory Locking

Memory management facilities ensure that processes have effective and equitable access to memory resources. The operating system maps and controls the relationship between physical memory and the virtual address space of a process. These activities are, for the most part, transparent to the user and controlled by the operating system. However, for many realtime applications you may need to make more efficient use of system resources by explicitly controlling virtual memory usage.

This chapter includes the following sections:

Memory locking is one way to ensure that a process stays in main memory and is exempt from paging. In a realtime environment, a system must be able to guarantee that it will lock a process in memory to reduce latency for data access, instruction fetches, buffer passing between processes, and so forth. Locking a process's address space in memory helps ensure that the application's response time satisfies realtime requirements. As a general rule, time-critical processes should be locked into memory.


4.1    Memory Management

In a multiprogramming environment, it is essential for the operating system to share available memory effectively among processes. Memory management policies are directly related to the amount of memory required to execute those processes. Memory management algorithms are designed to optimize the number of runnable processes in primary memory while avoiding conflicts that adversely affect system performance. If a process is to remain in memory, the kernel must allocate adequate units of memory. If only part of a process needs to be in primary memory at any given time, then memory management can work together with the scheduler to make optimal use of resources.

Virtual address space is divided into fixed-sized units, called pages. Each process usually occupies a number of pages, which are independently moved in and out of primary memory as the process executes. Normally, a subset of a process's pages resides in primary memory when the process is executing.

Since the amount of primary memory available is finite, paging is often done at the expense of some pages; to move pages in, others must be moved out. If the page that is going to be replaced is modified during execution, that page is written to a file area. That page is brought back into primary memory as needed and execution is delayed while the kernel retrieves the page.

Paging is generally transparent to the current process. The amount of paging can be decreased by increasing the size of physical memory or by locking the pages into memory. However, if the process is very large or if pages are frequently being paged in and out, the system overhead required for paging may decrease efficiency.

For realtime applications, having adequate memory is more important than for nonrealtime applications. Realtime applications must ensure that processes are locked into memory and that there is an adequate amount of memory available for both realtime processes and the system. Latency due to paging is often unacceptable for critical realtime tasks.


4.2    Memory-Locking and Unlocking Functions

Realtime application developers should consider memory locking as a required part of program initialization. Many realtime applications remain locked for the duration of execution, but some may want to lock and unlock memory as the application runs. DIGITAL UNIX memory-locking functions let you lock the entire process at the time of the function call and throughout the life of the application, or selectively lock and unlock as needed.

Memory locking applies to a process's address space. Only the pages mapped into a process's address space can be locked into memory. When the process exits, pages are removed from the address space and the locks are removed.

Two functions, mlock and mlockall, are used to lock memory. The mlock function allows the calling process to lock a selected region of address space. The mlockall function causes all of a process's address space to be locked. Locked memory remains locked until either the process exits or the application calls the corresponding munlock or munlockall function.

Memory locks are not inherited across a fork and all memory locks associated with a process are unlocked on a call to the exec function or when the process terminates.

For most realtime applications the following control flow minimizes program complexity and achieves greater determinism by locking the entire address into memory.

  1. Perform nonrealtime tasks, such as opening files or allocating memory

  2. Lock the address space of the process calling mlockall function

  3. Perform realtime tasks

  4. Release resources and exit

The memory-locking functions are as follows:

Function Description
mlock Locks a specified region of a process's address space
mlockall Locks all of a process's address space
munlock Unlocks a specified region of a process's address space
munlockall Unlocks all of a process's address space

You must have superuser privileges to call the memory locking functions.


4.2.1    Locking and Unlocking a Specified Region

The mlock function locks a preallocated specified region. The address and size arguments of the mlock function determine the boundaries of the preallocated region. On a successful call to mlock, the specified region becomes locked. Memory is locked by the system according to system-defined pages. If the address and size arguments specify an area smaller than a page, the kernel rounds up the amount of locked memory to the next page. The mlock function locks all pages containing any part of the requested range, which can result in locked addresses beyond the requested range.

Repeated calls to mlock could request more physical memory than is available; in such cases, subsequent processes must wait for locked memory to become available. Realtime applications often cannot tolerate the latency introduced when a process must wait for lockable space to become available. Preallocating and locking regions is recommended for realtime applications.

If the process requests more locked memory than will ever be available in the system, an error is returned.

Figure 4-1 illustrates memory allocation before and after a call to the mlock function. Prior to the call to the mlock function, buffer space in the data area is not locked and is therefore subject to paging. After the call to the mlock function the buffer space cannot be paged out of memory.

Figure 4-1:  Memory Allocation with mlock

The mlock function locks all pages defined by the range addr to addr+len-1 (inclusive). The area locked is the same as if the len argument were rounded up to a multiple of the next page size before decrementing by 1. The address must be on a page boundary and all pages mapped by the specified range are locked. Therefore, you must determine how far the return address is from a page boundary and align it before making a call to the mlock function.

Use the sysconf(_SC_PAGE_SIZE) function to determine the page size. The size of a page can vary from system to system. To ensure portability, call the sysconf function as part of your application or profile when writing applications that use the memory-locking functions. The sys/mman.h header file defines the maximum amount of memory that can be locked. Use the getrlimit function to determine the amount of total memory.

Exercise caution when you lock memory; if your processes require a large amount of memory and your application locks memory as it executes, your application may take resources away from other processes. In addition, you could attempt to lock more virtual pages than can be contained in physical memory.

Locked space is automatically unlocked when the process exits, but you can also explicitly unlock space. The munlock function unlocks the specified address range regardless of the number of times the mlock function was called. In other words, you can lock address ranges over multiple calls to the mlock function, but can remove the locks with a single call to the munlock function. Space locked with a call to the mlock function must be unlocked with a corresponding call to the munlock function.

Example 4-1 shows how to lock and unlock memory segments. Each user-written function determines page size, adjusts boundaries, and then either locks or unlocks the segment.

Example 4-1:  Aligning and Locking a Memory Segment

#include <unistd.h>     /* Support all standards    */
#include <sys/mman.h>   /* Memory locking functions */

#define DATA_SIZE 2048

lock_memory(char   *addr,
            size_t  size)
{
  unsigned long    page_offset, page_size;

  page_size = sysconf(_SC_PAGE_SIZE);
  page_offset = (unsigned long) addr % page_size;

  addr -= page_offset;  /* Adjust addr to page boundary */
  size += page_offset;  /* Adjust size with page_offset */

  return ( mlock(addr, size) );  /* Lock the memory */
}

unlock_memory(char   *addr,
              size_t  size)
{
  unsigned long    page_offset, page_size;

  page_size = sysconf(_SC_PAGE_SIZE);
  page_offset = (unsigned long) addr % page_size;

  addr -= page_offset;  /* Adjust addr to page boundary */
  size += page_offset;  /* Adjust size with page_offset */

  return ( munlock(addr, size) );  /* Unlock the memory */
}

main()
{
  char data[DATA_SIZE];

  if ( lock_memory(data, DATA_SIZE) == -1 )
    perror("lock_memory");

           /* Do work here */

  if ( unlock_memory(data, DATA_SIZE) == -1 )
    perror("unlock_memory");
}


4.2.2    Locking and Unlocking an Entire Process Space

The mlockall function locks all of the pages mapped by a process's address space. On a successful call to mlockall, the specified process becomes locked and memory-resident. The mlockall function takes two flags, MCL_CURRENT and MCL_FUTURE, which determine whether the pages to be locked are those currently mapped, or if pages mapped in the future are to be locked. You must specify at least one flag for the mlockall function to lock pages. If you specify both flags, the address space to be locked is constructed from the logical OR of the two flags.

If you specify MCL_CURRENT only, all currently mapped pages of the process's address space are memory-resident and locked. Subsequent growth in any area of the specified region is not locked into memory. If you specify the MCL_FUTURE flag only, all future pages are locked in memory. If you specify both MCL_CURRENT and MCL_FUTURE, then the current pages are locked and subsequent growth is automatically locked into memory.

Figure 4-2 shows memory allocation before and after a call to the mlockall function with both MCL_CURRENT and MCL_FUTURE flags. Prior to the call to the mlockall function, space is not locked and is therefore subject to paging. After a call to the mlockall function, which specifies the MCL_CURRENT and MCL_FUTURE flags, all memory used by the process, both currently and in the future, is locked into memory. The call to the malloc function increases the amount of memory locked for the process.

Figure 4-2:  Memory Allocation with mlockall

The munlockall function unlocks all pages mapped by a call to the mlockall function, even if the MCL_FUTURE flag was specified on the call. The call to the munlockall function cancels the MCL_FUTURE flag. If you want additional locking later, you must call the memory-locking functions again.

Example 4-2 illustrates how the mlockall function might be used to lock current and future address space.

Example 4-2:  Using the mlockall Function

#include <unistd.h>     /* Support all standards    */
#include <stdlib.h>     /* malloc support           */
#include <sys/mman.h>   /* Memory locking functions */

#define BUFFER 2048

main()
{
  void *p[3];  /* Array of 3 pointers to void */

  p[0] = malloc(BUFFER);

       /* Currently no memory is locked */

  if ( mlockall(MCL_CURRENT) == -1 )
    perror("mlockall:1");

       /* All currently allocated memory is locked */

  p[1] = malloc(BUFFER);

       /* All memory but data pointed to by p[1] is locked */

  if ( munlockall() == -1 )
    perror("munlockall:1");

       /* No memory is now locked */

  if ( mlockall(MCL_FUTURE) == -1 )
    perror("mlockall:2");

       /* Only memory allocated in the future */
       /*   will be locked */

  p[2] = malloc(BUFFER);

       /* Only data pointed to by data[2] is locked */

  if ( mlockall(MCL_CURRENT|MCL_FUTURE) == -1 )
    perror("mlockall:3");

       /* All memory currently allocated and all memory that  */
       /* gets allocated in the future will be locked         */
}