[BeRTOS] [RFC][PATCH v4] kernel preemption

Andrea Righi arighi at develer.com
Tue Jan 12 01:04:00 CET 2010


Here's the new version. Most of the changes are a result of an
interesting discussion in chat together with Francesco and Bernie (see
the Changelog for details).

Note: all is still experimental anyway, so something may not work and/or
may change in the near future.

Please review and test it. ;)

How to test:
 1) apply the patch to the latest trunk

 2) build the demo application:
    $ make
   (build ./images/demo using the cooperative scheduler)

    $ make PREEMPT=1
   (build ./images/demo using the preemptible scheduler)

Changelog (v3 -> v4)
~~~~~~~~~~~~~~~~~~~~
 - context switch redesign: voluntary preemption context switch is
   considered as a function call (only the callee-save registers are
   saved / restored); kernel preemption context switch must be interrupt
   safe: all the registers (minus the callee-save registers) are saved
   inside an interrupt entry point - IRQ_ENTRY() - and restored at the end
   of an interrupt exit point - IRQ_EXIT(). Last one must also take care of
   switching from interrupt-context mode to user-context mode and call the
   voluntary preemption context switch (as it happened in a normal
   user-context).

   In this way context switch always happens from user-context (and BTW
   for this reason the same routines of the cooperative scheduler can be
   used). Only when strictly needed (kernel preemption on
   interrupt/signal) all the registers are saved/restored and the stack of
   a process is never corrupted by different user/irq contextes.

   TODO: arch-dependent interrupt context switch routines must be
   implemented, and IRQ_ENTRY / IRQ_EXIT markers must be added to all
   (as many as possible) interrupt service routines. For now,
   IRQ_ENTRY/IRQ_EXIT have been added only to the timer ISR.

 - preemption redesign: preemption is explicitly regulated inside the
   any interrupt service routine (IRQ_EXIT). Each task obtains a time
   quantum when scheduled on the CPU and its quantum is decremented at each
   clock tick. When the quantum expires the handler proc_preempt() checks
   if the preemption is enabled and in this case proc_schedule() is called,
   that possibly replaces the current running thread with a different one.
   Preemption may also happen when a higher priority task is added to
   the ready list.

 - process priority: a high-priority process can preempt a low-priority
   process immediately (it will be descheduled and replaced in the
   interrupt exit point). Processes running at the same priority can be
   descheduled when they expire the time quantum. So, actually this is a
   preemptible round-robin scheduler for the tasks that runs at the same
   priority.

 - sleep() aka timer_delayTicks() fallbacks to a busy wait if called when
   preemption is disabled (even when CONFIG_KERN_SIGNALS is enabled)

 - idle directly calls proc_switch() instead of proc_yield() (in this way
   we can remove some CurrentProcess != idle_proc checks in some core
   scheduler routines)

 - code restyling & new comments

---
 bertos/cpu/attr.h             |    8 ++
 bertos/cpu/frame.h            |    4 +-
 bertos/cpu/irq.h              |    8 --
 bertos/cpu/types.h            |    3 +
 bertos/drv/timer.c            |   43 +++++---
 bertos/kern/coop.c            |   11 ++
 bertos/kern/idle.c            |   25 +++--
 bertos/kern/idle.h            |    2 +
 bertos/kern/irq.c             |   26 -----
 bertos/kern/irq.h             |   10 ++
 bertos/kern/msg_test.c        |    6 -
 bertos/kern/preempt.c         |  234 +++++++++++++++++++++++++++-------------
 bertos/kern/preempt_test.c    |  210 ++++++++++++++++++++++++++++++++++++
 bertos/kern/proc.c            |   51 ++++-----
 bertos/kern/proc.h            |  113 +++++++++-----------
 bertos/kern/proc_p.h          |   28 ++++-
 bertos/kern/proc_test.c       |   24 +++--
 bertos/kern/sem_test.c        |    6 -
 bertos/kern/signal.c          |    8 ++-
 bertos/kern/signal_test.c     |    6 -
 examples/demo/cfg/cfg_proc.h  |    4 +
 examples/demo/cfg/cfg_timer.h |   68 ++++++++++++
 examples/demo/demo.c          |   13 ++-
 examples/demo/demo.mk         |   16 +++-
 test/run_tests.sh             |    8 ++-
 25 files changed, 670 insertions(+), 265 deletions(-)

diff --git a/bertos/cpu/attr.h b/bertos/cpu/attr.h
index 98ed7b2..e2fbf87 100644
--- a/bertos/cpu/attr.h
+++ b/bertos/cpu/attr.h
@@ -63,6 +63,7 @@
 #if CPU_I196
 
 	#define NOP                     nop_instruction()
+	#define PAUSE                   {NOP; MEMORY_BARRIER;}
 
 	#define CPU_REG_BITS            16
 	#define CPU_REGS_CNT            16
@@ -94,6 +95,8 @@
 
 	#ifdef __GNUC__
 		#define NOP         asm volatile ("nop")
+		/* This is a good thing to insert into busy-wait loops. */
+		#define PAUSE       asm volatile ("rep; nop" ::: "memory")
 		#define BREAKPOINT  asm volatile ("int3" ::)
 	#endif
 
@@ -116,6 +119,7 @@
 		#define CPU_BYTE_ORDER (__BIG_ENDIAN__ ? CPU_BIG_ENDIAN : CPU_LITTLE_ENDIAN)
 
 		#define NOP            __no_operation()
+		#define PAUSE          {NOP; MEMORY_BARRIER;}
 
 	#else /* GCC and compatibles */
 
@@ -128,6 +132,7 @@
   		#endif
 
 		#define NOP            asm volatile ("mov r0,r0" ::)
+		#define PAUSE          {NOP; MEMORY_BARRIER;}
 		#define BREAKPOINT  /* asm("bkpt 0") DOES NOT WORK */
 
 		#if CONFIG_FAST_MEM
@@ -172,6 +177,7 @@
 
 	#ifdef __GNUC__
 	    #define NOP         asm volatile ("nop" ::)
+		#define PAUSE          {NOP; MEMORY_BARRIER;}
 		#define BREAKPOINT  asm volatile ("twge 2,2" ::)
 	#endif
 
@@ -193,11 +199,13 @@
 	#define CPU_RAM_START       0x200
 
 	#define NOP                     asm(nop)
+	#define PAUSE                   {NOP; MEMORY_BARRIER;}
 	#define BREAKPOINT              asm(debug)
 
 #elif CPU_AVR
 
 	#define NOP                     asm volatile ("nop" ::)
+	#define PAUSE                   {NOP; MEMORY_BARRIER;}
 
 	#define CPU_REG_BITS            8
 	#define CPU_REGS_CNT           33 /* Includes SREG */
