This section describes the facilities provided for manipulating the MMU programmatically using low-level routines in vmLib. You can make data private to a task or code segment, make portions of memory noncacheable, or write-protect portions of memory. The fundamental structure used to implement virtual memory is the virtual memory context (VMC).
For a summary of the VxVMI routines, see the reference entry for vmLib.
Some system objects, such as text segments and semaphores, must be accessible to all tasks in the system regardless of which virtual memory context is made current. These objects are made accessible by means of global virtual memory. Global virtual memory is created by mapping all the physical memory in the system (the mapping is defined in sysPhysMemDesc) to the identical address in the virtual memory space. In the default system configuration, this initially gives a one-to-one relationship between physical memory and global virtual memory; for example, virtual address 0x5000 maps to physical address 0x5000. On some architectures, it is possible to use sysPhysMemDesc to set up virtual memory so that the mapping of virtual-to-physical addresses is not one-to-one; see 7.3 Virtual Memory Configuration for additional information.
Global virtual memory is accessible from all virtual memory contexts. Modifications made to the global mapping in one virtual memory context appear in all virtual memory contexts. Before virtual memory contexts are created, add all global memory with vmGlobalMap( ). Global memory that is added after virtual memory contexts are created may not be available to existing contexts.
Global virtual memory is initialized by vmGlobalMapInit( ) in usrMmuInit( ), which is called from usrRoot( ). The routine usrMmuInit( ) is in installDir/target/src/config/usrMmuInit.c, and creates global virtual memory using sysPhysMemDesc. It then creates a default virtual memory context and makes the default context current. Optionally, it also enables the MMU.
Each virtual memory page (typically 8KB) has a state associated with it. A page can be valid/invalid, writable/nonwritable, or cacheable/noncacheable. See Table 7-2 for the associated constants.
|
|||||||||||||||||||
|
|||||||||||||||||||
|
|||||||||||||||||||
Change the state of a page with the routine vmStateSet( ). In addition to specifying the state flags, a state mask must describe which flags are being changed; see Table 7-3. Additional architecture-dependent states are specified in vmLib.h.
|
|||||||||||||||||||
|
|||||||||||||||||||
|
|||||||||||||||||||
Private virtual memory can be created by creating a new virtual memory context. This is useful for protecting data by making it inaccessible to other tasks or by limiting access to specific routines. Virtual memory contexts are not automatically created for tasks, but can be created and swapped in and out in an application-specific manner.
At system initialization, a default context is created. All tasks use this default context. To create private virtual memory, a task must create a new virtual memory context using vmContextCreate( ), and make it current. All virtual memory contexts share the global mappings that are created at system initialization; see Figure 7-1. Only the valid virtual memory in the current virtual memory context (including global virtual memory) is accessible. Virtual memory defined in other virtual memory contexts is not accessible. To make another memory context current, use vmCurrentSet( ).
To create a new virtual-to-physical mapping, use vmMap( ); both the physical and virtual address must be determined in advance. The physical memory (which must be page aligned) can be obtained using valloc( ). The easiest way to determine the virtual address is to use vmGlobalInfoGet( ) to find a virtual page that is not a global mapping. With this scheme, if multiple mappings are required, a task must keep track of its own private virtual memory pages to guarantee it does not map the same non-global address twice.
When physical pages are mapped into new sections of the virtual space, the physical page is accessible from two different virtual addresses (a condition known as aliasing): the newly mapped virtual address and the virtual address equal to the physical address in the global virtual memory. This can cause problems for some architectures, because the cache may hold two different values for the same underlying memory location. To avoid this, invalidate the virtual page (using vmStateSet( )) in the global virtual memory. This also ensures that the data is accessible only when the virtual memory context containing the new mapping is current.
Figure 7-2 depicts two private virtual memory contexts. The new context (pvmc2) maps virtual address 0x6000000 to physical address 0x10000. To prevent access to this address from outside of this virtual context (pvmc1), the corresponding physical address (0x10000) must be set to invalid. If access to the memory is made using address 0x10000, a bus error occurs because that address is now invalid.
In the following code example, private virtual memory contexts are used for allocating memory from a task's private memory partition. The setup routine, contextSetup( ), creates a private virtual memory context that is made current during a context switch. The virtual memory context is stored in the field spare1 in the task's TCB. Switch hooks are used to save the old context and install the task's private context. Note that the use of switch hooks increases the context switch time. A user-defined memory partition is created using the private virtual memory context. The partition ID is stored in spare2 in the tasks TCB. Any task wanting a private virtual memory context must call contextSetup( ). A sample task to test the code is included.
/* contextExample.h - header file for vm contexts used by switch hooks */
/* context.c - use context switch hooks to make task private context current */
/* taskExample.h - header file for testing VM contexts used by switch hook */
/* testTask.c - task code to test switch hooks */
VM_CONTEXT_ID testVmContextGet ( UINT tid ) { return ((VM_CONTEXT_ID) ((taskTcb (tid))->spare1)); }
Architectures that do not support bus snooping must disable the memory caching that is used for interprocessor communication (or by DMA devices). If multiple processors are reading from and writing to a memory location, you must guarantee that when the CPU accesses the data, it is using the most recent value. If caching is used in one or more CPUs in the system, there can be a local copy of the data in one of the CPUs' data caches. In the example in Figure 7-3, a system with multiple CPUs share data, and one CPU on the system (CPU 0) caches the shared data. A task on CPU 0 reads the data [1] and then modifies the value [2]; however, the new value may still be in the cache and not flushed to memory when a task on another CPU (CPU 1) accesses it [3]. Thus the value of the data used by the task on CPU 1 is the old value and does not reflect the modifications done by the task on CPU 0; that value is still in CPU 0's data cache [2].
To disable caching on a page basis, use vmStateSet( ); for example:
vmStateSet (pContext, pSData, len, VM_STATE_MASK_CACHEABLE, VM_STATE_CACHEABLE_NOT)
Memory can be marked as nonwritable. Sections of memory can be write-protected using vmStateSet( ) to prevent inadvertent access.
One use of this is to restrict modification of a data object to a particular routine. If a data object is global but read-only, tasks can read the object but not modify it. Any task that must modify this object must call the associated routine. Inside the routine, the data is made writable for the duration of the routine, and on exit, the memory is set to VM_STATE_WRITABLE_NOT.
In this code example, to modify the data structure pointed to by pData, a task must call dataModify( ). This routine makes the memory writable, modifies the data, and sets the memory back to nonwritable. If a task tries to read the memory, it is successful; however, if it tries to modify the data outside of dataModify( ), a bus error occurs.
/* privateCode.h - header file to make data writable from routine only */
/* privateCode.c - uses VM contexts to make data private to a code segment */
If INCLUDE_MMU_FULL_SHOW is included in the project facility VxWorks view, you can use vmContextShow( ) to display a virtual memory context on the standard output device. In the following example, the current virtual memory context is displayed. Virtual addresses between 0x0 and 0x59fff are write protected; 0xff800000 through 0xffbfffff are noncacheable; and 0x2000000 through 0x2005fff are private. All valid entries are listed and marked with a V+. Invalid entries are not listed.
-> vmContextShow 0 value = 0 = 0x0
The output is sent to the standard output device, and looks like the following:
VIRTUAL ADDR BLOCK LENGTH PHYSICAL ADDR STATE 0x0 0x5a000 0x0 W- C+ V+ (global) 0x5a000 0x1f3c000 0x5a000 W+ C+ V+ (global) 0x1f9c000 0x2000 0x1f9c000 W+ C+ V+ (global) 0x1f9e000 0x2000 0x1f9e000 W- C+ V+ (global) 0x1fa0000 0x2000 0x1fa0000 W+ C+ V+ (global) 0x1fa2000 0x2000 0x1fa2000 W- C+ V+ (global) 0x1fa4000 0x6000 0x1fa4000 W+ C+ V+ (global) 0x1faa000 0x2000 0x1faa000 W- C+ V+ (global) 0x1fac000 0xa000 0x1fac000 W+ C+ V+ (global) 0x1fb6000 0x2000 0x1fb6000 W- C+ V+ (global) 0x1fb8000 0x36000 0x1fb8000 W+ C+ V+ (global) 0x1fee000 0x2000 0x1fee000 W- C+ V+ (global) 0x1ff0000 0x2000 0x1ff0000 W+ C+ V+ (global) 0x1ff2000 0x2000 0x1ff2000 W- C+ V+ (global) 0x1ff4000 0x2000 0x1ff4000 W+ C+ V+ (global) 0x1ff6000 0x2000 0x1ff6000 W- C+ V+ (global) 0x1ff8000 0x2000 0x1ff8000 W+ C+ V+ (global) 0x1ffa000 0x2000 0x1ffa000 W- C+ V+ (global) 0x1ffc000 0x4000 0x1ffc000 W+ C+ V+ (global) 0x2000000 0x6000 0x1f96000 W+ C+ V+ 0xff800000 0x400000 0xff800000 W- C- V+ (global) 0xffe00000 0x20000 0xffe00000 W+ C+ V+ (global) 0xfff00000 0xf0000 0xfff00000 W+ C- V+ (global)
Memory that is marked as global cannot be remapped using vmMap( ). To add to global virtual memory, use vmGlobalMap( ). For further information on adding global virtual memory, see 7.5.2 Private Virtual Memory.
Performances of MMUs vary across architectures; in fact, some architectures may cause the system to become non-deterministic. For additional information, see the architecture-specific documentation for your hardware.
1: This routine is not built in to the Tornado shell. To use it from the Tornado shell, you must define INCLUDE_MMU_FULL_SHOW in your VxWorks configuration; see the Tornado User's Guide: Projects. When invoked this routine's output is sent to the standard output device.