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 Management, Section 4.1
Memory-Locking and Unlocking Functions, Section 4.2
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.
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.
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.
Perform nonrealtime tasks, such as opening files or allocating memory
Lock the address space of the process calling
mlockall
function
Perform realtime tasks
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.
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.
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.
#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"); }
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.
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.
#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 */ }