diff --git a/bertos/cpu/frame.h b/bertos/cpu/frame.h
index 2244b3b..159ebd7 100644
--- a/bertos/cpu/frame.h
+++ b/bertos/cpu/frame.h
@@ -73,7 +73,7 @@
 	#define CPU_CREATE_NEW_STACK(stack, entry, exit) \
 	do { \
 		/* Process entry point */ \
-		CPU_PUSH_CALL_FRAME(stack, entry); \
+		CPU_PUSH_CALL_FRAME(stack, proc_entry); \
 		/* LR (proc_exit) */ \
 		CPU_PUSH_CALL_FRAME(stack, exit); \
 		/* R11 */ \
@@ -267,7 +267,7 @@
 			size_t i; \
 			/* Initialize process stack frame */ \
 			CPU_PUSH_CALL_FRAME(stack, exit); \
-			CPU_PUSH_CALL_FRAME(stack, entry); \
+			CPU_PUSH_CALL_FRAME(stack, proc_entry); \
 			/* Push a clean set of CPU registers for asm_switch_context() */ \
 			for (i = 0; i < CPU_SAVED_REGS_CNT; i++) \
 				CPU_PUSH_WORD(stack, CPU_REG_INIT_VALUE(i)); \
diff --git a/bertos/cpu/irq.h b/bertos/cpu/irq.h
index 011524b..653cf60 100644
--- a/bertos/cpu/irq.h
+++ b/bertos/cpu/irq.h
@@ -222,14 +222,6 @@
 	#error No CPU_... defined.
 #endif
 
-#ifndef IRQ_ENTRY
-	#define IRQ_ENTRY() /* NOP */
-#endif
-
-#ifndef IRQ_EXIT
-	#define IRQ_EXIT() /* NOP */
-#endif
-
 #ifdef IRQ_RUNNING
 	/// Ensure callee is running within an interrupt
 	#define ASSERT_IRQ_CONTEXT()  ASSERT(IRQ_RUNNING())
diff --git a/bertos/cpu/types.h b/bertos/cpu/types.h
index daeec2e..02d7030 100644
--- a/bertos/cpu/types.h
+++ b/bertos/cpu/types.h
@@ -197,6 +197,9 @@
 
 /*\}*/
 
+#define INT_MAX	((int)((unsigned int)~0 >> 1))
+#define INT_MIN	(-INT_MAX - 1)
+
 /* Sanity checks for the above definitions */
 STATIC_ASSERT(sizeof(char) == SIZEOF_CHAR);
 STATIC_ASSERT(sizeof(short) == SIZEOF_SHORT);
diff --git a/bertos/drv/timer.c b/bertos/drv/timer.c
index 9e2a117..5c17367 100644
--- a/bertos/drv/timer.c
+++ b/bertos/drv/timer.c
@@ -52,6 +52,8 @@
 #include <cpu/irq.h>
 #include <cpu/power.h> // cpu_relax()
 
+#include <kern/irq.h> // IRQ_ENTRY() / IRQ_EXIT()
+
 /*
  * Include platform-specific binding code if we're hosted.
  * Try the CPU specific one for bare-metal environments.
@@ -182,6 +184,8 @@ Timer *timer_abort(Timer *timer)
 
 /**
  * Wait for the specified amount of timer ticks.
+ *
+ * \note Sleeping while preemption is disabled fallbacks to a busy wait sleep.
  */
 void timer_delayTicks(ticks_t delay)
 {
@@ -191,21 +195,23 @@ void timer_delayTicks(ticks_t delay)
 #if defined(CONFIG_KERN_SIGNALS) && CONFIG_KERN_SIGNALS
 	Timer t;
 
-	ASSERT(!sig_check(SIG_SINGLE));
-	timer_setSignal(&t, proc_current(), SIG_SINGLE);
-	timer_setDelay(&t, delay);
-	timer_add(&t);
-	sig_wait(SIG_SINGLE);
-
-#else /* !CONFIG_KERN_SIGNALS */
-
-	ticks_t start = timer_clock();
-
-	/* Busy wait */
-	while (timer_clock() - start < delay)
-		cpu_relax();
-
+	if (proc_preemptAllowed())
+	{
+		ASSERT(!sig_check(SIG_SINGLE));
+		timer_setSignal(&t, proc_current(), SIG_SINGLE);
+		timer_setDelay(&t, delay);
+		timer_add(&t);
+		sig_wait(SIG_SINGLE);
+	}
+	else
 #endif /* !CONFIG_KERN_SIGNALS */
+	{
+		ticks_t start = timer_clock();
+
+		/* Busy wait */
+		while (timer_clock() - start < delay)
+			cpu_relax();
+	}
 }
 
 
@@ -257,7 +263,6 @@ void timer_delayHp(hptime_t delay)
 }
 #endif /* CONFIG_TIMER_UDELAY */
 
-
 /**
  * Timer interrupt handler. Find soft timers expired and
  * trigger corresponding events.
@@ -277,12 +282,13 @@ DEFINE_TIMER_ISR
 	Timer *timer;
 #endif
 
+	IRQ_ENTRY();
 	/*
 	 * On systems sharing IRQ line and vector, this check is needed
 	 * to ensure that IRQ is generated by timer source.
 	 */
 	if (!timer_hw_triggered())
-		return;
+		goto out;
 
 	TIMER_STROBE_ON;
 
@@ -292,6 +298,9 @@ DEFINE_TIMER_ISR
 	/* Update the master ms counter */
 	++_clock;
 
+	/* Update the current task's quantum */
+	dec_proc_quantum();
+
 #if CONFIG_TIMER_EVENTS
 	/*
 	 * Check the first timer request in the list and process
@@ -316,6 +325,8 @@ DEFINE_TIMER_ISR
 #endif /* CONFIG_TIMER_EVENTS */
 
 	TIMER_STROBE_OFF;
+out:
+	IRQ_EXIT();
 }
 
 MOD_DEFINE(timer)
diff --git a/bertos/kern/coop.c b/bertos/kern/coop.c
index 9b73616..e47d76f 100644
--- a/bertos/kern/coop.c
+++ b/bertos/kern/coop.c
@@ -141,3 +141,14 @@ void proc_yield(void)
 	ATOMIC(SCHED_ENQUEUE(CurrentProcess));
 	proc_switch();
 }
+
+/**
+ * Entry point for all the processes.
+ */
+void proc_entry(void)
+{
+        void (*__entry)(void) = CurrentProcess->user_entry;
+
+	/* Initialization stuff goes here */
+        __entry();
+}
diff --git a/bertos/kern/idle.c b/bertos/kern/idle.c
index 810560a..8503309 100644
--- a/bertos/kern/idle.c
+++ b/bertos/kern/idle.c
@@ -38,11 +38,15 @@
 #include "idle.h"
 #include "proc.h"
 
+#include <cpu/power.h> // cpu_relax()
 #include <cfg/module.h>
+#include <cpu/types.h> // INT_MIN
 
+#include <kern/proc_p.h>
 
-// below there's a TRACE so we need a big stack
-PROC_DEFINE_STACK(idle_stack, KERN_MINSTACKSIZE * 2);
+struct Process *idle_proc;
+
+PROC_DEFINE_STACK(idle_stack, KERN_MINSTACKSIZE);
 
 /**
  * The idle process
@@ -60,14 +64,21 @@ static NORETURN void idle(void)
 {
 	for (;;)
 	{
-		TRACE;
-		//monitor_report();
-		proc_yield(); // FIXME: CPU_IDLE
+		PAUSE;
+		proc_switch();
 	}
 }
 
 void idle_init(void)
 {
-	struct Process *idle_proc = proc_new(idle, NULL, sizeof(idle_stack), idle_stack);
-	proc_setPri(idle_proc, (int)~0);
+	/*
+	 * Idle will be added to the ProcReadyList, but immediately removed
+	 * after the first cpu_relax() execution.
+	 *
+	 * XXX: it would be better to never add idle_proc to the ProcReadyList,
+	 * e.g., changing the prototype of proc_new() (or introducing a
+	 * proc_new_nostart()) to allow the creation of "sleeping" tasks.
+	 */
+	idle_proc = proc_new(idle, NULL, sizeof(idle_stack), idle_stack);
+	proc_setPri(idle_proc, INT_MIN);
 }
