From 433550b1b9d8d67650ebfb79048ca442249dac0d Mon Sep 17 00:00:00 2001 From: patacongo Date: Sun, 15 Aug 2010 15:02:45 +0000 Subject: Completes demand paging core implementation git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@2853 42af7a65-404d-4744-a932-0658087f49c3 --- nuttx/sched/Makefile | 4 +- nuttx/sched/os_internal.h | 8 +- nuttx/sched/os_start.c | 26 ++- nuttx/sched/pg_internal.h | 33 ++- nuttx/sched/pg_miss.c | 2 +- nuttx/sched/pg_worker.c | 537 ++++++++++++++++++++++++++++++++++++++++++++ nuttx/sched/work_internal.h | 2 +- 7 files changed, 594 insertions(+), 18 deletions(-) create mode 100755 nuttx/sched/pg_worker.c (limited to 'nuttx/sched') diff --git a/nuttx/sched/Makefile b/nuttx/sched/Makefile index 40392504c..b11511b65 100644 --- a/nuttx/sched/Makefile +++ b/nuttx/sched/Makefile @@ -1,7 +1,7 @@ ############################################################################ # sched/Makefile # -# Copyright (C) 2007-2009 Gregory Nutt. All rights reserved. +# Copyright (C) 2007-2010 Gregory Nutt. All rights reserved. # Author: Gregory Nutt # # Redistribution and use in source and binary forms, with or without @@ -140,7 +140,7 @@ WORK_SRCS = work_thread.c work_queue.c work_cancel.c endif ifeq ($(CONFIG_PAGING),y) -PGFILL_SRCS = pg_miss.c +PGFILL_SRCS = pg_miss.c pg_worker.c endif IRQ_SRCS = irq_initialize.c irq_attach.c irq_dispatch.c irq_unexpectedisr.c diff --git a/nuttx/sched/os_internal.h b/nuttx/sched/os_internal.h index d37000b6a..f2cbbf863 100644 --- a/nuttx/sched/os_internal.h +++ b/nuttx/sched/os_internal.h @@ -1,7 +1,7 @@ /**************************************************************************** * sched/os_internal.h * - * Copyright (C) 2007-2009 Gregory Nutt. All rights reserved. + * Copyright (C) 2007-2010 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -199,6 +199,12 @@ extern volatile dq_queue_t g_waitingformqnotempty; extern volatile dq_queue_t g_waitingformqnotfull; #endif +/* This is the list of all tasks that are blocking waiting for a page fill */ + +#ifdef CONFIG_PAGING +extern volatile dq_queue_t g_waitingforfill; +#endif + /* This the list of all tasks that have been initialized, but not yet * activated. NOTE: This is the only list that is not prioritized. */ diff --git a/nuttx/sched/os_start.c b/nuttx/sched/os_start.c index ac52c4d66..9e299a946 100644 --- a/nuttx/sched/os_start.c +++ b/nuttx/sched/os_start.c @@ -62,6 +62,9 @@ #include "clock_internal.h" #include "timer_internal.h" #include "irq_internal.h" +#ifdef CONFIG_PAGING +# include "pg_internal.h" +#endif #ifdef CONFIG_SCHED_WORKQUEUE # include "work_internal.h" #endif @@ -172,12 +175,6 @@ pidhash_t g_pidhash[CONFIG_MAX_TASKS]; pid_t g_worker; #endif -/* The task ID of the page fill worker thread */ - -#ifdef CONFIG_PAGING -pid_t g_pgworker; -#endif - /* This is a table of task lists. This table is indexed by * the task state enumeration type (tstate_t) and provides * a pointer to the associated static task list (if there @@ -451,6 +448,23 @@ void os_start(void) (void)sched_setupidlefiles(&g_idletcb); + /* Start the page fill worker thread that will resolve page faults. + * This should always be the first thread started because it may + * have to resolve page faults in other threads + */ + +#ifdef CONFIG_PAGING +#ifndef CONFIG_CUSTOM_STACK + g_pgworker = task_create("pgfill", CONFIG_PAGING_DEFPRIO, + CONFIG_PAGING_STACKSIZE, + (main_t)pg_worker, (const char **)NULL); +#else + g_pgworker = task_create("pgfill", CONFIG_PAGING_DEFPRIO, + (main_t)pg_worker, (const char **)NULL); +#endif + ASSERT(g_pgworker != ERROR); +#endif + /* Start the worker thread that will perform misc garbage clean-up */ #ifdef CONFIG_SCHED_WORKQUEUE diff --git a/nuttx/sched/pg_internal.h b/nuttx/sched/pg_internal.h index 5a22daad1..15f7bbb18 100755 --- a/nuttx/sched/pg_internal.h +++ b/nuttx/sched/pg_internal.h @@ -51,12 +51,20 @@ /* Configuration ************************************************************/ -#ifndef CONFIG_PAGING_WORKPRIORITY -# define CONFIG_PAGING_WORKPRIORITY 50 +/* Supply reasonable (but probably non-optimal) default settings if + * configuration items are omitted. + */ + +#ifndef CONFIG_PAGING_DEFPRIO +# define CONFIG_PAGING_DEFPRIO 50 +#endif + +#ifndef CONFIG_PAGING_WORKPERIOD +# define CONFIG_PAGING_WORKPERIOD (500*1000) /* 1/2 second */ #endif -#ifndef CONFIG_PAGING_WORKSTACKSIZE -# define CONFIG_PAGING_WORKSTACKSIZE CONFIG_IDLETHREAD_STACKSIZE +#ifndef CONFIG_PAGING_STACKSIZE +# define CONFIG_PAGING_STACKSIZE CONFIG_IDLETHREAD_STACKSIZE #endif #ifdef CONFIG_DISABLE_SIGNALS @@ -73,13 +81,24 @@ #ifndef __ASSEMBLY -/* If is the PID of the page fill worker thread */ +/* This is the task IDof the page fill worker thread. This value was set in + * os_start when the page fill worker thread was started. + */ extern pid_t g_pgworker; -/* If non-NULL, this points to the TCB of the task currently being filled */ +/* The page fill worker thread maintains a static variable called + * g_pendingfilltcb. If no fill is in progress, g_pendingfilltcb will be NULL. + * Otherwise, g_pendingfile will point to the TCB of the task which is + * receiving the fill that is in progess. + * + * NOTE: I think that this is the only state in which a TCB does not reside + * in some list. Here is it in limbo, outside of the normally queuing while + * the page file is in progress. Where here, it will be marked with + * TSTATE_TASK_INVALID. + */ -extern FAR _TCB *g_filltcb; +extern FAR _TCB *g_pendingfilltcb; /**************************************************************************** * Public Function Prototypes diff --git a/nuttx/sched/pg_miss.c b/nuttx/sched/pg_miss.c index d1fdf8b31..10680365b 100644 --- a/nuttx/sched/pg_miss.c +++ b/nuttx/sched/pg_miss.c @@ -166,7 +166,7 @@ void pg_miss(void) * thread to start working on the queued page fill requests. */ - if (!g_filltcb) + if (!g_pendingfilltcb) { kill(g_pgworker, SIGWORK); } diff --git a/nuttx/sched/pg_worker.c b/nuttx/sched/pg_worker.c new file mode 100755 index 000000000..51975b05a --- /dev/null +++ b/nuttx/sched/pg_worker.c @@ -0,0 +1,537 @@ +/**************************************************************************** + * sched/pg_worker.c + * Page fill worker thread implementation. + * + * Copyright (C) 2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "os_internal.h" +#include "pg_internal.h" + +#ifdef CONFIG_PAGING + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +#ifdef CONFIG_DISABLE_SIGNALS +# warning "Signals needed by this function (CONFIG_DISABLE_SIGNALS=n)" +#endif + +/**************************************************************************** + * Private Type Declarations + ****************************************************************************/ + +/**************************************************************************** + * Public Variables + ****************************************************************************/ + +/* This is the task ID of the page fill worker thread. This value was set in + * os_start when the page fill worker thread was started. + */ + +pid_t g_pgworker; + +/* The page fill worker thread maintains a static variable called + * g_pendingfilltcb. If no fill is in progress, g_pendingfilltcb will be NULL. + * Otherwise, g_pendingfile will point to the TCB of the task which is + * receiving the fill that is in progess. + * + * NOTE: I think that this is the only state in which a TCB does not reside + * in some list. Here is it in limbo, outside of the normally queuing while + * the page file is in progress. Where here, it will be marked with + * TSTATE_TASK_INVALID. + */ + +FAR _TCB *g_pendingfilltcb; + +/**************************************************************************** + * Private Variables + ****************************************************************************/ + +#ifndef CONFIG_PAGING_BLOCKINGFILL + +/* When a page fill completes, the result of the fill is stored here. The + * value -EBUSY means that the page fill callback has not yet been received. + */ + +static int g_fillresult; + +/* A configurable timeout period (in clock ticks) may be select to detect + * page fill failures. + */ + +#ifdef CONFIG_PAGING_TIMEOUT_TICKS +status uint32_t g_starttime; +#endif +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pg_callback + * + * Description: + * This function is called from the architecture-specific, page fill logic + * when the page fill completes (with or without an error). A reference to + * this function was provided to up_fillpage(). The driver will provide + * the result of the fill as an argument. + * + * NOTE: pg_callback() must also be locked in memory. + * + * When pg_callback() is called, it will perform the following operations: + * + * - Verify that g_pendingfilltcb is non-NULL. + * - Find the higher priority between the task waiting for the fill to + * complete in g_pendingfilltcb and the task waiting at the head of the + * g_waitingforfill list. That will be the priority of he highest priority + * task waiting for a fill. + * - If this higher priority is higher than current page fill worker thread, + * then boost worker thread's priority to that level. Thus, the page fill + * worker thread will always run at the priority of the highest priority + * task that is waiting for a fill. + * - Signal the page fill worker thread. + * + * Input parameters: + * tcb - The TCB of the task that just received the fill. + * result - The result of the page fill operation. + * + * Returned Value: + * None + * + * Assumptions: + * Possibly executing in the context of a driver interrupt handler??? + * + ****************************************************************************/ + +#ifndef CONFIG_PAGING_BLOCKINGFILL +static void pg_callback(FAR _TCB *tcb, int result) +{ + /* Verify that g_pendingfilltcb is non-NULL */ + + if (g_pendingfilltcb) + { + FAR _TCB *htcb = (FAR _TCB *)g_waitingforfill.head; + FAR _TCB *wtcb = sched_gettcb(g_pgworker); + + /* Find the higher priority between the task waiting for the fill to + * complete in g_pendingfilltcb and the task waiting at the head of the + * g_waitingforfill list. That will be the priority of he highest + * priority task waiting for a fill. + */ + + int priority = g_pendingfilltcb->sched_priority; + if (htcb && priority < htcb->sched_priority) + { + priority = htcb->sched_priority; + } + + /* If this higher priority is higher than current page fill worker + * thread, then boost worker thread's priority to that level. Thus, + * the page fill worker thread will always run at the priority of + * the highest priority task that is waiting for a fill. + */ + + if (priority > wtcb->sched_priority) + { + sched_setpriority(wtcb, priority); + } + + /* Save the page fill result (don't permit the value -EBUSY) */ + + if (result == -EBUSY) + { + result = -ENOSYS; + } + g_fillresult = result; + } + + /* Signal the page fill worker thread (in any event) */ + + kill(g_pgworker, SIGWORK); +} +#endif + +/**************************************************************************** + * Name: pg_startfill + * + * Description: + * Start a page fill operation on the thread whose TCB is at the head of + * of the g_waitingforfill task list. That is a prioritized list so that will + * be the highest priority task waiting for a page fill (in the event that + * are multiple tasks waiting for a page fill). + * + * This function may be called either (1) when the page fill worker thread + * is notified that there is a new page fill TCB in the g_waitingforfill + * prioritized list, or (2) when a page fill completes and there are more + * pages to be filled in g_waitingforfill list. + * + * Input parameters: + * None + * + * Returned Value: + * None + * + * Assumptions: + * Executing in the context of the page fill worker thread with all + * interrupts disabled. + * + ****************************************************************************/ + +static inline void pg_startfill(void) +{ + FAR void *vpage; + int result; + + /* Remove the TCB at the head of the g_waitfor fill list */ + + g_pendingfilltcb = (FAR _TCB *)dq_remfirst((dq_queue_t*)&g_waitingforfill); + if (g_pendingfilltcb != NULL) + { + /* Call the architecture-specific function up_checkmapping() 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. + */ + + result = up_checkmapping(g_pendingfilltcb); + if (result == OK) + { + up_unblock_task(g_pendingfilltcb); + g_pendingfilltcb = NULL; + return; + } + + /* Call up_allocpage(tcb, &vpage). This architecture-specific function will + * set aside page in memory and map to virtual address (vpage). If all + * available pages are in-use (the typical case), this function will select + * a page in-use, un-map it, and make it available. + */ + + result = up_allocpage(g_pendingfilltcb, &vpage); + DEBUGASSERT(result == OK); + + /* Start the fill. The exact way that the fill is started depends upon + * the nature of the architecture-specific up_fillpage() function -- Is it + * a blocking or a non-blocking call? + */ +#ifdef CONFIG_PAGING_BLOCKINGFILL + /* If CONFIG_PAGING_BLOCKINGFILL is defined, then up_fillpage is blocking + * call. In this case, up_fillpage() will accept only (1) a reference to + * the TCB that requires the fill. Architecture-specific context information + * within the TCB will be sufficient to perform the fill. And (2) the + * (virtual) address of the allocated page to be filled. The resulting + * status of the fill will be provided by return value from up_fillpage(). + */ + + result = up_fillpage(g_pendingfilltcb, vpage); + DEBUGASSERT(result == OK); +#else + /* If CONFIG_PAGING_BLOCKINGFILL is defined, then up_fillpage is non-blocking + * call. In this case up_fillpage() will accept an additional argument: The page + * fill worker thread will provide a callback function, pg_callback. + * + * Calling up_fillpage will start an asynchronous page fill. pg_callback + * ill be called when the page fill is finished (or an error occurs). This + * This callback will probably from interrupt level. + */ + + result = up_fillpage(g_pendingfilltcb, vpage, pg_callback); + DEBUGASSERT(result == OK); + + /* Save the time that the fill was started. These will be used to check for + * timeouts. + */ + +#ifdef CONFIG_PAGING_TIMEOUT_TICKS + g_starttime = g_system_timer; +#endif + + /* Return and wait to be signaled for the next event -- the fill completion + * event. While the fill is in progress, other tasks may execute. If + * another page fault occurs during this time, the faulting task will be + * blocked, its TCB will be added (in priority order) to g_waitingforfill + * and the priority of the page worker task may be boosted. But no action + * will be taken until the current page fill completes. NOTE: The IDLE task + * must also be fully locked 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. + */ +#endif /* CONFIG_PAGING_BLOCKINGFILL */ + } +} + +/**************************************************************************** + * Name: pg_alldone + * + * Description: + * Called by the page fill worker thread when all pending page fill + * operations have been completed and the g_waitingforfill list is empty. + * + * This functin will perform the following operations: + * + * - Set g_pendingfilltcb to NULL. + * - Restore the default priority of the page fill worker thread. + * + * Input parameters: + * None. + * + * Returned Value: + * None + * + * Assumptions: + * Executing in the context of the page fill worker thread with interrupts + * disabled. + * + ****************************************************************************/ + +static inline void pg_alldone(void) +{ + FAR _TCB *wtcb = (FAR _TCB *)g_readytorun.head; + g_pendingfilltcb = NULL; + sched_setpriority(wtcb, CONFIG_PAGING_DEFPRIO); +} + +/**************************************************************************** + * Name: pg_fillcomplete + * + * Description: + * Called by the page fill worker thread when a page fill completes. + * Either (1) in the non-blocking up_fillpage(), after the architecture- + * specific driver call the pg_callback() to wake up the page fill worker + * thread, or (2) after the blocking up_fillpage() returens (when + * CONFIG_PAGING_BLOCKINGFILL is defined). + * + * This function is just a dumb wrapper around up_unblocktask(). This + * function simply makes the task that just received the fill ready-to-run. + * + * Input parameters: + * None. + * + * Returned Value: + * None + * + * Assumptions: + * Executing in the context of the page fill worker thread with interrupts + * disabled. + * + ****************************************************************************/ + +static inline void pg_fillcomplete(void) +{ + /* Call up_unblocktask(g_pendingfilltcb) to make the task that just + * received the fill ready-to-run. + */ + + up_unblock_task(g_pendingfilltcb); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +/**************************************************************************** + * Name: pg_worker + * + * Description: + * This is the page fill worker thread that performs pages fills for tasks + * that have received a pag fault and are blocked in the g_waitingforfill + * task queue. + * + * The page fill worker thread will be awakened on one of three conditions: + * - When signaled by pg_miss(), the page fill worker thread will be + * awakenend, or + * - if CONFIG_PAGING_BLOCKINGFILL is not defined, from pg_fillcomplete() + * after completing a page fill. + * - A configurable timeout with no activity. + * + * Input parameters: + * argc, argv (not used) + * + * Returned Value: + * Does not return + * + ****************************************************************************/ + +int pg_worker(int argc, char *argv[]) +{ + irqstate_t flags; + + /* Loop forever -- Notice that interrupts will be disable at all times that + * this thread runs. That is so that we con't lose signals or have + * asynchronous page faults. + * + * All interrupt logic as well as all page fill worker thread logic must + * be locked in memory. Therefore, keeping interrupts disabled here + * should prevent any concurrent page faults. Any page faults or page + * fill completions should occur while this thread sleeps. + */ + + flags = irqsave(); + for (;;) + { + /* Wait awhile. We will wait here until either the configurable timeout + * elapses or until we are awakened by a signal (which terminates the + * usleep with an EINTR error). Note that interrupts will be re-enabled + * while this task sleeps. + * + * The timeout is a failsafe that will handle any cases where a single + * is lost (that would really be a bug and shouldn't happen!) and also + * supports timeouts for case of non-blocking, asynchronous fills. + */ + + usleep(CONFIG_PAGING_WORKPERIOD); + + /* The page fill worker thread will be awakened on one of three conditions: + * + * - When signaled by pg_miss(), the page fill worker thread will be awakenend, + * - if CONFIG_PAGING_BLOCKINGFILL is not defined, from pg_callback() + * after completing a page fill, or + * - On a configurable timeout expires with no activity. + * + * Interrupts are still disabled. + */ + +#ifdef CONFIG_PAGING_BLOCKINGFILL + /* For the non-blocking up_fillpage(), the page fill worker thread will detect + * that the page fill is complete when it is awakened with g_pendingfilltcb non-NULL + * and fill completion status from pg_callback. + */ + + if (g_pendingfilltcb != NULL) + { + /* If it is a real page fill completion event, then the result of the page + * fill will be in g_fillresult and will not be equal to -EBUSY. + */ + + if (g_fillresult != -EBUSY) + { + /* Any value other than OK, brings the system down */ + + ASSERT(g_fillresult == OK); + + /* Handle the page fill complete event */ + + pg_fillcomplete(); + + /* Check if there are are more pending page fills */ + + if (g_waitingforfill.head != NULL) + { + /* Yes .. Start the next asynchronous fill */ + + pg_startfill(); + } + else + { + /* Otherwise, there is nothing more to do */ + + pg_alldone(); + } + } + + /* If a configurable timeout period expires with no page fill completion + * event, then declare a failure. + */ + +#if defined() && defined(CONFIG_PAGING_TIMEOUT_TICKS) + else + { + dbg("Timeout!\n"); + ASSERT(g_system_timer - g_starttime < CONFIG_PAGING_TIMEOUT_TICKS); + } +#endif + } + + /* Otherwise, this might be a page fill initiation event. When + * awakened from pg_miss(), no fill will be in progress and + * g_pendingfilltcb will be NULL. + */ + + else + { + /* Are there tasks blocked and waiting for a fill? */ + + if (g_waitingforfill.head != NULL) + { + /* Yes .. Start the asynchronous fill */ + + pg_startfill(); + } + } +#else + /* Are there tasks blocked and waiting for a fill? Loop until all + * pending fills have been processed + */ + + while (g_waitingforfill.head != NULL) + { + /* Yes .. Start the fill and block until the fill completes */ + + pg_startfill(); + + /* Handle the page fill complete event. In the non-blocking case, + * the page fill worker thread will know that the page fill is + * complete when pg_startfill() returns. + */ + + pg_fillcomplete(); + } + + /* All queued fills have been processed */ + + pg_alldone(); +#endif + } + return OK; /* To keep some compilers happy */ +} +#endif /* CONFIG_PAGING */ diff --git a/nuttx/sched/work_internal.h b/nuttx/sched/work_internal.h index 840577859..a25c9615b 100755 --- a/nuttx/sched/work_internal.h +++ b/nuttx/sched/work_internal.h @@ -1,7 +1,7 @@ /**************************************************************************** * sched/work_internal.h * - * Copyright (C) 2009 Gregory Nutt. All rights reserved. + * Copyright (C) 2009-2010 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without -- cgit v1.2.3