VxWorks provides two levels of virtual memory support. The basic level is bundled with VxWorks and provides caching on a per-page basis. The full level is unbundled, and requires the optional component VxVMI. VxVMI provides write protection of text segments and the VxWorks exception vector table, and an architecture-independent interface to the CPU's memory management unit (MMU). For information on how to install VxVMI, see the Tornado Getting Started Guide.
For systems with an MMU, VxWorks allows you to perform DMA and interprocessor communication more efficiently by rendering related buffers noncacheable. This is necessary to ensure that data is not being buffered locally when other processors or DMA devices are accessing the same memory location. Without the ability to make portions of memory noncacheable, caching must be turned off globally (resulting in performance degradation) or buffers must be flushed/invalidated manually.
Basic virtual memory support is included by selecting INCLUDE_MMU_BASIC in the project facility VxWorks view; see 12.3 Virtual Memory Configuration. It is also possible to allocate noncacheable buffers using cacheDmaMalloc( ); see the reference entry for cacheLib.
The following discussion of configuration applies to both bundled and unbundled virtual memory support. In the project facility, define the constants in Table 12-1 to reflect your system configuration.
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
The appropriate default page size for your processor (4 KB or 8KB) is defined by VM_PAGE_SIZE in your BSP. If you must change this value for some reason, redefine VM_PAGE_SIZE in config.h. (See the Tornado User's Guide: Configuration and Build.)
To make memory noncacheable, it must have a virtual-to-physical mapping. The data structure PHYS_MEM_DESC in vmLib.h defines the parameters used for mapping physical memory. Each board's memory map is defined in sysLib.c using sysPhysMemDesc (which is declared as an array of PHYS_MEM_DESC). In addition to defining the initial state of the memory pages, the sysPhysMemDesc structure defines the virtual addresses used for mapping virtual-to-physical memory. For a discussion of page states, see Page States.
Modify the sysPhysMemDesc structure to reflect your system configuration. For example, you may need to add the addresses of interprocessor communication buffers not already included in the structure. Or, you may need to map and make noncacheable the VMEbus addresses of the shared-memory data structures. Most board support packages have a section of VME space defined in sysPhysMemDesc; however, this may not include all the space required by your system configuration.
I/O devices and memory not already included in the structure must also be mapped and made noncacheable. In general, off-board memory regions are specified as noncacheable; see VxWorks Network Programmer's Guide: Data Link Layer Network Components.
The following example configuration consists of multiple CPUs using the shared-memory network. A separate memory board is used for the shared-memory pool. Because this memory is not already mapped, it must be added to sysPhysMemDesc for all the boards on the network. The memory starts at 0x4000000 and must be made noncacheable, as shown in the following code excerpt:
/* shared memory */
{
(void *) 0x4000000, /* virtual address */
(void *) 0x4000000, /* physical address */
0x20000, /* length */
/* initial state mask */
VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE |VM_STATE_MASK_CACHEABLE,
/* initial state */
VM_STATE_VALID | VM_STATE_WRITABLE | VM_STATE_CACHEABLE_NOT
}
For MC680x0 boards, the virtual address must be the same as the physical address. For other boards, the virtual and physical addresses are the same as a matter of convention.
This section describes VxVMI's general use and configuration for write-protecting text segments and the exception vector table.
VxVMI uses the MMU to prevent portions of memory from being overwritten. This is done by write-protecting pages of memory. Not all target hardware supports write protection; see the architecture appendices in this manual for further information. For most architectures, the page size is 8KB. An attempt to write to a memory location that is write-protected causes a bus error.
When VxWorks is loaded, all text segments are write-protected; see 12.3 Virtual Memory Configuration. The text segments of additional object modules loaded using ld( ) are automatically marked as read-only. When object modules are loaded, memory to be write-protected is allocated in page-size increments. No additional steps are required to write-protect application code.
During system initialization, VxWorks write-protects the exception vector table. The only way to modify the interrupt vector table is to use the routine intConnect( ), which write-enables the exception vector table for the duration of the call.
To include write protection, select the following in the project facility VxWorks view:
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 12.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 12-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 12-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 12-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 12-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.
Example 12-1: Private Virtual Memory Contexts
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 */ #define NUM_PAGES (3)
/* context.c - use context switch hooks to make task private context current */
#include "vxWorks.h"
#include "vmLib.h"
#include "semLib.h"
#include "taskLib.h"
#include "taskHookLib.h"
#include "memLib.h"
#include "contextExample.h"
void privContextSwitch (WIND_TCB *pOldTask, WIND_TCB *pNewTask);
/*
* initContextSetup - install context switch hook
*/
STATUS initContextSetup ( )
{
/* Install switch hook */
if (taskSwitchHookAdd ((FUNCPTR) privContextSwitch) == ERROR)
return (ERROR);
return (OK);
}
/*
* contextSetup - initialize context and create separate memory partition
*
* Call only once for each task that wants a private context.
*
* This could be made into a create-hook routine if every task on the
* system needs a private context. To use as a create hook, the code for
* installing the new virtual memory context should be replaced by simply
* saving the new context in spare1 of the task's TCB.
*/
STATUS contextSetup (void)
{
VM_CONTEXT_ID pNewContext;
int pageSize;
int pageBlkSize;
char * pPhysAddr;
char * pVirtAddr;
UINT8 * globalPgBlkArray;
int newMemSize;
int index;
WIND_TCB * pTcb;
/* create context */
pNewContext = vmContextCreate();
/* get page and page block size */
pageSize = vmPageSizeGet ();
pageBlkSize = vmPageBlockSizeGet ();
newMemSize = pageSize * NUM_PAGES;
/* allocate physical memory that is page aligned */
if ((pPhysAddr = (char *) valloc (newMemSize)) == NULL)
return (ERROR);
/* Select virtual address to map. For this example, since only one page
* block is used per task, simply use the first address that is not a
* global mapping. vmGlobalInfoGet( ) returns a boolean array where each
* element corresponds to a block of virtual memory.
*/
globalPgBlkArray = vmGlobalInfoGet();
for (index = 0; globalPgBlkArray[index] == TRUE; index++)
;
pVirtAddr = (char *) (index * pageBlkSize);
/* map physical memory to new context */
if (vmMap (pNewContext, pVirtAddr, pPhysAddr, newMemSize) == ERROR)
{
free (pPhysAddr);
return (ERROR);
}
/*
* Set state in global virtual memory to be invalid - any access to
* this memory must be done through new context.
*/
if (vmStateSet(pNewContext, pPhysAddr, newMemSize, VM_STATE_MASK_VALID, VM_STATE_VALID_NOT) == ERROR) return (ERROR); /* get tasks TCB */ pTcb = taskTcb (taskIdSelf()); /* change virtual memory contexts */ /* * Stash the current vm context in the spare TCB field -- the switch * hook will install this when this task gets swapped out. */ pTcb->spare1 = (int) vmCurrentGet(); /* install new tasks context */ vmCurrentSet (pNewContext); /* create new memory partition and store id in task's TCB */ if ((pTcb->spare2 = (int) memPartCreate (pVirtAddr,newMemSize)) == NULL) return (ERROR); return (OK); }
/*
* privContextSwitch - routine to be executed on a context switch
*
* If old task had private context, save it. If new task has private
* context, install it.
*/
void privContextSwitch
(
WIND_TCB *pOldTcb,
WIND_TCB *pNewTcb
)
{
VM_CONTEXT_ID pContext = NULL;
/* If previous task had private context, save it--reset previous context. */
if (pOldTcb->spare1)
{
pContext = (VM_CONTEXT_ID) pOldTcb->spare1;
pOldTcb->spare1 = (int) vmCurrentGet ();
/* restore old context */
vmCurrentSet (pContext);
}
/*
* If next task has private context, map new context and save previous
* context in task's TCB.
*/
if (pNewTcb->spare1)
{
pContext = (VM_CONTEXT_ID) pNewTcb->spare1;
pNewTcb->spare1 = (int) vmCurrentGet();
/* install new tasks context */
vmCurrentSet (pContext);
}
}
/* taskExample.h - header file for testing VM contexts used by switch hook */
/* This code is used by the sample task. */
#define MAX (10000000)
typedef struct myStuff {
int stuff;
int myStuff;
} MY_DATA;
/* testTask.c - task code to test switch hooks */ #include "vxWorks.h" #include "memLib.h" #include "taskLib.h" #include "stdio.h" #include "vmLib.h" #include "taskExample.h"
IMPORT char *string = "test\n";
MY_DATA *pMem;
/*
* testTask - allocate private memory and use it
*
* Loop forever, modifying memory and printing out a global string. Use this
* in conjunction with testing from the shell. Since pMem points to private
* memory, the shell should generate a bus error when it tries to read it.
* For example:
* -> sp testTask
* -> d pMem
*/
STATUS testTask (void)
{
int val;
WIND_TCB *myTcb;
/* install private context */
if (contextSetup () == ERROR)
return (ERROR);
/* get TCB */
myTcb = taskTcb (taskIdSelf ());
/* allocate private memory */
if ((pMem = (MY_DATA *) memPartAlloc((PART_ID) myTcb->spare2,
sizeof (MY_DATA))) == NULL)
return (ERROR);
/*
* Forever, modify data in private memory and display string in
* global memory.
*/
FOREVER
{
for (val = 0; val <= MAX; val++)
{
/* modify structure */
pMem->stuff = val;
pMem->myStuff = val / 2;
/* make sure can access global virtual memory */
printf (string);
taskDelay (sysClkRateGet() * 10);
}
}
return (OK);
}
/*
* testVmContextGet - return a task's virtual memory context stored in TCB
*
* Used with vmContextShow()1
to display a task's virtual memory context.
* For example, from the shell, type:
* -> tid = sp (testTask)
* -> vmContextShow (testVmContextGet (tid))
*/
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 12-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].
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.
Example 12-2: Nonwritable Memory
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 */
#define MAX 1024
typedef struct myData
{
char stuff[MAX];
int moreStuff;
} MY_DATA;
/* privateCode.c - uses VM contexts to make data private to a code segment */
#include "vxWorks.h"
#include "vmLib.h"
#include "semLib.h"
#include "privateCode.h"
MY_DATA * pData;
SEM_ID dataSemId;
int pageSize;
/*
* initData - allocate memory and make it nonwritable
*
* This routine initializes data and should be called only once.
*
*/
STATUS initData (void)
{
pageSize = vmPageSizeGet();
/* create semaphore to protect data */
dataSemId = semBCreate (SEM_Q_PRIORITY, SEM_EMPTY);
/* allocate memory = to a page */
pData = (MY_DATA *) valloc (pageSize);
/* initialize data and make it read-only */
bzero (pData, pageSize);
if (vmStateSet (NULL, pData, pageSize, VM_STATE_MASK_WRITABLE,
VM_STATE_WRITABLE_NOT) == ERROR)
{
semGive (dataSemId);
return (ERROR);
}
/* release semaphore */
semGive (dataSemId);
return (OK);
}
/*
* dataModify - modify data
*
* To modify data, tasks must call this routine, passing a pointer to
* the new data.
* To test from the shell use:
* -> initData
* -> sp dataModify
* -> d pData
* -> bfill (pdata, 1024, 'X')
*/
STATUS dataModify
(
MY_DATA * pNewData
)
{
/* take semaphore for exclusive access to data */
semTake (dataSemId, WAIT_FOREVER);
/* make memory writable */
if (vmStateSet (NULL, pData, pageSize, VM_STATE_MASK_WRITABLE,
VM_STATE_WRITABLE) == ERROR)
{
semGive (dataSemId);
return (ERROR);
}
/* update data*/
bcopy (pNewData, pData, sizeof(MY_DATA));
/* make memory not writable */
if (vmStateSet (NULL, pData, pageSize, VM_STATE_MASK_WRITABLE,
VM_STATE_WRITABLE_NOT) == ERROR)
{
semGive (dataSemId);
return (ERROR);
}
semGive (dataSemId);
return (OK);
}
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 12.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.