diff --git a/bertos/kern/idle.h b/bertos/kern/idle.h
index bad98fe..ca2fafc 100644
--- a/bertos/kern/idle.h
+++ b/bertos/kern/idle.h
@@ -37,5 +37,7 @@
 #ifndef KERN_IDLE_H
 #define KERN_IDLE_H
 
+extern struct Process *idle_proc;
+
 void idle_init(void);
 #endif /* KERN_IDLE_H */
diff --git a/bertos/kern/irq.c b/bertos/kern/irq.c
index 479ad67..8560814 100644
--- a/bertos/kern/irq.c
+++ b/bertos/kern/irq.c
@@ -48,7 +48,6 @@
 
 #include <unistd.h> // FIXME: move POSIX stuff to irq_posix.h
 
-
 MOD_DEFINE(irq)
 
 // FIXME
@@ -57,32 +56,7 @@ static void (*irq_handlers[100])(void);
 /* signal handler */
 void irq_entry(int signum)
 {
-#if CONFIG_KERN_PREEMPT
-	Process * const old_process = CurrentProcess;
-#endif
-
 	irq_handlers[signum]();
-
-#if CONFIG_KERN_PREEMPT
-	ASSERT2(CurrentProcess, "no idle proc?");
-
-	if (old_process != CurrentProcess)
-	{
-		IRQ_DISABLE;
-
-		TRACEMSG("switching from %p:%s to %p:%s",
-			old_process, old_process ? old_process->monitor.name : "---",
-			CurrentProcess, proc_currentName());
-
-		if (old_process)
-			swapcontext(&old_process->context, &CurrentProcess->context);
-		else
-			setcontext(&CurrentProcess->context);
-
-		IRQ_ENABLE;
-	}
-	TRACEMSG("resuming %p:%s", CurrentProcess, CurrentProcess->monitor.name);
-#endif // CONFIG_KERN_PREEMPT
 }
 
 void irq_register(int irq, void (*callback)(void))
diff --git a/bertos/kern/irq.h b/bertos/kern/irq.h
index eb63702..f838be7 100644
--- a/bertos/kern/irq.h
+++ b/bertos/kern/irq.h
@@ -37,6 +37,16 @@
 #ifndef KERN_IRQ_H
 #define KERN_IRQ_H
 
+#include "proc_p.h" // proc_preempt()
+
+#ifndef IRQ_ENTRY
+	#define IRQ_ENTRY() /* NOP */
+#endif
+
+#ifndef IRQ_EXIT
+	#define IRQ_EXIT() proc_preempt()
+#endif
+
 void irq_entry(int irq);
 void irq_register(int irq, void (*handler)(void));
 void irq_init(void);
