nuttx/Documentation/NuttXDemandPaging.html
patacongo 03f1e72ad8 update
git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@2852 42af7a65-404d-4744-a932-0658087f49c3
2010-08-14 16:42:20 +00:00

632 lines
24 KiB
HTML
Executable File

<html>
<head>
<title>On-Demand Paging</title>
</head>
<body background="backgd.gif">
<hr><hr>
<table width ="100%">
<tr align="center" bgcolor="#e4e4e4">
<td>
<h1><big><font color="#3c34ec"><i>On-Demand Paging</i></font></big></h1>
<h2><font color="#dc143c">&gt;&gt;&gt; Under Construction &lt;&lt;&lt;</font></h2>
<p>Last Updated: August 12, 2010</p>
</td>
</tr>
</table>
<hr><hr>
<table width ="100%">
<tr bgcolor="#e4e4e4">
<td>
<h1>Table of Contents</h1>
</td>
</tr>
</table>
<center><table width ="80%">
<tr>
<td>
<table>
<tr>
<td valign="top" width="22"><img height="20" width="20" src="favicon.ico"></td>
<td>
<a href="#Introduction">Introduction</a>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<a href="#Overview">Overview</a>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<a href="#Terminology">Terminology</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table>
<tr>
<td valign="top" width="22"><img height="20" width="20" src="favicon.ico"></td>
<td>
<a href="#NuttXDesign">NuttX Common Logic Design Description</a>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<a href="#Initialization">Initialization</a>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<a href="#PageFaults">Page Faults</a>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<a href="#Fillnitiation">Fill Initiation</a>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<a href="#FillComplete">Fill Complete</a>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<a href="#TaskResumption">Task Resumption</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table>
<tr>
<td valign="top" width="22"><img height="20" width="20" src="favicon.ico"></td>
<td>
<a href="#ArchSupport">Architecture-Specific Support Requirements</a>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<a href="#MemoryOrg">Memory Organization</a>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<a href="#ArchFuncs">Architecture-Specific Functions</a>
</td>
</tr>
</table>
</td>
</tr>
</table></center>
<table width ="100%">
<tr bgcolor="#e4e4e4">
<td>
<a name="Introduction"><h1>Introduction</h1></a>
</td>
</tr>
</table>
<a name="Overview"><h2>Overview</h2></a>
<p>
This document summarizes the design of NuttX on-demand page.
This feature permits embedded MCUs with some limited RAM space to execute large programs some some non-random access media.
This feature was first discussed in this email thread:
<a href="http://tech.groups.yahoo.com/group/nuttx/message/213">http://tech.groups.yahoo.com/group/nuttx/message/213</a>.
</p>
<p>
What kind of platforms can support NuttX on-demang paging?
<ol>
<li>
The MCU should have some large, probably low-cost non-volatile storage such as serial FLASH or an SD card.
This storage probably does not support non-random access (otherwise, why not just execute the program directly on the storage media).
SD and serial FLASH are inexpensive and do not require very many pins and SPI support is prevalent in just about all MCUs.
This large serial FLASH would contain a big program. Perhaps a program of several megabytes in size.
</li>
<li>
The MCU must have a (relatively) small block of fast SRAM from which it can execute code.
A size of, say 256Kb (or 192Kb as in the ea3131) would be sufficient for many applications.
</li>
<li>
The MCU has an MMU (again like the ea3131).
</li>
</ol>
</p>
<p>
If the platforms meets these requirement, then NuttX can provide on-demand paging:
It can copy .text from the large program in non-volatile media into RAM as needed to execute the huge program from the small RAM.
</p>
<a name="Terminology"><h2>Terminology</h2></a>
<dl>
<dt><code>g_waitingforfill</code></dt>
<dd>An OS list that is used to hold the TCBs of tasks that are waiting for a page fill.</dd>
<dt><code>g_pendingfill</code></dt>
<dd>A variable that holds a reference to the TCB of the thread that is currently be re-filled.</dd>
<dt><code>g_pgworker</code></dt>
<dd>The <i>process</i> ID of of the thread that will perform the page fills.</dd>
<dt><code>pg_callback()</code></dt>
<dd>The callback function that is invoked from a driver when the fill is complete.</dd>
<dt><code>pg_miss()</code></dt>
<dd>The function that is called from chip-specific code to handle a page fault.</dd>
<dt><code>TCB</code></dt>
<dd>Task Control Block</dd>
</dl>
<table width ="100%">
<tr bgcolor="#e4e4e4">
<td>
<a name="NuttXDesign"><h1>NuttX Common Logic Design Description</h1></a>
</td>
</tr>
</table>
<a name="Initialization"><h2>Initialization</h2></a>
<p>
The following declarations will be added.
<ul>
<li>
<b><code>g_waitingforfill</code></b>.
A doubly linked list that will be used to implement a prioritized list of the TCBs of tasks that are waiting for a page fill.
</li>
<li>
<b><code>g_pgworker</code></b>.
The <i>process</i> ID of of the thread that will perform the page fills
</li>
</ul>
</p>
<p>
During OS initialization in <code>sched/os_start.c</code>, the following steps
will be performed:
<ul>
<li>
The <code>g_waitingforfill</code> queue will be initialized.
</li>
<li>
The special, page fill worker thread, will be started.
The <code>pid</code> of the page will worker thread will be saved in <code>g_pgworker</code>.
Note that we need a special worker thread to perform fills;
we cannot use the &quot;generic&quot; worker thread facility because we cannot be
assured that all actions called by that worker thread will always be resident in memory.
</li>
</ul>
</p>
<p>
Declarations for <code>g_waitingforfill</code>, <code>g_pgworker</code>, and other
internal, private definitions will be provided in <code>sched/pg_internal.h</code>.
All public definitions that should be used by the chip-specific code will be available
in <code>include/nuttx/page.h</code> and <code>include/nuttx/arch.h</code>.
</p>
<a name="PageFaults"><h2>Page Faults</h2></a>
<p>
<b>Page fault exception handling</b>.
Page fault handling is performed by the function <code>pg_miss()</code>.
This function is called from architecture-specific memory segmentation
fault handling logic. This function will perform the following
operations:
<ol>
<li>
<b>Sanity checking</b>.
This function will ASSERT if the currently executing task is the page fill worker thread.
The page fill worker thread is how the the page fault is resolved and all logic associated with the page fill worker
must be &quot;<a href="#MemoryOrg">locked</a>&quot; and always present in memory.
</li>
<li>
<b>Block the currently executing task</b>.
This function will call <code>up_block_task()</code> to block the task at the head of the ready-to-run list.
This should cause an interrupt level context switch to the next highest priority task.
The blocked task will be marked with state <code>TSTATE_WAIT_PAGEFILL</code> and will be retained in the <code>g_waitingforfill</code> prioritized task list.
</li>
<li>
<b>Boost the page fill worker thread priority</b>.
Check the priority of the task at the head of the <code>g_waitingforfill</code> list.
If the priority of that task is higher than the current priority of the page fill worker thread, then boost the priority of the page fill worker thread to that priority.
</li>
<li>
<b>Signal the page fill worker thread</b>.
Is there a page already being filled?
If not then signal the page fill worker thread to start working on the queued page fill requests.
</li>
</ol>
</p>
<p>
When signaled from <code>pg_miss()</code>, the page fill worker thread will be awakenend and will initiate the fill operation.
</p>
<p>
<b>Input Parameters.</b>
None -- The head of the ready-to-run list is assumed to be that task that caused the exception.
The current task context should already be saved in the TCB of that task.
No additional inputs are required.
</p>
<p>
<b>Assumptions</b>.
<ul>
<li>
It is assumed that this function is called from the level of an exception handler and that all interrupts are disabled.
</li>
<li>
The <code>pg_miss()</code> must be &quot;<a href="#MemoryOrg">locked</a>&quot; in memory.
Calling <code>pg_miss()</code> cannot cause a nested page fault.
</li>
<li>
It is assumed that currently executing task (the one at the head of the ready-to-run list) is the one that cause the fault.
This will always be true unless the page fault occurred in an interrupt handler.
Interrupt handling logic must always be available and &quot;<a href="#MemoryOrg">locked</a>&quot; into memory so that page faults never come from interrupt handling.
</li>
<li>
The chip-specific page fault exception handling has already verified that the exception did not occur from interrupt/exception handling logic.
</li>
<li>
As mentioned above, the task causing the page fault must not be the page fill worker thread because that is the only way to complete the page fill.
</li>
</ul>
</p>
<p>
<b>Locking code in Memory</b>.
One way to accomplish this would be a two phase link:
<ul>
<li>
In the first phase, create a partially linked objected containing all interrupt/exception handling logic, the page fill worker thread plus all parts of the IDLE thread (which must always be available for execution).
</li>
<li>
All of the <code>.text</code> and <code>.rodata</code> sections of this partial link should be collected into a single section.
</li>
<li>
The second link would link the partially linked object along with the remaining object to produce the final binary.
The linker script should position the &quot;special&quot; section so that it lies in a reserved, &quot;non-swappable&quot; region.
</ul>
</p>
<a name="FillInitiation"><h2>Fill Initiation</h2></a>
<p>
The page fill worker thread will be awakened on one of two conditions:
<ul>
<li>
When signaled by <code>pg_miss()</code>, the page fill worker thread will be awakenend (see above), or
</li>
<li>
From <code>pg_fillcomplete()</code> after completing last fill (see below).
</li>
</ul>
</p>
<p>
The page fill worker thread will maintain a static variable called <code>_TCB *g_pendingfill</code>.
If not fill is in progress, <code>g_pendingfill</code> will be NULL.
Otherwise, will point to the TCB of the task which is receiving the fill that is in progess.
</p>
<p>
When awakened from <code>pg_miss()</code>, no fill will be in progress and <code>g_pendingfill</code> will be NULL.
In this case, the page fill worker thread will call <code>pg_startfill()</code>.
That function will perform the following operations:
<ul>
<li>
Call the architecture-specific function <code>up_checkmapping()</code> to see if the page fill
still needs to be performed.
In certain conditions, the page fault may occur on several threads and be queued multiple times.
In this corner case, the blocked task will simply be restarted (see the logic below for the
case of normal completion of the fill operation).
</li>
<li>
Call <code>up_allocpage(tcb, &vpage)</code>.
This chip-specific function will set aside page in memory and map to virtual address (vpage).
If all pages available pages are in-use (the typical case),
this function will select a page in-use, un-map it, and make it available.
</li>
<li>
Call the chip-specific function <code>up_fillpage(page, pg_callback)</code>.
This will start asynchronous page fill.
The page fill worker thread will provide a callback function, <code>pg_callback</code>,
that will be called when the page fill is finished (or an error occurs).
This callback will probably from interrupt level.
</li>
<li>
Restore default priority of the page fill worker thread's default priority and wait to be signaled for the next event -- the fill completion event.
</li>
</ul>
</p>
<p>
While the fill is in progress, other tasks may execute.
If another page fault occurs during this time, the faulting task will be blocked and its TCB will be added (in priority order) to <code>g_waitingforfill</code>.
But no action will be taken until the current page fill completes.
NOTE: The IDLE task must also be fully <a href="#MemoryOrg">locked</a> in memory.
The IDLE task cannot be blocked.
It the case where all tasks are blocked waiting for a page fill, the IDLE task must still be available to run.
<p>
The chip-specific functions, <code>up_allocpage(tcb, &vpage)</code> and <code>up_fillpage(page, pg_callback)</code>
will be prototyped in <code>include/nuttx/arch.h</code>
</p>
<a name="FillComplete"><h2>Fill Complete</h2></a>
<p>
When the chip-specific driver completes the page fill, it will call the <code>pg_callback()</code> that was provided to <code>up_fillpage</code>.
<code>pg_callback()</code> will probably be called from driver interrupt-level logic.
The driver ill provide the result of the fill as an argument.
NOTE: <code>pg_callback()</code> must also be <a href="#MemoryOrg">locked</a> in memory.
</p>
<p>
When <code>pg_callback()</code> is called, it will perform the following operations:
<ul>
<li>
Verify that <code>g_pendingfill</code> is non-NULL.
</li>
<li>
If the priority of thread in <code>g_pendingfill</code> is higher than page fill worker thread, boost work thread to that level.
</li>
<li>
Signal the page fill worker thread.
</li>
</ul>
</p>
<a name="TaskResumption"><h2>Task Resumption</h2></a>
<p>
When the page fill worker thread is awakened and <code>g_pendingfill</code> is non-NULL (and other state variables are in concurrence),
the page fill thread will know that is was awakened because of a page fill completion event.
In this case, the page fill worker thread will:
<ul>
<li>
Verify consistency of state information and <code>g_pendingfill</code>.
</li>
<li>
Verify that the page fill completed successfully, and if so,
</li>
<li>
Call <code>up_unblocktask(g_pendingfill)</code> to make the task that just received the fill ready-to-run.
</li>
<li>
Check if the <code>g_waitingforfill</code> list is empty.
If not:
<ul>
<li>
Remove the highest priority task waiting for a page fill from <code>g_waitingforfill</code>,
</li>
<li>
Save the task's TCB in <code>g_pendingfill</code>,
</li>
<li>
If the priority of the thread in <code>g_pendingfill</code>, is higher in priority than the default priority of the page fill worker thread, then set the priority of the page fill worker thread to that priority.
</li>
<li>
Call <code>pg_startfill()</code> which will start the next fill (as described above).
</li>
</ul>
</li>
<li>
Otherwise,
<ul>
<li>
Set <code>g_pendingfill</code> to NULL.
</li>
<li>
Restore the default priority of the page fill worker thread.
</li>
<li>
Wait for the next fill related event (a new page fault).
</li>
</ul>
</li>
</ul>
</p>
<table width ="100%">
<tr bgcolor="#e4e4e4">
<td>
<a name="ArchSupport"><h1>Architecture-Specific Support Requirements</h1></a>
</td>
</tr>
</table>
<a name="MemoryOrg"><h2>Memory Organization</h2></a>
<p>
Chip specific logic will map the virtual and physical address spaces into three general regions:
<ol>
<li>
A .text region containing &quot;<a href="#MemoryOrg">locked-in-memory</a>&quot; code that is always avaialable and will never cause a page fault.
This locked memory is loaded at boot time and remains resident for all time.
This memory regions must include:
<ul>
<li>
All logic for all interrupt pathes.
All interrupt logic must be locked in memory because the design present here will not support page faults from interrupt handlers.
This includes the page fault handling logic and <a href="#PageFaults"><code>pg_miss()</code></a> that is called from the page fault handler.
It also includes the <a href="#FillComplete"><code>pg_callback()</code></a> function that wakes up the page fill worker thread
and whatever chip-specific logic that calls <code>pg_callback()</code>.
</li>
<li>
All logic for the IDLE thread.
The IDLE thread must always be ready to run and cannot be blocked for any reason.
</li>
<li>
All of the page fill worker thread must be locked in memory.
This thread must execute in order to unblock any thread waiting for a fill.
It this thread were to block, there would be no way to complete the fills!
</ul>
</li>
<li>
A .text region containing pages that can be assigned allocated, mapped to various virtual addresses, and filled from some mass storage medium.
</li>
<li>
And a fixed RAM space for .bss, .text, and .heap.
</li>
</ol>
</p>
<p>
This memory organization is illustrated in the following table.
Notice that:
<ul>
<li>
There is a one-to-one relationship between pages in the virtual address space and between pages of .text in the non-volatile mass storage device.
</li>
<li>
There are, however, far fewer physical pages available than virtual pages.
Only a subset of physical pages will be mapped to virtual pages at any given time.
This mapping will be performed on-demand as needed for program execution.
</ul>
</p>
<center><table width="80%">
<tr>
<th width="33%">SRAM</th>
<th width="33%">Virtual Address Space</th>
<th width="34%">Non-Volatile Storage</th>
</tr>
<tr>
<td>&nbsp;</td>
<td bgcolor="lightslategray">DATA</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td bgcolor="lightskyblue">Virtual Page <i>n</i> (<i>n</i> > <i>m</i>)</td>
<td bgcolor="lightskyblue">Stored Page <i>n</i></td>
</tr>
<tr>
<td>&nbsp;</td>
<td bgcolor="lightskyblue">Virtual Page <i>n-1</i></td>
<td bgcolor="lightskyblue">Stored Page <i>n-1</i></td>
</tr>
<tr>
<td bgcolor="lightslategray">DATA</td>
<td bgcolor="lightskyblue">...</td>
<td bgcolor="lightskyblue">...</td>
</tr>
<tr>
<td bgcolor="lightskyblue">Physical Page <i>m</i> (<i>m</i> < <i>n</i>)</td>
<td bgcolor="lightskyblue">...</td>
<td bgcolor="lightskyblue">...</td>
</tr>
<tr>
<td bgcolor="lightskyblue">Physical Page <i>m-1</i></td>
<td bgcolor="lightskyblue">...</td>
<td bgcolor="lightskyblue">...</td>
</tr>
<tr>
<td bgcolor="lightskyblue">...</td>
<td bgcolor="lightskyblue">...</td>
<td bgcolor="lightskyblue">...</td>
</tr>
<tr>
<td bgcolor="lightskyblue">Physical Page <i>1</i></td>
<td bgcolor="lightskyblue">Virtual Page <i>1</i></td>
<td bgcolor="lightskyblue">Stored Page <i>1</i></td>
</tr>
<tr>
<td bgcolor="slategray">Locked Memory</td>
<td bgcolor="slategray">Locked Memory</td>
<td bgcolor="slategray">Memory Resident</td>
</tr>
</table></center>
<p>
As an example, suppose that the size of the SRAM is 192Kb (as in the NXP LPC3131). And suppose further that:
<ul>
<li>
The size of the locked, memory resident .text area is 32Kb, and
</li>
<li>
The size of the DATA area is 64Kb.
</li>
<li>
The size of one, managed page is 1Kb.
</li>
<li>
The size of the whole .text image on the non-volatile, mass storage device is 1024Kb.
</li>
</ul>
<p>
Then, the size of the locked, memory resident code is 32Kb (32 pages).
The size of the physical page region is 96Kb (96 pages), and the
size of the data region is 64 pages.
And the size of the virtual paged region must then be greater than or equal to (1024-32) or 992 pages (<i>m</i>).
</p>
<a name="ArchFuncs"><h2>Architecture-Specific Functions</h2></a>
<p>
Standard functions that should already be provided in the architecture port:
</p>
<ul><dl>
<dt>
<code>void up_block_task(FAR _TCB *tcb, tstate_t task_state);</code>
</dt>
<dd>
The currently executing task at the head of the ready to run list must be stopped.
Save its context and move it to the inactive list specified by task_state.
This function is called by the on-demand paging logic in order to block the task that requires the
page fill, and to
</dd>
<dt>
<code>void up_unblock_task(FAR _TCB *tcb);</code>
</dt>
<dd>
A task is currently in an inactive task list but has been prepped to execute.
Move the TCB to the ready-to-run list, restore its context, and start execution.
This function will be called
</dd>
</dl></ul>
<p>
New, additional functions that must be implemented just for on-demand paging support:
</p>
<ul><dl>
<dt>
<code>int up_checkmapping(FAR _TCB *tcb);</code>
</dt>
<dd>
The function <code>up_checkmapping()</code> returns an indication that checks if the page fill still needs to performed or not.
In certain conditions, the page fault may occur on several threads and be queued multiple times.
This function will prevent the same page from be filled multiple times.
</dd>
<dt>
<code>int up_allocpage(FAR _TCB *tcb, FAR void *vpage);</code>
</dt>
<dd>
This chip-specific function will set aside page in memory and map to its correct virtual address.
Architecture-specific context information saved within the TCB will provide the function with the information need to identify the virtual miss address.
This function will return the allocated physical page address in <code>paddr</code>.
The size of a physical page is determined by the configuration setting <code>CONFIG_PAGING_PAGESIZE</code>.
NOTE: This function must <i>always</i> return a page allocation.
If all pages available pages are in-use (the typical case), then this function will select a page in-use, un-map it, and make it available.
</dd>
<dt><code>int up_fillpage(FAR _TCB *tcb, FAR const void *vpage, void (*pg_callback)(FAR _TCB *tcb, int result));</code>
</dt>
The actual filling of the page with data from the non-volatile, be performed by a separate call to the architecture-specific function, <code>up_fillpage()</code>.
This will start asynchronous page fill.
The common logic will provide a callback function, <code>pg_callback</code>, that will be called when the page fill is finished (or an error occurs).
This callback is assumed to occur from an interrupt level when the device driver completes the fill operation.
</dt>
</dl></ul>
</body>
</html>