diff --git a/bertos/kern/msg_test.c b/bertos/kern/msg_test.c
index 1e4ed21..bcb20ea 100644
--- a/bertos/kern/msg_test.c
+++ b/bertos/kern/msg_test.c
@@ -271,12 +271,6 @@ int msg_testSetup(void)
 {
 	kdbg_init();
 
-	#if CONFIG_KERN_PREEMPT
-		kprintf("Init Interrupt (preempt mode)..");
-		irq_init();
-		kprintf("Done.\n");
-	#endif
-
 	kprintf("Init Timer..");
 	timer_init();
 	kprintf("Done.\n");
diff --git a/bertos/kern/preempt.c b/bertos/kern/preempt.c
index 48a1278..40ccae7 100644
--- a/bertos/kern/preempt.c
+++ b/bertos/kern/preempt.c
@@ -27,21 +27,59 @@
  * the GNU General Public License.
  *
  * Copyright 2008 Bernie Innocenti <bernie at codewiz.org>
+ * Copyright 2009 Andrea Righi <arighi at develer.com>
  * -->
  *
  * \brief Simple preemptive multitasking scheduler.
  *
- * All voluntary and preemptive context switching happens on exit from
- * a common interrupt (signal) dispatcher.  Preemption on quantum timeout
- * is regulated by a soft-timer.  Other kinds of preemption could happen
- * if an interrupt sends a signal to a higher priority process (but this
- * is still unimplemented).
+ * Preemption is explicitly regulated inside the timer interrupt service
+ * routine. Each task obtains a time quantum when scheduled on the CPU and its
+ * quantum is decremented at each clock tick. The frequency of the timer
+ * determines the system tick granularity and CONFIG_KERN_QUANTUM the time
+ * sharing interval.
  *
- * In the POSIX implementaiton, context switching is done by the portable
- * SVR4 swapcontext() facility.
+ * When the quantum expires the handler proc_preempt() checks if the preemption
+ * is enabled and in this case proc_schedule() is called, that possibly
+ * replaces the current running thread with a different one.
+ *
+ * The preemption can be disabled or enabled via proc_forbid() and
+ * proc_permit() primitives. This is implemented using a global atomic counter.
+ * When the counter is greater than 0 the task cannot be preempted; only when
+ * the counter reaches 0 the task can be preempted again.
+ *
+ * Preemption-disabled sections may be nested. The preemption will be
+ * re-enabled when the outermost preemption-disabled section completes.
+ *
+ * The voluntary preemption still happens via proc_switch() or proc_yield().
+ * The first one assumes the current process has been already added to a
+ * private wait queue (e.g., on a semaphore or a signal), while the second one
+ * takes care of adding the process into the ready queue.
+ *
+ * Context switch is done by CPU-dependent support routines. In case of a
+ * voluntary preemption the context switch routine must take care of
+ * saving/restoring the callee-save registers (the voluntary-preemption case is
+ * actually a function call). The kernel-preemption always happens on a
+ * signal/interrupt context and so it must take care of saving all registers
+ * (minus the caller-save registers) in a common interrupt entry point, then
+ * switch to user-context and call the voluntary context switch on a interrupt
+ * exit point. On resume from the switch, the interrupt exit point must move
+ * back in interrupt mode, resume the caller-save registers (saved in the entry
+ * point) and return from the interrupt context.
+ *
+ * \note Thread priority defines the order in the \p ProcReadyList and the
+ * capability to deschedule a running process. A low-priority thread can't
+ * preempt a high-priority thread.
+ *
+ * A high-priority process can preempt a low-priority process immediately (it
+ * will be descheduled and replaced in the interrupt exit point). Processes
+ * running at the same priority can be descheduled when they expire the time
+ * quantum.
+ *
+ * \note Sleeping while preemption is disabled fallbacks to a busy-wait sleep.
+ * Voluntary preemption when preemption is disabled raises a kernel bug.
  *
- * \version $Id$
  * \author Bernie Innocenti <bernie at codewiz.org>
+ * \author Andrea Righi <arighi at develer.com>
  */
 
 #include "cfg/cfg_proc.h"
@@ -50,115 +88,159 @@
 
 #include "proc_p.h"
 #include "proc.h"
-#include "idle.h"
 
 #include <kern/irq.h>
 #include <kern/monitor.h>
+#include <kern/idle.h> // idle_proc
 #include <cpu/frame.h> // CPU_IDLE
 #include <cpu/irq.h>   // IRQ_DISABLE()...
-#include <drv/timer.h>
+#include <cfg/log.h>
 #include <cfg/module.h>
 #include <cfg/depend.h>    // CONFIG_DEPEND()
 
 // Check config dependencies
-CONFIG_DEPEND(CONFIG_KERN_PREEMPT, CONFIG_KERN && CONFIG_TIMER_EVENTS && CONFIG_KERN_IRQ);
+CONFIG_DEPEND(CONFIG_KERN_PREEMPT, CONFIG_KERN);
 
 MOD_DEFINE(preempt)
 
-/// Global preemption disabling nesting counter
-cpu_atomic_t _preempt_forbid_cnt;
+/**
+ * CPU dependent context switching routines.
+ *
+ * Saving and restoring the context on the stack is done by a CPU-dependent
+ * support routine which usually needs to be written in assembly.
+ */
+EXTERN_C void asm_switch_context(cpu_stack_t **new_sp, cpu_stack_t **save_sp);
 
-static Timer preempt_timer;
+/* Global preemption nesting counter */
+cpu_atomic_t preempt_count;
 
+/*
+ * The time sharing interval: when a process is scheduled on a CPU it gets an
+ * amount of CONFIG_KERN_QUANTUM clock ticks. When these ticks expires and
+ * preemption is enabled a new process is selected to run.
+ */
+int _proc_quantum;
 
-void proc_schedule(void)
+/**
+ * Call the scheduler and eventually replace the current running process.
+ */
+static void proc_schedule(void)
 {
-	IRQ_DISABLE;
+	Process *old_process = CurrentProcess;
+	cpu_flags_t flags;
 
-	ASSERT(proc_preemptAllowed());
+	IRQ_SAVE_DISABLE(flags);
+	/* Poll on the ready queue for the first ready process */
 	LIST_ASSERT_VALID(&ProcReadyList);
-	CurrentProcess = (struct Process *)list_remHead(&ProcReadyList);
-	ASSERT2(CurrentProcess, "no idle proc?");
-
-	IRQ_ENABLE;
+	CurrentProcess = (Process *)list_remHead(&ProcReadyList);
+	if (UNLIKELY(!CurrentProcess))
+		CurrentProcess = idle_proc;
+	_proc_quantum = CONFIG_KERN_QUANTUM;
+	/*
+	 * Optimization: don't switch contexts when the active process has not
+	 * changed.
+	 */
+	if (LIKELY(old_process != CurrentProcess))
+	{
+		cpu_stack_t *dummy;
+
+		/*
+		 * Save context of old process and switch to new process. If
+		 * there is no old process, we save the old stack pointer into
+		 * a dummy variable that we ignore. In fact, this happens only
+		 * when the old process has just exited.
+		 *
+		 * \todo Instead of physically clearing the process at exit
+		 * time, a zombie list should be created.
+		 */
+		asm_switch_context(&CurrentProcess->stack,
+				old_process ? &old_process->stack : &dummy);
+	}
+	IRQ_RESTORE(flags);
 
-	TRACEMSG("launching %p:%s", CurrentProcess, proc_currentName());
+	/* This RET resumes the execution on the new process */
+	LOG_INFO("resuming %p:%s\n", CurrentProcess, proc_currentName());
 }
 
-void proc_preempt(UNUSED_ARG(void *, param))
+/**
+ * Check if we need to schedule another task
+ */
+INLINE int need_preempt(void)
 {
-	if (proc_preemptAllowed())
-	{
-		IRQ_DISABLE;
-
-		#if CONFIG_KERN_PRI
-			Process *rival = (Process *)LIST_HEAD(&ProcReadyList);
-			if (rival && rival->link.pri >= CurrentProcess->link.pri)
-			{
-		#endif
-
-		TRACEMSG("preempting %p:%s", CurrentProcess, proc_currentName());
+	if (!proc_preemptAllowed())
+		return 0;
+	return _proc_quantum ? prio_next() > prio_curr() :
+			prio_next() >= prio_curr();
+}
 
-// FIXME: this still breaks havoc, probably because of some reentrancy issue
-#if 0
+/**
+ * Preempt the current task.
+ */
+void proc_preempt(void)
+{
+	ASSERT_IRQ_CONTEXT();
+	IRQ_ASSERT_DISABLED();
+
+	if (!need_preempt())
+		return;
+	/* Perform the kernel preemption */
+	LOG_INFO("preempting %p:%s\n", CurrentProcess, proc_currentName());
+	/* We are inside a IRQ context, so ATOMIC is not needed here */
+	if (CurrentProcess != idle_proc)
 		SCHED_ENQUEUE(CurrentProcess);
-		proc_schedule();
-#endif
-		#if CONFIG_KERN_PRI
-			}
-		#endif
-
-		IRQ_ENABLE;
-	}
-
-	timer_setDelay(&preempt_timer, CONFIG_KERN_QUANTUM);
-	timer_add(&preempt_timer);
+	proc_schedule();
 }
 
+/**
+ * Give the control of the CPU to another process.
+ *
+ * \note Assume the current process has been already added to a wait queue.
+ *
+ * \warning This should be considered an internal kernel function, even if it
+ * is allowed, usage from application code is strongly discouraged.
+ */
 void proc_switch(void)
 {
-	ATOMIC(LIST_ASSERT_VALID(&ProcReadyList));
-	TRACEMSG("%p:%s", CurrentProcess, proc_currentName());
-	ATOMIC(LIST_ASSERT_VALID(&ProcReadyList));
-
-	/* Sleeping with IRQs disabled or preemption forbidden is illegal */
-	IRQ_ASSERT_ENABLED();
 	ASSERT(proc_preemptAllowed());
-
-	// Will invoke proc_switch() in interrupt context
-	kill(0, SIGUSR1);
+	proc_schedule();
 }
 
+/**
+ * Voluntarily release the CPU.
+ */
 void proc_yield(void)
 {
-	TRACEMSG("%p:%s", CurrentProcess, proc_currentName());
-
-	IRQ_DISABLE;
-	SCHED_ENQUEUE(CurrentProcess);
-	IRQ_ENABLE;
-
-	proc_switch();
+	/*
+	 * Voluntary preemption while preemption is disabled is considered
+	 * legal, although not very useful in practice.
+	 *
+	 * At least, print a warning if it happens.
+	 */
+	ASSERT(proc_preemptAllowed());
+	ATOMIC(SCHED_ENQUEUE(CurrentProcess));
+	proc_schedule();
 }
 
-void proc_entry(void (*user_entry)(void))
+/**
+ * Entry point for all the processes.
+ */
+void proc_entry(void)
 {
-	user_entry();
-	proc_exit();
+	void (*__entry)(void) = CurrentProcess->user_entry;
+
+	LOG_INFO("new process starting at %p", __entry);
+	/*
+	 * Return from a context switch assumes interrupts are disabled, so
+	 * we need to explicitly re-enable them as soon as possible.
+	 */
+	IRQ_ENABLE;
+	/* Call the actual application's entry point */
+	__entry();
 }
 
 void preempt_init(void)
 {
-	MOD_CHECK(irq);
-	MOD_CHECK(timer);
-
-	irq_register(SIGUSR1, proc_schedule);
-
-	timer_setSoftint(&preempt_timer, proc_preempt, NULL);
-	timer_setDelay(&preempt_timer, CONFIG_KERN_QUANTUM);
-	timer_add(&preempt_timer);
-
 	idle_init();
-
 	MOD_INIT(preempt);
 }
 
diff --git a/bertos/kern/preempt_test.c b/bertos/kern/preempt_test.c
new file mode 100644
index 0000000..a91a07f
--- /dev/null
+++ b/bertos/kern/preempt_test.c
@@ -0,0 +1,210 @@
+/**
+ * \file
+ * <!--
+ * This file is part of BeRTOS.
+ *
+ * Bertos is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction.  Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License.  This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2009 Develer S.r.l. (http://www.develer.com/)
+ * -->
+ *
+ *
+ * \brief Test kernel preemption.
+ *
+ * This testcase spawns TASKS parallel threads that runs for TIME seconds. They
+ * continuously spin updating a global counter (one counter for each thread).
+ *
+ * At exit each thread checks if the others have been che chance to update
+ * their own counter. If not, it means the preemption didn't occur and the
+ * testcase returns an error message.
+ *
+ * Otherwise, if all the threads have been able to update their own counter it
+ * means preemption successfully occurs, since there is no active sleep inside
+ * each thread's implementation.
+ *
+ * \author Andrea Righi <arighi at develer.com>
+ *
+ * $test$: cp bertos/cfg/cfg_proc.h $cfgdir/
+ * $test$: echo  "#undef CONFIG_KERN" >> $cfgdir/cfg_proc.h
+ * $test$: echo "#define CONFIG_KERN 1" >> $cfgdir/cfg_proc.h
+ * $test$: echo  "#undef CONFIG_KERN_PRI" >> $cfgdir/cfg_proc.h
+ * $test$: echo "#define CONFIG_KERN_PRI 1" >> $cfgdir/cfg_proc.h
+ * $test$: echo  "#undef CONFIG_KERN_PREEMPT" >> $cfgdir/cfg_proc.h
+ * $test$: echo "#define CONFIG_KERN_PREEMPT 1" >> $cfgdir/cfg_proc.h
+ * $test$: cp bertos/cfg/cfg_monitor.h $cfgdir/
+ * $test$: sed -i "s/CONFIG_KERN_MONITOR 0/CONFIG_KERN_MONITOR 1/" $cfgdir/cfg_monitor.h
+ * $test$: cp bertos/cfg/cfg_signal.h $cfgdir/
+ * $test$: echo  "#undef CONFIG_KERN_SIGNALS" >> $cfgdir/cfg_signal.h
+ * $test$: echo "#define CONFIG_KERN_SIGNALS 1" >> $cfgdir/cfg_signal.h
+ *
+ */
+
+#include <stdio.h> // sprintf
+#include <string.h> // memset
+
+#include <kern/proc.h>
+#include <kern/irq.h>
+#include <kern/monitor.h>
+
+#include <drv/timer.h>
+#include <cfg/test.h>
+
+enum
+{
+	TEST_OK = 1,
+	TEST_FAIL = 2,
+};
+
+/* Number of tasks to spawn */
+#define TASKS	8
+/* Time to run each thread (in seconds) */
+#define TIME	10
+
+static char name[TASKS][32];
+
+static unsigned int counter[TASKS];
+static unsigned int done[TASKS];
+
+static cpu_atomic_t barrier[TASKS];
+static cpu_atomic_t main_barrier;
+
+// Define process stacks for test.
+static cpu_stack_t worker_stack[TASKS][KERN_MINSTACKSIZE];
+
+static void worker(void)
+{
+	long pid = (long)proc_currentUserData();
+	unsigned int *my_count = &counter[pid - 1];
+	ticks_t start, stop;
+	int i;
+
+	barrier[pid - 1] = 1;
+	/* Synchronize on the main barrier */
+	while (!main_barrier)
+		proc_yield();
+	kprintf("> Process[%ld] running\n", pid);
+	start = timer_clock();
+	stop  = ms_to_ticks(TIME * 1000);
+	while (timer_clock() - start < stop)
+	{
+		(*my_count)++;
+		/* be sure to wrap to a value different than 0 */
+		if (UNLIKELY(*my_count == (unsigned int)~0))
+			*my_count = 1;
+	}
+	kprintf("> Process[%ld] completed: (counter = %d)\n", pid, *my_count);
+	for (i = 0; i < TASKS; i++)
+		if (!counter[i])
+		{
+			done[pid - 1] = TEST_FAIL;
+			return;
+		}
+	done[pid - 1] = TEST_OK;
+}
+
+/**
+ * Process scheduling test
+ */
+int preempt_testRun(void)
+{
+	unsigned long score = 0;
+	long i;
+
+	// Clear shared data (this is needed when this testcase is embedded in
+	// the demo application).
+	memset(counter, 0, sizeof(counter));
+	memset(done, 0, sizeof(done));
+	memset(barrier, 0, sizeof(barrier));
+	main_barrier = 0;
+
+	// Init the test processes
+	kputs("Run Preemption test..\n");
+	for (i = 0; i < TASKS; i++)
+	{
+		sprintf(&name[i][0], "worker_%ld", i + 1);
+		proc_new_with_name(name[i], worker, (iptr_t)(i + 1),
+				KERN_MINSTACKSIZE, &worker_stack[i][0]);
+	}
+	kputs("> Main: Processes created\n");
+	/* Synchronize on start */
+	while (1)
+	{
+		for (i = 0; i < TASKS; i++)
+			if (!barrier[i])
+				break;
+		if (i == TASKS)
+			break;
+		proc_yield();
+	}
+	/* Now all threads have been created, start them all */
+	main_barrier = 1;
+	MEMORY_BARRIER;
+	kputs("> Main: Processes started\n");
+	while (1)
+	{
+		for (i = 0; i < TASKS; i++)
+		{
+			if (!done[i])
+				break;
+			else if (done[i] == TEST_FAIL)
+			{
+				kputs("> Main: process test finished..fail!\n");
+				return -1;
+			}
+		}
+		if (i == TASKS)
+			break;
+		monitor_report();
+		timer_delay(1000);
+	}
+	for (i = 0; i < TASKS; i++)
+		score += counter[i];
+	kputs("> Main: process test finished..ok!\n");
+	kprintf("> Score: %lu\n", score);
+
+	return 0;
+}
+
+int preempt_testSetup(void)
+{
+	kdbg_init();
+
+	kprintf("Init Timer..");
+	timer_init();
+	kprintf("Done.\n");
+
+	kprintf("Init Process..");
+	proc_init();
+	kprintf("Done.\n");
+
+	return 0;
+}
+
+int preempt_testTearDown(void)
+{
+	kputs("TearDown Process test.\n");
+	return 0;
+}
+
+TEST_MAIN(preempt);
diff --git a/bertos/kern/proc.c b/bertos/kern/proc.c
index 57cda38..9634432 100644
--- a/bertos/kern/proc.c
+++ b/bertos/kern/proc.c
@@ -85,10 +85,10 @@ REGISTER Process *CurrentProcess;
  *
  * Access to this list must be protected by PROC_ATOMIC().
  */
-List StackFreeList;
+static List StackFreeList;
 
 #define NPROC 10
-cpu_stack_t proc_stacks[NPROC][(64 * 1024) / sizeof(cpu_stack_t)];
+static cpu_stack_t proc_stacks[NPROC][KERN_MINSTACKSIZE / sizeof(cpu_stack_t)];
 #endif
 
 /** The main process (the one that executes main()). */
@@ -112,7 +112,6 @@ static void proc_init_struct(Process *proc)
 #if CONFIG_KERN_PRI
 	proc->link.pri = 0;
 #endif
-
 }
 
 MOD_DEFINE(proc);
@@ -157,7 +156,7 @@ void proc_init(void)
  * \endcode
  * is a more convenient way to create a process, as you don't have to specify
  * the name.
- * 
+ *
  * \return Process structure of new created process
  *         if successful, NULL otherwise.
  */
@@ -242,20 +241,8 @@ struct Process *proc_new_with_name(UNUSED_ARG(const char *, name), void (*entry)
 		proc->flags |= PF_FREESTACK;
 	#endif
 #endif
-
-	#if CONFIG_KERN_PREEMPT
-
-		getcontext(&proc->context);
-		proc->context.uc_stack.ss_sp = proc->stack;
-		proc->context.uc_stack.ss_size = stack_size - 1;
-		proc->context.uc_link = NULL;
-		makecontext(&proc->context, (void (*)(void))proc_entry, 1, entry);
-
-	#else // !CONFIG_KERN_PREEMPT
-	
-		CPU_CREATE_NEW_STACK(proc->stack, entry, proc_exit);
-		
-	#endif // CONFIG_KERN_PREEMPT
+	proc->user_entry = entry;
+	CPU_CREATE_NEW_STACK(proc->stack, proc_entry, proc_exit);
 
 	#if CONFIG_KERN_MONITOR
 		monitor_add(proc, name);
@@ -321,17 +308,13 @@ void proc_rename(struct Process *proc, const char *name)
  */
 void proc_setPri(struct Process *proc, int pri)
 {
-		if (proc->link.pri == pri)
-			return;
+	if (proc->link.pri == pri)
+		return;
 
-		proc->link.pri = pri;
+	proc->link.pri = pri;
 
-		if (proc != CurrentProcess)
-		{
-				proc_forbid();
-				ATOMIC(sched_reenqueue(proc));
-				proc_permit();
-		}
+	if (proc != CurrentProcess)
+		ATOMIC(sched_reenqueue(proc));
 }
 #endif // CONFIG_KERN_PRI
 
@@ -342,6 +325,16 @@ void proc_exit(void)
 {
 	LOG_INFO("%p:%s", CurrentProcess, proc_currentName());
 
+	/*
+	 * Never preempt on exit (we don't want to preempt when the stack has
+	 * been deallocated).
+	 *
+	 * NOTE: the preemption will be automatically re-enabled in the context
+	 * of the next task, so there's no need to add a proc_permit() at the
+	 * end of this function. Actually, the preemption for this task will be
+	 * never re-enabled.
+	 */
+	proc_forbid();
 #if CONFIG_KERN_MONITOR
 	monitor_remove(CurrentProcess);
 #endif
@@ -349,7 +342,7 @@ void proc_exit(void)
 #if CONFIG_KERN_HEAP
 	/*
 	 * The following code is BROKEN.
-	 * We are freeing our own stack before entering proc_schedule()
+	 * We are freeing our own stack before entering proc_switch()
 	 * BAJO: A correct fix would be to rearrange the scheduler with
 	 *  an additional parameter which frees the old stack/process
 	 *  after a context switch.
@@ -371,8 +364,10 @@ void proc_exit(void)
 #endif /* ARCH_EMUL */
 
 	CurrentProcess = NULL;
+	proc_permit();
 	proc_switch();
 	/* not reached */
+	ASSERT(0);
 }
 
 
diff --git a/bertos/kern/proc.h b/bertos/kern/proc.h
index db26939..b3d1d20 100644
--- a/bertos/kern/proc.h
+++ b/bertos/kern/proc.h
@@ -90,9 +90,8 @@ typedef struct Process
 	size_t       stack_size;  /**< Size of process stack */
 #endif
 
-#if CONFIG_KERN_PREEMPT
-	ucontext_t   context;
-#endif
+	/* The actual process entry point */
+	void (*user_entry)(void);
 
 #if CONFIG_KERN_MONITOR
 	struct ProcMonitor
@@ -104,6 +103,8 @@ typedef struct Process
 
 } Process;
 
+extern Process *CurrentProcess;
+
 /**
  * Initialize the process subsystem (kernel).
  * It must be called before using any process related function.
@@ -170,6 +171,12 @@ int proc_testSetup(void);
 int proc_testRun(void);
 int proc_testTearDown(void);
 
+#if CONFIG_KERN_PREEMPT
+int preempt_testSetup(void);
+int preempt_testRun(void);
+int preempt_testTearDown(void);
+#endif
+
 /**
  * Return the context structure of the currently running process.
  *
@@ -191,6 +198,9 @@ INLINE struct Process *proc_current(void)
 	}
 #endif
 
+#if CONFIG_KERN_PREEMPT
+extern cpu_atomic_t preempt_count;
+
 /**
  * Disable preemptive task switching.
  *
@@ -201,9 +211,6 @@ INLINE struct Process *proc_current(void)
  * \note Calling functions that could sleep while task switching is disabled
  * is dangerous and unsupported.
  *
- * \note calling proc_forbid() from within an interrupt is illegal and
- * meaningless.
- *
  * \note proc_permit() expands inline to 1-2 asm instructions, so it's a
  * very efficient locking primitive in simple but performance-critical
  * situations.  In all other cases, semaphores offer a more flexible and
@@ -213,39 +220,12 @@ INLINE struct Process *proc_current(void)
  */
 INLINE void proc_forbid(void)
 {
-	#if CONFIG_KERN_PREEMPT
-		extern cpu_atomic_t _preempt_forbid_cnt;
-		/*
-		 * We don't need to protect the counter against other processes.
-		 * The reason why is a bit subtle.
-		 *
-		 * If a process gets here, preempt_forbid_cnt can be either 0,
-		 * or != 0.  In the latter case, preemption is already disabled
-		 * and no concurrency issues can occur.
-		 *
-		 * In the former case, we could be preempted just after reading the
-		 * value 0 from memory, and a concurrent process might, in fact,
-		 * bump the value of preempt_forbid_cnt under our nose!
-		 *
-		 * BUT: if this ever happens, then we won't get another chance to
-		 * run until the other process calls proc_permit() to re-enable
-		 * preemption.  At this point, the value of preempt_forbid_cnt
-		 * must be back to 0, and thus what we had originally read from
-		 * memory happens to be valid.
-		 *
-		 * No matter how hard you think about it, and how complicated you
-		 * make your scenario, the above holds true as long as
-		 * "preempt_forbid_cnt != 0" means that no task switching is
-		 * possible.
-		 */
-		++_preempt_forbid_cnt;
-
-		/*
-		 * Make sure _preempt_forbid_cnt is flushed to memory so the
-		 * preemption softirq will see the correct value from now on.
-		 */
-		MEMORY_BARRIER;
-	#endif
+	++preempt_count;
+	/*
+	 * Make sure preempt_count is flushed to memory so the preemption
+	 * softirq will see the correct value from now on.
+	 */
+	MEMORY_BARRIER;
 }
 
 /**
@@ -255,41 +235,44 @@ INLINE void proc_forbid(void)
  */
 INLINE void proc_permit(void)
 {
-	#if CONFIG_KERN_PREEMPT
-
-		/*
-		 * This is to ensure any global state changed by the process gets
-		 * flushed to memory before task switching is re-enabled.
-		 */
-		MEMORY_BARRIER;
-		extern cpu_atomic_t _preempt_forbid_cnt;
-		/* No need to protect against interrupts here. */
-		ASSERT(_preempt_forbid_cnt != 0);
-		--_preempt_forbid_cnt;
-
-		/*
-		 * This ensures _preempt_forbid_cnt is flushed to memory immediately
-		 * so the preemption interrupt sees the correct value.
-		 */
-		MEMORY_BARRIER;
-
-	#endif
+	/*
+	 * This is to ensure any global state changed by the process gets
+	 * flushed to memory before task switching is re-enabled.
+	 */
+	MEMORY_BARRIER;
+	/* No need to protect against interrupts here. */
+	ASSERT(preempt_count > 0);
+	--preempt_count;
+	/*
+	 * This ensures preempt_count is flushed to memory immediately so the
+	 * preemption interrupt sees the correct value.
+	 */
+	MEMORY_BARRIER;
 }
 
 /**
  * \return true if preemptive task switching is allowed.
- * \note This accessor is needed because _preempt_forbid_cnt
+ * \note This accessor is needed because preempt_count
  *       must be absoultely private.
  */
 INLINE bool proc_preemptAllowed(void)
 {
-	#if CONFIG_KERN_PREEMPT
-		extern cpu_atomic_t _preempt_forbid_cnt;
-		return (_preempt_forbid_cnt == 0);
-	#else
-		return true;
-	#endif
+	return (preempt_count == 0);
+}
+#else /* CONFIG_KERN_PREEMPT */
+INLINE void proc_forbid(void)
+{
+}
+
+INLINE void proc_permit(void)
+{
+}
+
+INLINE bool proc_preemptAllowed(void)
+{
+	return true;
 }
+#endif /* CONFIG_KERN_PREEMPT */
 
 /** Deprecated, use the proc_preemptAllowed() macro. */
 #define proc_allowed() proc_preemptAllowed()
diff --git a/bertos/kern/proc_p.h b/bertos/kern/proc_p.h
index a5c7dbe..0b0653e 100644
--- a/bertos/kern/proc_p.h
+++ b/bertos/kern/proc_p.h
@@ -48,11 +48,8 @@
 #include <cpu/types.h>        /* for cpu_stack_t */
 #include <cpu/irq.h>          // IRQ_ASSERT_DISABLED()
 
-#if CONFIG_KERN_PREEMPT
-	#include <ucontext.h> // XXX
-#endif
-
 #include <kern/proc.h>   // struct Process
+#include <kern/idle.h>        // idle_proc
 
 
 /**
@@ -74,8 +71,15 @@ extern REGISTER Process	*CurrentProcess;
 extern REGISTER List     ProcReadyList;
 
 #if CONFIG_KERN_PRI
+	#define prio_next()	(LIST_EMPTY(&ProcReadyList) ? idle_proc->link.pri : \
+					((PriNode *)LIST_HEAD(&ProcReadyList))->pri)
+	#define prio_curr()	(CurrentProcess->link.pri)
+
 	#define SCHED_ENQUEUE_INTERNAL(proc) LIST_ENQUEUE(&ProcReadyList, &(proc)->link)
 #else
+	#define prio_next()	0
+	#define prio_curr()	0
+
 	#define SCHED_ENQUEUE_INTERNAL(proc) ADDTAIL(&ProcReadyList, &(proc)->link)
 #endif
 
@@ -132,11 +136,23 @@ INLINE void sched_reenqueue(struct Process *proc)
 
 /// Schedule another process *without* adding the current one to the ready list.
 void proc_switch(void);
+void proc_entry(void);
 
 #if CONFIG_KERN_PREEMPT
-void proc_entry(void (*user_entry)(void));
 void preempt_init(void);
-#endif
+void proc_preempt(void);
+
+extern int _proc_quantum;
+
+INLINE void dec_proc_quantum(void)
+{
+	if (_proc_quantum > 0)
+		_proc_quantum--;
+}
+#else /* !CONFIG_KERN_PREEMPT */
+#define proc_preempt()
+#define dec_proc_quantum()
+#endif /* CONFIG_KERN_PREEMPT */
 
 #if CONFIG_KERN_MONITOR
 	/** Initialize the monitor */
diff --git a/bertos/kern/proc_test.c b/bertos/kern/proc_test.c
index ad30f71..7eeb8a9 100644
--- a/bertos/kern/proc_test.c
+++ b/bertos/kern/proc_test.c
@@ -158,6 +158,18 @@ PROC_PRI_TEST(2)
 int proc_testRun(void)
 {
 	int ret_value = 0;
+
+	// Init all variables (this is needed when this testcase is embedded in
+	// the demo application)
+	t1_count = 0;
+	t2_count = 0;
+	t3_count = 0;
+	t4_count = 0;
+	t5_count = 0;
+	t6_count = 0;
+	t7_count = 0;
+	t8_count = 0;
+
 	kprintf("Run Process test..\n");
 
 	//Init the process tests
@@ -197,11 +209,13 @@ int proc_testRun(void)
 	}
 
 #if CONFIG_KERN_SIGNALS & CONFIG_KERN_PRI
+	struct Process *curr = proc_current();
+	int orig_pri = curr->link.pri;
+
 	// test process priority
 	// main process must have the higher priority to check signals received
 	proc_setPri(proc_current(), 10);
 
-	struct Process *curr = proc_current();
 	// the order in which the processes are created is important!
 	PROC_PRI_TEST_INIT(0, curr);
 	PROC_PRI_TEST_INIT(1, curr);
@@ -229,10 +243,12 @@ int proc_testRun(void)
 	{
 		kputs("Priority test successfull.\n");
 	}
+	proc_setPri(proc_current(), orig_pri);
 
 	return ret_value;
 
 priority_fail:
+	proc_setPri(proc_current(), orig_pri);
 	kputs("Priority test failed.\n");
 	return -1;
 
@@ -246,12 +262,6 @@ int proc_testSetup(void)
 {
 	kdbg_init();
 
-	#if CONFIG_KERN_PREEMPT
-		kprintf("Init Interrupt (preempt mode)..");
-		irq_init();
-		kprintf("Done.\n");
-	#endif
-
 	kprintf("Init Timer..");
 	timer_init();
 	kprintf("Done.\n");
diff --git a/bertos/kern/sem_test.c b/bertos/kern/sem_test.c
index cf927d0..555f755 100644
--- a/bertos/kern/sem_test.c
+++ b/bertos/kern/sem_test.c
@@ -184,12 +184,6 @@ int sem_testSetup(void)
 	sem_init(&sem);
 	kprintf("Done.\n");
 
-	#if CONFIG_KERN_PREEMPT
-		kprintf("Init Interrupt (preempt mode)..");
-		irq_init();
-		kprintf("Done.\n");
-	#endif
-
 	kprintf("Init Timer..");
 	timer_init();
 	kprintf("Done.\n");
diff --git a/bertos/kern/signal.c b/bertos/kern/signal.c
index dbe1261..508c3b0 100644
--- a/bertos/kern/signal.c
+++ b/bertos/kern/signal.c
@@ -249,7 +249,13 @@ void sig_signal(Process *proc, sigmask_t sigs)
 	/* Check if process needs to be awoken */
 	if (proc->sig_recv & proc->sig_wait)
 	{
-		/* Wake up process and enqueue in ready list */
+		/*
+		 * Wake up process and enqueue in ready list.
+		 *
+		 * XXX: consider to move this process to the head of the ready
+		 * list, so that it will be chosen at the next scheduling
+		 * point.
+		 */
 		proc->sig_wait = 0;
 		SCHED_ENQUEUE(proc);
 	}
diff --git a/bertos/kern/signal_test.c b/bertos/kern/signal_test.c
index d489f62..484c033 100644
--- a/bertos/kern/signal_test.c
+++ b/bertos/kern/signal_test.c
@@ -166,12 +166,6 @@ int signal_testSetup(void)
 {
 	kdbg_init();
 
-	#if CONFIG_KERN_PREEMPT
-		kprintf("Init Interrupt (preempt mode)..");
-		irq_init();
-		kprintf("Done.\n");
-	#endif
-
 	kprintf("Init Timer..");
 	timer_init();
 	kprintf("Done.\n");
diff --git a/examples/demo/cfg/cfg_proc.h b/examples/demo/cfg/cfg_proc.h
index 745634d..e5894da 100644
--- a/examples/demo/cfg/cfg_proc.h
+++ b/examples/demo/cfg/cfg_proc.h
@@ -64,7 +64,11 @@
  *
  * $WIZ$ type = "boolean"
  */
+#ifdef PREEMPT
+#define CONFIG_KERN_PREEMPT 1
+#else
 #define CONFIG_KERN_PREEMPT 0
+#endif
 
 /**
  * Priority-based scheduling policy.
diff --git a/examples/demo/cfg/cfg_timer.h b/examples/demo/cfg/cfg_timer.h
new file mode 100644
index 0000000..1cff78b
--- /dev/null
+++ b/examples/demo/cfg/cfg_timer.h
@@ -0,0 +1,68 @@
+/**
+ * \file
+ * <!--
+ * This file is part of BeRTOS.
+ *
+ * Bertos is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction.  Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License.  This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2008 Develer S.r.l. (http://www.develer.com/)
+ * All Rights Reserved.
+ * -->
+ *
+ * \brief Configuration file for timer module.
+ *
+ * \version $Id$
+ *
+ * \author Daniele Basile <asterix at develer.com>
+ */
+
+#ifndef CFG_TIMER_H
+#define CFG_TIMER_H
+
+/**
+ * Hardware timer selection for drv/timer.c.
+ * $WIZ$ type = "enum"
+ * $WIZ$ value_list = "timer_select"
+ */
+#define CONFIG_TIMER TIMER_DEFAULT
+
+/**
+ * Debug timer interrupt using a strobe pin.
+ * $WIZ$ type = "boolean"
+ */
+#define CONFIG_TIMER_STROBE  0
+
+/**
+ * Enable asynchronous timers.
+ * $WIZ$ type = "boolean"
+ */
+#define CONFIG_TIMER_EVENTS  1
+
+/**
+ * Support hi-res timer_usleep().
+ * $WIZ$ type = "boolean"
+ */
+#define CONFIG_TIMER_UDELAY  1
+
+#endif /* CFG_TIMER_H */
diff --git a/examples/demo/demo.c b/examples/demo/demo.c
index 5a6f507..aa9463a 100644
--- a/examples/demo/demo.c
+++ b/examples/demo/demo.c
@@ -230,6 +230,13 @@ void proc_demo(void)
 	proc_testRun();
 }
 
+#if CONFIG_KERN_PREEMPT
+void preempt_demo(void)
+{
+	preempt_testRun();
+}
+#endif
+
 void timer_demo(void)
 {
 	timer_testRun();
@@ -286,6 +293,9 @@ static struct MenuItem main_items[] =
 	{ (const_iptr_t)"Bounce!",     0, (MenuHook)bouncing_logo,(iptr_t)&lcd_bitmap    },
 	{ (const_iptr_t)"Hello World", 0, (MenuHook)hello_world,  (iptr_t)&lcd_bitmap    },
 	{ (const_iptr_t)"Scheduling",  0, (MenuHook)proc_demo,    (iptr_t)&lcd_bitmap    },
+#if CONFIG_KERN_PREEMPT
+	{ (const_iptr_t)"Preemption",  0, (MenuHook)preempt_demo, (iptr_t)&lcd_bitmap    },
+#endif
 	{ (const_iptr_t)"Timer Test",  0, (MenuHook)timer_demo,   (iptr_t)&lcd_bitmap    },
 	{ (const_iptr_t)"Menu MX",     0, (MenuHook)menu_handle,  (iptr_t)&mx_menu       },
 	{ (const_iptr_t)"Display",     0, (MenuHook)menu_handle,  (iptr_t)&display_menu  },
@@ -301,9 +311,6 @@ int main(int argc, char *argv[])
 {
 	emul_init(&argc, argv);
 
-	#if CONFIG_KERN_PREEMPT
-		irq_init();
-	#endif
 	timer_init();
 	buz_init();
 	kbd_init();
diff --git a/examples/demo/demo.mk b/examples/demo/demo.mk
index db4f82e..764ac6f 100644
--- a/examples/demo/demo.mk
+++ b/examples/demo/demo.mk
@@ -57,7 +57,6 @@ demo_CSRC = \
 	bertos/mware/sprintf.c \
 	bertos/kern/idle.c \
 	bertos/kern/irq.c \
-	bertos/kern/coop.c \
 	bertos/kern/proc.c \
 	bertos/kern/proc_test.c \
 	bertos/kern/sem.c \
@@ -83,6 +82,21 @@ demo_CFLAGS = -O0 -g3 -ggdb -Iexamples/demo $(EMUL_CFLAGS)
 demo_CXXFLAGS = -O0 -g3 -ggdb -Iexamples/demo $(EMUL_CFLAGS)
 demo_LDFLAGS = $(EMUL_LDFLAGS)
 
+# Kernel preemption
+ifeq ($(PREEMPT),1)
+demo_CSRC += \
+	bertos/kern/preempt.c \
+	bertos/kern/preempt_test.c
+demo_CFLAGS += -DPREEMPT
+demo_CXXFLAGS += -DPREEMPT
+demo_LDFLAGS += -DPREEMPT
+else
+ddemo_CFLAGS += -DNPREEMPT
+demo_CXXFLAGS += -DNPREEMPT
+demo_LDFLAGS += -DNPREEMPT
+demo_CSRC += bertos/kern/coop.c
+endif
+
 # Debug stuff
 ifeq ($(demo_DEBUG),0)
 	demo_CFLAGS += -Os
diff --git a/test/run_tests.sh b/test/run_tests.sh
index ed6ee00..0d36ba7 100755
--- a/test/run_tests.sh
+++ b/test/run_tests.sh
@@ -14,6 +14,7 @@
 #  3 - execution output
 #  4 - build commands
 VERBOSE=${VERBOSE:-1}
+PREEMPT=${PREEMPT:0}
 
 CC=gcc
 #FIXME: -Ibertos/emul should not be needed
@@ -34,7 +35,6 @@ SRC_LIST="
 	bertos/drv/kdebug.c
 	bertos/drv/timer.c
 	bertos/fs/battfs.c
-	bertos/kern/coop.c
 	bertos/kern/idle.c
 	bertos/kern/kfile.c
 	bertos/kern/monitor.c
@@ -63,6 +63,12 @@ SRC_LIST="
 	bertos/net/nmea.c
 "
 
+if [ "z$PREEMPT" == "z1" ]; then
+	SRC_LIST="${SRC_LIST}bertos/kern/preempt.c"
+else
+	SRC_LIST="${SRC_LIST}bertos/kern/coop.c"
+fi
+
 buildout='/dev/null'
 runout='/dev/null'
 [ "$VERBOSE" -ge 2 ] && buildout='/dev/stdout'

-- 
Andrea Righi - Develer S.r.l.
http://www.develer.com


More information about the BeRTOS mailing list