mruby-task: introduce HAL (hardware abstraction layer) for platform support

separates platform-specific timer and interrupt code into hal-posix-task
and hal-win-task gems. mruby-task now uses HAL interface defined in
task_hal.h, making it easier to port to new platforms.

hal-posix-task: uses sigalrm/setitimer for timer, sigprocmask for irq
protection, and SA_RESTART flag to prevent EINTR on system calls.

hal-win-task: uses multimedia timer API and critical_section for irq
protection.

both HALs support multiple mrb_state instances with single shared timer.
auto-detection loads appropriate HAL based on platform.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-10-14 08:09:33 +09:00
parent 4181a42cd2
commit 9c100ab844
10 changed files with 938 additions and 618 deletions
+102
View File
@@ -0,0 +1,102 @@
# hal-posix-task
POSIX Hardware Abstraction Layer (HAL) implementation for mruby-task.
## Description
Provides timer and interrupt support for the mruby-task cooperative scheduler on POSIX-compliant platforms. Uses `SIGALRM` and `setitimer()` for periodic timer ticks, and `sigprocmask()` for interrupt protection.
## Supported Platforms
- Linux
- macOS
- BSD (FreeBSD, OpenBSD, NetBSD)
- Other POSIX-compliant Unix systems
## Requirements
- POSIX-compliant operating system
- Signal support (`SIGALRM`, `sigaction`, `sigprocmask`)
- Timer support (`setitimer`, `ITIMER_REAL`)
## Usage
### Explicit HAL Selection (Recommended)
```ruby
MRuby::Build.new do |conf|
# ... other configuration ...
# Specify POSIX HAL - automatically brings in mruby-task
conf.gem core: 'hal-posix-task'
end
```
### Auto-detection (Development)
```ruby
MRuby::Build.new do |conf|
# ... other configuration ...
# Auto-detects and selects hal-posix-task on POSIX platforms
conf.gem core: 'mruby-task'
end
```
## Implementation Details
### Timer Mechanism
- Uses `setitimer(ITIMER_REAL, ...)` to generate periodic `SIGALRM` signals
- Timer interval configured by `MRB_TICK_UNIT` (default: 4ms)
- Signal handler calls `mrb_tick()` for all registered VM instances
### Interrupt Protection
- Critical sections protected using `sigprocmask()` to block `SIGALRM`
- Prevents race conditions during task queue modifications
- Supports nested critical sections through signal masking
### Multi-VM Support
- Supports up to `MRB_TASK_MAX_VMS` concurrent mruby VM instances (default: 8)
- Single shared timer ticks all registered VMs
- Per-VM task counters optimize timer usage (timer disabled when idle)
### Timer Optimization
The implementation dynamically enables/disables the timer based on task state:
- **Timer enabled** when: Multiple ready tasks OR any waiting tasks exist
- **Timer disabled** when: Single task or all tasks dormant/suspended
- Reduces CPU usage and power consumption when scheduler is idle
## Configuration
Override these macros in your build config if needed:
```ruby
conf.gem core: 'hal-posix-task' do |spec|
# Custom tick interval (10ms instead of default 4ms)
spec.build.defines << 'MRB_TICK_UNIT=10'
# Custom timeslice (5 ticks instead of default 3)
spec.build.defines << 'MRB_TIMESLICE_TICK_COUNT=5'
# More concurrent VMs (16 instead of default 8)
spec.build.defines << 'MRB_TASK_MAX_VMS=16'
end
```
## Known Limitations
- `SIGALRM` conflicts with other code using the same signal
- Timer resolution limited by platform (typically 1-10ms)
- Signal delivery may be delayed under heavy system load
- Not suitable for hard real-time requirements
## See Also
- `mruby-task` - Core task scheduler
- `hal-win-task` - Windows HAL implementation
- Task scheduler documentation: `mrbgems/mruby-task/README.md`
+8
View File
@@ -0,0 +1,8 @@
MRuby::Gem::Specification.new('hal-posix-task') do |spec|
spec.license = 'MIT'
spec.authors = 'mruby developers'
spec.summary = 'POSIX HAL for mruby-task (Linux, macOS, BSD, Unix)'
# HAL gem depends on feature gem - brings in mruby-task automatically
spec.add_dependency 'mruby-task', core: 'mruby-task'
end
+174
View File
@@ -0,0 +1,174 @@
/*
** task_hal.c - POSIX HAL implementation for mruby-task
**
** See Copyright Notice in mruby.h
**
** POSIX implementation using SIGALRM and setitimer() for timer,
** and sigprocmask() for interrupt protection.
**
** Supported platforms: Linux, macOS, BSD, Unix
*/
#include <mruby.h>
#include "task_hal.h"
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdint.h>
/* Multi-VM support */
static mrb_state *vm_list[MRB_TASK_MAX_VMS];
static volatile sig_atomic_t vm_count = 0;
static sigset_t alarm_mask;
/* SIGALRM signal handler - ticks all registered VMs */
static void
sigalrm_handler(int sig)
{
int i;
(void)sig;
/* Tick all registered VMs */
for (i = 0; i < vm_count; i++) {
if (vm_list[i]) {
mrb_tick(vm_list[i]);
}
}
}
/*
* HAL Interface Implementation
*/
void
mrb_task_hal_init(mrb_state *mrb)
{
struct sigaction sa;
struct itimerval timer;
int i;
int vm_index = -1;
/* Initialize task state */
for (i = 0; i < 4; i++) {
mrb->task.queues[i] = NULL;
}
mrb->task.tick = 0;
mrb->task.wakeup_tick = UINT32_MAX;
mrb->task.switching = FALSE;
/* Block SIGALRM during registration to avoid race */
sigemptyset(&alarm_mask);
sigaddset(&alarm_mask, SIGALRM);
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
/* Check if this VM is already registered */
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
vm_index = i;
break;
}
}
/* Register new VM if not already present */
if (vm_index < 0) {
if (vm_count >= MRB_TASK_MAX_VMS) {
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
mrb_raisef(mrb, E_RUNTIME_ERROR,
"too many mrb_states with task scheduler (max: %d)",
MRB_TASK_MAX_VMS);
}
vm_list[vm_count] = mrb;
vm_count++;
}
/* Set up signal handler and timer only for first VM */
if (vm_count == 1) {
/* Set up signal handler - SA_RESTART to avoid breaking IO operations */
sa.sa_handler = sigalrm_handler;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaction(SIGALRM, &sa, NULL);
/* Start timer */
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = MRB_TICK_UNIT * 1000;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = MRB_TICK_UNIT * 1000;
setitimer(ITIMER_REAL, &timer, NULL);
}
/* Unblock SIGALRM */
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
}
void
mrb_task_enable_irq(void)
{
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
}
void
mrb_task_disable_irq(void)
{
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
}
void
mrb_task_hal_idle_cpu(mrb_state *mrb)
{
(void)mrb;
/* On POSIX, just pause briefly */
usleep(MRB_TICK_UNIT * 1000);
}
void
mrb_task_hal_final(mrb_state *mrb)
{
struct itimerval timer;
int i, j;
/* Block SIGALRM during unregistration */
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
/* Find and remove this VM from the list */
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
/* Shift remaining VMs down */
for (j = i; j < vm_count - 1; j++) {
vm_list[j] = vm_list[j + 1];
}
vm_list[vm_count - 1] = NULL;
vm_count--;
break;
}
}
/* Stop timer if last VM */
if (vm_count == 0) {
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &timer, NULL);
}
/* Unblock SIGALRM */
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
}
/*
* Gem initialization (empty - HAL functions called by mruby-task)
*/
void
mrb_hal_posix_task_gem_init(mrb_state *mrb)
{
(void)mrb;
/* HAL interface functions are called by mruby-task gem */
}
void
mrb_hal_posix_task_gem_final(mrb_state *mrb)
{
(void)mrb;
/* Cleanup handled by mrb_task_hal_final called from mruby-task */
}
+109
View File
@@ -0,0 +1,109 @@
# hal-win-task
Windows Hardware Abstraction Layer (HAL) implementation for mruby-task.
## Description
Provides timer and interrupt support for the mruby-task cooperative scheduler on Windows platforms. Uses multimedia timer (`timeSetEvent`/`timeKillEvent`) for periodic timer ticks, and `CRITICAL_SECTION` for interrupt protection.
## Supported Platforms
- Windows 7 and later
- Windows Server 2008 R2 and later
- All versions with multimedia timer support
## Requirements
- Windows operating system
- Multimedia timer API (`winmm.lib`)
- Visual C++, MinGW, or compatible compiler
## Usage
### Explicit HAL Selection (Recommended)
```ruby
MRuby::Build.new do |conf|
# ... other configuration ...
# Specify Windows HAL - automatically brings in mruby-task
conf.gem core: 'hal-win-task'
end
```
### Auto-detection (Development)
```ruby
MRuby::Build.new do |conf|
# ... other configuration ...
# Auto-detects and selects hal-win-task on Windows platforms
conf.gem core: 'mruby-task'
end
```
## Implementation Details
### Timer Mechanism
- Uses Windows multimedia timer (`timeSetEvent`) for periodic callbacks
- Timer interval configured by `MRB_TICK_UNIT` (default: 4ms)
- `TIME_KILL_SYNCHRONOUS` flag ensures clean timer shutdown
- Requests 1ms timer resolution via `timeBeginPeriod(1)`
### Interrupt Protection
- Critical sections protected using `CRITICAL_SECTION` objects
- Prevents race conditions during task queue modifications
- `EnterCriticalSection`/`LeaveCriticalSection` used for mutual exclusion
- Supports nested critical sections (automatic lock counting)
### Multi-VM Support
- Supports up to `MRB_TASK_MAX_VMS` concurrent mruby VM instances (default: 8)
- Single shared timer ticks all registered VMs
- Per-VM task counters optimize timer usage (timer disabled when idle)
- Interlocked operations (`InterlockedIncrement`/`InterlockedDecrement`) for thread safety
### Timer Optimization
The implementation dynamically enables/disables the timer based on task state:
- **Timer enabled** when: Multiple ready tasks OR any waiting tasks exist
- **Timer disabled** when: Single task or all tasks dormant/suspended
- Reduces CPU usage and power consumption when scheduler is idle
## Configuration
Override these macros in your build config if needed:
```ruby
conf.gem core: 'hal-win-task' do |spec|
# Custom tick interval (10ms instead of default 4ms)
spec.build.defines << 'MRB_TICK_UNIT=10'
# Custom timeslice (5 ticks instead of default 3)
spec.build.defines << 'MRB_TIMESLICE_TICK_COUNT=5'
# More concurrent VMs (16 instead of default 8)
spec.build.defines << 'MRB_TASK_MAX_VMS=16'
end
```
## Known Limitations
- Timer resolution typically limited to 1-2ms even with `timeBeginPeriod(1)`
- Multimedia timers consume system resources (kernel timer objects)
- Timer callbacks execute in separate thread context (handled internally)
- Not suitable for hard real-time requirements
- May interfere with other multimedia applications requesting different timer resolutions
## See Also
- `mruby-task` - Core task scheduler
- `hal-posix-task` - POSIX/Unix HAL implementation
- Task scheduler documentation: `mrbgems/mruby-task/README.md`
## Build Notes
The `winmm` library is automatically linked by the gem specification. No additional linker configuration is needed.
+11
View File
@@ -0,0 +1,11 @@
MRuby::Gem::Specification.new('hal-win-task') do |spec|
spec.license = 'MIT'
spec.authors = 'mruby developers'
spec.summary = 'Windows HAL for mruby-task'
# HAL gem depends on feature gem - brings in mruby-task automatically
spec.add_dependency 'mruby-task', core: 'mruby-task'
# Windows multimedia timer library
spec.linker.libraries << 'winmm'
end
+176
View File
@@ -0,0 +1,176 @@
/*
** task_hal.c - Windows HAL implementation for mruby-task
**
** See Copyright Notice in mruby.h
**
** Windows implementation using multimedia timer (timeSetEvent/timeKillEvent)
** for periodic timer, and CRITICAL_SECTION for interrupt protection.
**
** Supported platforms: Windows (all versions with multimedia timer support)
*/
#include <mruby.h>
#include "task_hal.h"
#include <windows.h>
#include <timeapi.h>
#include <stdint.h>
/* Multi-VM support */
static mrb_state *vm_list[MRB_TASK_MAX_VMS];
static volatile LONG vm_count = 0;
static CRITICAL_SECTION irq_lock;
static MMRESULT timer_id = 0;
/* Multimedia timer callback - called periodically by Windows */
static void CALLBACK
timer_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
int i;
(void)uID; (void)uMsg; (void)dwUser; (void)dw1; (void)dw2;
/* Tick all registered VMs */
EnterCriticalSection(&irq_lock);
for (i = 0; i < vm_count; i++) {
if (vm_list[i]) {
mrb_tick(vm_list[i]);
}
}
LeaveCriticalSection(&irq_lock);
}
/*
* HAL Interface Implementation
*/
void
mrb_task_hal_init(mrb_state *mrb)
{
int i;
LONG idx;
/* Initialize task state */
for (i = 0; i < 4; i++) {
mrb->task.queues[i] = NULL;
}
mrb->task.tick = 0;
mrb->task.wakeup_tick = UINT32_MAX;
mrb->task.switching = FALSE;
/* Initialize critical section on first VM */
if (vm_count == 0) {
InitializeCriticalSection(&irq_lock);
}
EnterCriticalSection(&irq_lock);
/* Check if this VM is already registered */
idx = -1;
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
idx = i;
break;
}
}
/* Register new VM if not already present */
if (idx < 0) {
if (vm_count >= MRB_TASK_MAX_VMS) {
LeaveCriticalSection(&irq_lock);
mrb_raisef(mrb, E_RUNTIME_ERROR,
"too many mrb_states with task scheduler (max: %d)",
MRB_TASK_MAX_VMS);
}
vm_list[vm_count] = mrb;
InterlockedIncrement(&vm_count);
}
/* Start timer for first VM */
if (vm_count == 1) {
/* Request 1ms timer resolution */
timeBeginPeriod(1);
/* Create periodic timer with MRB_TICK_UNIT interval */
timer_id = timeSetEvent(
MRB_TICK_UNIT, /* interval in milliseconds */
1, /* resolution in milliseconds */
timer_callback, /* callback function */
0, /* user data */
TIME_PERIODIC | TIME_KILL_SYNCHRONOUS
);
}
LeaveCriticalSection(&irq_lock);
}
void
mrb_task_enable_irq(void)
{
LeaveCriticalSection(&irq_lock);
}
void
mrb_task_disable_irq(void)
{
EnterCriticalSection(&irq_lock);
}
void
mrb_task_hal_idle_cpu(mrb_state *mrb)
{
(void)mrb;
/* On Windows, just sleep briefly */
Sleep(MRB_TICK_UNIT);
}
void
mrb_task_hal_final(mrb_state *mrb)
{
int i, j;
EnterCriticalSection(&irq_lock);
/* Find and remove this VM from the list */
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
/* Shift remaining VMs down */
for (j = i; j < vm_count - 1; j++) {
vm_list[j] = vm_list[j + 1];
}
vm_list[vm_count - 1] = NULL;
InterlockedDecrement(&vm_count);
break;
}
}
/* Stop timer if last VM */
if (vm_count == 0) {
if (timer_id != 0) {
timeKillEvent(timer_id);
timeEndPeriod(1);
timer_id = 0;
}
LeaveCriticalSection(&irq_lock);
DeleteCriticalSection(&irq_lock);
}
else {
LeaveCriticalSection(&irq_lock);
}
}
/*
* Gem initialization (empty - HAL functions called by mruby-task)
*/
void
mrb_hal_win_task_gem_init(mrb_state *mrb)
{
(void)mrb;
/* HAL interface functions are called by mruby-task gem */
}
void
mrb_hal_win_task_gem_final(mrb_state *mrb)
{
(void)mrb;
/* Cleanup handled by mrb_task_hal_final called from mruby-task */
}
+159 -21
View File
@@ -251,42 +251,180 @@ These grow automatically as needed, similar to Fiber.
### HAL (Hardware Abstraction Layer)
The task scheduler requires platform-specific timer and interrupt support via four HAL functions:
The task scheduler uses a Hardware Abstraction Layer (HAL) to support different platforms. Platform-specific timer and interrupt handling is provided by separate HAL gems.
```c
void mrb_task_hal_init(mrb_state *mrb); // Initialize timer
void mrb_task_enable_irq(void); // Enable timer interrupts
void mrb_task_disable_irq(void); // Disable timer interrupts
void mrb_task_hal_idle_cpu(mrb_state *mrb); // Idle when no tasks ready
#### Built-in HAL Gems
**hal-posix-task** - For POSIX systems (Linux, macOS, BSD, Unix)
- Uses `SIGALRM` and `setitimer()` for timer
- Uses `sigprocmask()` for interrupt protection
- Uses `SA_RESTART` to prevent `EINTR` on system calls
- Supports multiple VMs per process
**hal-win-task** - For Windows
- Uses multimedia timer API (`timeSetEvent`/`timeKillEvent`)
- Uses `CRITICAL_SECTION` for interrupt protection
- Supports multiple VMs per process
#### HAL Selection
The task scheduler will automatically select an appropriate HAL gem based on your platform. For explicit control, you can specify the HAL gem in your build configuration:
```ruby
MRuby::Build.new do |conf|
# Option 1: Explicit HAL selection (recommended)
conf.gem core: 'hal-posix-task' # For Linux/macOS/BSD
# or
conf.gem core: 'hal-win-task' # For Windows
# mruby-task automatically loads if HAL is loaded
# But you can also specify it explicitly:
conf.gem core: 'mruby-task'
end
```
### POSIX Platform
**Auto-detection behavior:**
On POSIX systems (Linux, macOS, BSD), the implementation uses:
- If you include `mruby-task` but no HAL gem, it will automatically load the appropriate HAL
- On Linux/macOS/BSD: loads `hal-posix-task`
- On Windows: loads `hal-win-task`
- On unknown platforms: fails with helpful error message
- `SIGALRM` signal for timer interrupts
- `setitimer()` for periodic tick generation
- `sigprocmask()` for interrupt enable/disable
- `SA_RESTART` flag to prevent `EINTR` on system calls
**Multi-VM support:**
### Other Platforms
- Both HAL implementations support multiple `mrb_state` instances
- A single system timer ticks all registered VMs
- Maximum VMs: configurable via `MRB_TASK_MAX_VMS` (default: 8)
For embedded or non-POSIX platforms, you must implement the HAL functions. See
the POSIX implementation in `src/task.c` as a reference.
#### Custom HAL Implementation
Example for a hypothetical embedded platform:
For embedded systems or unsupported platforms, you can create a custom HAL gem. The HAL must provide five functions defined in `mruby-task/include/task_hal.h`:
```c
void mrb_task_hal_init(mrb_state *mrb) {
// Setup hardware timer to call mrb_tick() every MRB_TICK_UNIT ms
hardware_timer_init(MRB_TICK_UNIT, timer_irq_handler);
/**
* Initialize timer and register VM
* Called during gem initialization
* Must set up periodic timer to call mrb_tick(mrb) every MRB_TICK_UNIT ms
*/
void mrb_task_hal_init(mrb_state *mrb);
/**
* Cleanup timer and unregister VM
* Called during gem finalization
*/
void mrb_task_hal_final(mrb_state *mrb);
/**
* Enable timer interrupts (exit critical section)
* Must be reentrant for nested calls
*/
void mrb_task_enable_irq(void);
/**
* Disable timer interrupts (enter critical section)
* Must be reentrant for nested calls
*/
void mrb_task_disable_irq(void);
/**
* Put CPU in low-power/idle mode
* Called when no tasks are ready but some are waiting
* Should sleep ~MRB_TICK_UNIT milliseconds
*/
void mrb_task_hal_idle_cpu(mrb_state *mrb);
```
**Example custom HAL gem structure:**
```
mrbgems/hal-myplatform-task/
├── mrbgem.rake # Gem specification
├── include/
│ └── task_hal.h # Symlink to mruby-task/include/task_hal.h
└── src/
└── task_hal.c # Platform implementation
```
**mrbgem.rake:**
```ruby
MRuby::Gem::Specification.new('hal-myplatform-task') do |spec|
spec.license = 'MIT'
spec.authors = 'Your Name'
spec.summary = 'My Platform HAL for mruby-task'
# HAL gem depends on feature gem (important for build order)
spec.add_dependency 'mruby-task', core: 'mruby-task'
# Add any platform-specific libraries or flags
# spec.linker.libraries << 'myplatform_timer'
end
```
**task_hal.c example for embedded system:**
```c
#include <mruby.h>
#include "task_hal.h"
#include "myplatform_hardware.h"
static mrb_state *registered_vm = NULL;
void mrb_task_hal_init(mrb_state *mrb)
{
registered_vm = mrb;
// Setup hardware timer to fire every MRB_TICK_UNIT milliseconds
hardware_timer_init(MRB_TICK_UNIT, timer_isr);
hardware_timer_start();
}
void timer_irq_handler(void) {
mrb_tick(global_mrb); // Must be called from timer interrupt
void mrb_task_hal_final(mrb_state *mrb)
{
hardware_timer_stop();
registered_vm = NULL;
}
void mrb_task_enable_irq(void)
{
hardware_enable_interrupts();
}
void mrb_task_disable_irq(void)
{
hardware_disable_interrupts();
}
void mrb_task_hal_idle_cpu(mrb_state *mrb)
{
(void)mrb;
hardware_sleep_mode(); // Enter low-power mode until interrupt
}
// Timer ISR - must call mrb_tick() for scheduler
void timer_isr(void)
{
if (registered_vm) {
mrb_tick(registered_vm);
}
}
// Gem initialization (required but can be empty)
void mrb_hal_myplatform_task_gem_init(mrb_state *mrb)
{
(void)mrb;
}
void mrb_hal_myplatform_task_gem_final(mrb_state *mrb)
{
(void)mrb;
}
```
See `hal-posix-task` and `hal-win-task` source code for complete reference implementations.
## Examples
### Basic Multitasking
+165
View File
@@ -0,0 +1,165 @@
/*
** task_hal.h - Task scheduler Hardware Abstraction Layer (HAL)
**
** See Copyright Notice in mruby.h
**
** This header defines the HAL interface that platform-specific implementations
** must provide. The HAL separates platform-specific timer and interrupt handling
** from the core task scheduler logic.
*/
#ifndef MRUBY_TASK_HAL_H
#define MRUBY_TASK_HAL_H
#include <mruby.h>
/*
* Configuration - can be overridden in platform-specific build configs
*/
/* Tick period in milliseconds - how often the timer fires */
#ifndef MRB_TICK_UNIT
#define MRB_TICK_UNIT 4
#endif
/* Number of timer ticks per task timeslice */
#ifndef MRB_TIMESLICE_TICK_COUNT
#define MRB_TIMESLICE_TICK_COUNT 3
#endif
/* Maximum number of concurrent mrb_state instances with task scheduler */
#ifndef MRB_TASK_MAX_VMS
#define MRB_TASK_MAX_VMS 8
#endif
/*
* HAL Interface Functions
*
* Platform-specific implementations (hal-posix-task, hal-win-task, etc.)
* must provide these functions.
*/
/**
* Initialize hardware timer and interrupt system
*
* Called once during mruby-task gem initialization. Should set up a periodic
* timer that calls mrb_tick(mrb) every MRB_TICK_UNIT milliseconds.
*
* Requirements:
* - Initialize platform-specific timer hardware/APIs
* - Set up timer to fire every MRB_TICK_UNIT milliseconds
* - Register mrb_state for multi-VM support if needed
* - Initialize interrupt protection mechanisms (mutexes, signal masks, etc.)
* - Timer should call mrb_tick() on each tick for all registered VMs
*
* @param mrb The mruby state to associate with the timer
*/
void mrb_task_hal_init(mrb_state *mrb);
/**
* Cleanup timer and interrupt resources
*
* Called during mruby-task gem finalization. Should clean up all resources
* allocated by mrb_task_hal_init().
*
* Requirements:
* - Stop and cleanup platform timer
* - Unregister mrb_state from multi-VM support
* - Free any allocated HAL resources
* - If last VM, cleanup global HAL state
*
* @param mrb The mruby state to disassociate from the timer
*/
void mrb_task_hal_final(mrb_state *mrb);
/**
* Enable timer interrupts (exit critical section)
*
* Called by the task scheduler when it's safe to allow timer interrupts.
* Should enable timer interrupts/callbacks that were disabled by
* mrb_task_disable_irq().
*
* Requirements:
* - Must be reentrant (can be called multiple times)
* - Should use nesting counter or equivalent for nested critical sections
* - On POSIX: unmask signals
* - On Windows: leave critical section
* - On embedded: enable timer interrupts
*/
void mrb_task_enable_irq(void);
/**
* Disable timer interrupts (enter critical section)
*
* Called by the task scheduler before modifying shared task state.
* Should disable timer interrupts/callbacks to prevent concurrent access.
*
* Requirements:
* - Must be reentrant (can be called multiple times)
* - Should use nesting counter or equivalent for nested critical sections
* - On POSIX: block signals
* - On Windows: enter critical section
* - On embedded: disable timer interrupts
*/
void mrb_task_disable_irq(void);
/**
* Put CPU in low-power/idle mode
*
* Called by the scheduler when no tasks are ready to run but some tasks
* are waiting or suspended. Should briefly idle the CPU or sleep for
* approximately MRB_TICK_UNIT milliseconds to allow timer to fire.
*
* Requirements:
* - Should return when timer fires or after ~MRB_TICK_UNIT milliseconds
* - Must allow timer interrupts to occur during idle
* - On POSIX: usleep() or nanosleep()
* - On Windows: Sleep()
* - On embedded: platform-specific sleep/wait-for-interrupt instruction
*
* @param mrb The mruby state (for context, may be unused)
*/
void mrb_task_hal_idle_cpu(mrb_state *mrb);
/*
* Core scheduler functions (implemented in task.c, called by HAL)
*
* These are provided by the core scheduler for HAL implementations to call.
*/
/**
* Tick handler - advances scheduler time and wakes sleeping tasks
*
* HAL timer callback must call this function every MRB_TICK_UNIT milliseconds
* for each registered mrb_state. This function:
* - Increments the global tick counter
* - Decrements running task's timeslice
* - Wakes tasks whose sleep time has expired
* - Triggers context switches when needed
*
* @param mrb The mruby state to tick
*/
void mrb_tick(mrb_state *mrb);
/**
* Main scheduler loop - runs all tasks until completion
*
* Executes tasks in priority order until all tasks are dormant.
* Handles task switching, preemption, and idle states.
*
* @param mrb The mruby state containing the task scheduler
* @return mrb_nil_value() when all tasks complete
*/
mrb_value mrb_tasks_run(mrb_state *mrb);
/**
* Mark all tasks for garbage collection
*
* Called by the GC during marking phase to protect task objects
* and their associated data from collection.
*
* @param mrb The mruby state
*/
void mrb_task_mark_all(mrb_state *mrb);
#endif /* MRUBY_TASK_HAL_H */
+25 -3
View File
@@ -6,8 +6,30 @@ MRuby::Gem::Specification.new('mruby-task') do |spec|
# Enable task scheduler globally (required for vm.c integration)
spec.build.defines << 'MRB_USE_TASK_SCHEDULER'
# Windows: link with multimedia timer library
if spec.for_windows?
spec.linker.libraries << 'winmm'
# Check if HAL gem is loaded
# HAL gems must be explicitly specified in build config (recommended) or via auto-selection below
spec.build.gems.one? { |g| g.name =~ /^hal-.*-task$/ } or begin
# No HAL found - determine appropriate error message or auto-load
suggested_hal = if spec.for_windows?
'hal-win-task'
elsif RUBY_PLATFORM =~ /linux|darwin|bsd/
'hal-posix-task'
else
nil
end
if suggested_hal
# Auto-load HAL gem for convenience (for development)
# This works because HAL gems declare dependency on mruby-task
warn "mruby-task: No HAL specified, loading #{suggested_hal} (explicit selection recommended)"
spec.build.gem core: suggested_hal
else
# Unknown platform - fail with helpful message
fail "mruby-task: No HAL available for platform '#{RUBY_PLATFORM}'.\n" \
"Please specify HAL gem explicitly in your build config:\n" \
" conf.gem core: 'hal-posix-task' # For Linux/macOS/BSD\n" \
" conf.gem core: 'hal-win-task' # For Windows\n" \
"Or create custom HAL - see mrbgems/mruby-task/README.md"
end
end
end
+9 -594
View File
@@ -19,13 +19,8 @@
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <time.h>
#include <unistd.h>
#endif
#include "../include/task.h"
#include "task_hal.h"
/*
* Queue helper macros
@@ -283,10 +278,6 @@ task_init_context(mrb_state *mrb, mrb_task *t, const struct RProc *proc)
* Scheduler core
*/
/* Forward declarations for timer control (defined in HAL section) */
static void update_timer_state(void);
static void task_count_update(mrb_state *mrb, uint8_t old_status, uint8_t new_status);
/* Wake up tasks waiting on join for a completed task */
static void
wake_up_join_waiters(mrb_state *mrb, mrb_task *completed_task)
@@ -301,7 +292,6 @@ wake_up_join_waiters(mrb_state *mrb, mrb_task *completed_task)
curr->reason = MRB_TASK_REASON_NONE;
curr->join = NULL;
q_insert_task(mrb, curr);
task_count_update(mrb, MRB_TASK_STATUS_WAITING, MRB_TASK_STATUS_READY);
mrb_task_enable_irq();
}
curr = next;
@@ -312,12 +302,10 @@ wake_up_join_waiters(mrb_state *mrb, mrb_task *completed_task)
static void
task_change_state(mrb_state *mrb, mrb_task *t, uint8_t new_status)
{
uint8_t old_status = t->status;
mrb_task_disable_irq();
q_delete_task(mrb, t);
t->status = new_status;
q_insert_task(mrb, t);
task_count_update(mrb, old_status, new_status);
mrb_task_enable_irq();
}
@@ -381,7 +369,6 @@ execute_task(mrb_state *mrb, mrb_task *t)
q_delete_task(mrb, t);
t->status = MRB_TASK_STATUS_DORMANT;
q_insert_task(mrb, t);
task_count_update(mrb, MRB_TASK_STATUS_RUNNING, MRB_TASK_STATUS_DORMANT);
mrb_task_enable_irq();
/* Wake up tasks waiting on join */
@@ -495,6 +482,14 @@ mrb_tasks_run(mrb_state *mrb)
* Sleep operations
*/
/* Platform-specific includes for root-context sleeping */
#if defined(__unix__) || defined(__APPLE__) || defined(__MACH__)
#include <time.h>
#include <unistd.h>
#elif defined(_WIN32)
#include <windows.h>
#endif
static void
sleep_us_impl(mrb_state *mrb, mrb_int usec)
{
@@ -580,7 +575,6 @@ sleep_us_impl(mrb_state *mrb, mrb_int usec)
}
q_insert_task(mrb, t);
task_count_update(mrb, MRB_TASK_STATUS_READY, MRB_TASK_STATUS_WAITING);
mrb_task_enable_irq();
@@ -610,7 +604,6 @@ mrb_f_sleep(mrb_state *mrb, mrb_value self)
q_delete_task(mrb, t);
t->status = MRB_TASK_STATUS_SUSPENDED;
q_insert_task(mrb, t);
task_count_update(mrb, MRB_TASK_STATUS_READY, MRB_TASK_STATUS_SUSPENDED);
mrb_task_enable_irq();
switching_ = TRUE;
}
@@ -659,580 +652,6 @@ mrb_f_usleep(mrb_state *mrb, mrb_value self)
return mrb_fixnum_value(usec);
}
/*
* HAL POSIX implementation (Linux, BSD, macOS)
*/
#if defined(__unix__) || defined(__APPLE__) || defined(__MACH__)
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
/* Maximum number of concurrent mrb_states with task scheduler */
#ifndef MRB_TASK_MAX_VMS
#define MRB_TASK_MAX_VMS 8
#endif
static mrb_state *vm_list[MRB_TASK_MAX_VMS];
static volatile sig_atomic_t vm_count = 0;
static volatile sig_atomic_t timer_enabled = 0;
static sigset_t alarm_mask;
/* Task counters for each VM */
static uint16_t vm_ready_counts[MRB_TASK_MAX_VMS];
static uint16_t vm_waiting_counts[MRB_TASK_MAX_VMS];
/* Find VM index in vm_list */
static int
find_vm_index(mrb_state *mrb)
{
int i;
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
return i;
}
}
return -1;
}
/* Helper functions to maintain task counters and update timer */
static void
task_count_update(mrb_state *mrb, uint8_t old_status, uint8_t new_status)
{
int vm_idx = find_vm_index(mrb);
if (vm_idx < 0) return;
/* Decrement old queue counter */
if (old_status == MRB_TASK_STATUS_READY || old_status == MRB_TASK_STATUS_RUNNING) {
if (vm_ready_counts[vm_idx] > 0) {
vm_ready_counts[vm_idx]--;
}
}
else if (old_status == MRB_TASK_STATUS_WAITING) {
if (vm_waiting_counts[vm_idx] > 0) {
vm_waiting_counts[vm_idx]--;
}
}
/* Increment new queue counter */
if (new_status == MRB_TASK_STATUS_READY || new_status == MRB_TASK_STATUS_RUNNING) {
vm_ready_counts[vm_idx]++;
}
else if (new_status == MRB_TASK_STATUS_WAITING) {
vm_waiting_counts[vm_idx]++;
}
update_timer_state();
}
/* Check if timer should be enabled based on task counts */
static int
should_timer_be_enabled(void)
{
int i;
/* Timer needed if any VM has multiple ready tasks OR any waiting tasks */
for (i = 0; i < vm_count; i++) {
if (vm_ready_counts[i] > 1 || vm_waiting_counts[i] > 0) {
return 1;
}
}
return 0;
}
/* Platform-specific timer control */
static void
hal_set_timer_enabled(int enable)
{
struct itimerval timer;
if (enable) {
/* Enable timer */
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = MRB_TICK_UNIT * 1000;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = MRB_TICK_UNIT * 1000;
}
else {
/* Disable timer */
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;
}
setitimer(ITIMER_REAL, &timer, NULL);
}
/* Update timer state based on task counts */
static void
update_timer_state(void)
{
int needs_timer = should_timer_be_enabled();
/* Update timer only if state changed */
if (needs_timer && !timer_enabled) {
hal_set_timer_enabled(1);
timer_enabled = 1;
}
else if (!needs_timer && timer_enabled) {
hal_set_timer_enabled(0);
timer_enabled = 0;
}
}
static void
sigalrm_handler(int sig)
{
int i;
(void)sig;
/* Tick all registered VMs */
for (i = 0; i < vm_count; i++) {
if (vm_list[i]) {
mrb_tick(vm_list[i]);
}
}
}
void
mrb_task_hal_init(mrb_state *mrb)
{
struct sigaction sa;
int i;
int vm_index = -1;
/* Initialize task state */
for (i = 0; i < 4; i++) {
mrb->task.queues[i] = NULL;
}
mrb->task.tick = 0;
mrb->task.wakeup_tick = UINT32_MAX;
mrb->task.switching = FALSE;
/* Block SIGALRM during registration to avoid race */
sigemptyset(&alarm_mask);
sigaddset(&alarm_mask, SIGALRM);
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
/* Check if this VM is already registered */
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
vm_index = i;
break;
}
}
/* Register new VM if not already present */
if (vm_index < 0) {
if (vm_count >= MRB_TASK_MAX_VMS) {
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
mrb_raisef(mrb, E_RUNTIME_ERROR,
"too many mrb_states with task scheduler (max: %d)",
MRB_TASK_MAX_VMS);
}
vm_list[vm_count] = mrb;
vm_ready_counts[vm_count] = 0;
vm_waiting_counts[vm_count] = 0;
vm_count++;
}
/* Set up signal handler only for first VM */
if (vm_count == 1) {
/* Set up signal handler - no SA_RESTART so nanosleep returns on EINTR */
sa.sa_handler = sigalrm_handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGALRM, &sa, NULL);
}
/* Update timer state based on current task queues */
update_timer_state();
/* Unblock SIGALRM */
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
}
void
mrb_task_enable_irq(void)
{
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
}
void
mrb_task_disable_irq(void)
{
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
}
void
mrb_task_hal_idle_cpu(mrb_state *mrb)
{
(void)mrb;
/* On POSIX, just pause briefly */
usleep(MRB_TICK_UNIT * 1000);
}
void
mrb_task_hal_final(mrb_state *mrb)
{
int i, j;
/* Block SIGALRM during unregistration */
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
/* Find and remove this VM from the list */
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
/* Shift remaining VMs and counters down */
for (j = i; j < vm_count - 1; j++) {
vm_list[j] = vm_list[j + 1];
vm_ready_counts[j] = vm_ready_counts[j + 1];
vm_waiting_counts[j] = vm_waiting_counts[j + 1];
}
vm_list[vm_count - 1] = NULL;
vm_ready_counts[vm_count - 1] = 0;
vm_waiting_counts[vm_count - 1] = 0;
vm_count--;
break;
}
}
/* Update timer state based on remaining VMs */
update_timer_state();
/* Unblock SIGALRM */
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
}
#elif defined(_WIN32)
/*
* HAL Windows implementation
*/
#include <windows.h>
#include <timeapi.h>
/* Maximum number of concurrent mrb_states with task scheduler */
#ifndef MRB_TASK_MAX_VMS
#define MRB_TASK_MAX_VMS 8
#endif
static mrb_state *vm_list[MRB_TASK_MAX_VMS];
static volatile LONG vm_count = 0;
static volatile LONG timer_enabled = 0;
static CRITICAL_SECTION irq_lock;
static MMRESULT timer_id = 0;
/* Task counters for each VM */
static uint16_t vm_ready_counts[MRB_TASK_MAX_VMS];
static uint16_t vm_waiting_counts[MRB_TASK_MAX_VMS];
/* Find VM index in vm_list */
static int
find_vm_index(mrb_state *mrb)
{
int i;
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
return i;
}
}
return -1;
}
/* Helper functions to maintain task counters and update timer */
static void
task_count_update(mrb_state *mrb, uint8_t old_status, uint8_t new_status)
{
int vm_idx = find_vm_index(mrb);
if (vm_idx < 0) return;
/* Decrement old queue counter */
if (old_status == MRB_TASK_STATUS_READY || old_status == MRB_TASK_STATUS_RUNNING) {
if (vm_ready_counts[vm_idx] > 0) {
vm_ready_counts[vm_idx]--;
}
}
else if (old_status == MRB_TASK_STATUS_WAITING) {
if (vm_waiting_counts[vm_idx] > 0) {
vm_waiting_counts[vm_idx]--;
}
}
/* Increment new queue counter */
if (new_status == MRB_TASK_STATUS_READY || new_status == MRB_TASK_STATUS_RUNNING) {
vm_ready_counts[vm_idx]++;
}
else if (new_status == MRB_TASK_STATUS_WAITING) {
vm_waiting_counts[vm_idx]++;
}
update_timer_state();
}
/* Check if timer should be enabled based on task counts */
static int
should_timer_be_enabled(void)
{
int i;
/* Timer needed if any VM has multiple ready tasks OR any waiting tasks */
for (i = 0; i < vm_count; i++) {
if (vm_ready_counts[i] > 1 || vm_waiting_counts[i] > 0) {
return 1;
}
}
return 0;
}
/* Multimedia timer callback - called periodically by Windows */
static void CALLBACK
timer_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
int i;
(void)uID; (void)uMsg; (void)dwUser; (void)dw1; (void)dw2;
/* Tick all registered VMs */
EnterCriticalSection(&irq_lock);
for (i = 0; i < vm_count; i++) {
if (vm_list[i]) {
mrb_tick(vm_list[i]);
}
}
LeaveCriticalSection(&irq_lock);
}
/* Platform-specific timer control */
static void
hal_set_timer_enabled(int enable)
{
if (enable) {
/* Enable timer - create multimedia timer if not already running */
if (timer_id == 0) {
/* Request 1ms timer resolution */
timeBeginPeriod(1);
/* Create periodic timer with MRB_TICK_UNIT interval */
timer_id = timeSetEvent(
MRB_TICK_UNIT, /* interval in milliseconds */
1, /* resolution in milliseconds */
timer_callback, /* callback function */
0, /* user data */
TIME_PERIODIC | TIME_KILL_SYNCHRONOUS
);
}
}
else {
/* Disable timer */
if (timer_id != 0) {
timeKillEvent(timer_id);
timeEndPeriod(1);
timer_id = 0;
}
}
}
/* Update timer state based on task counts */
static void
update_timer_state(void)
{
int needs_timer = should_timer_be_enabled();
/* Update timer only if state changed */
if (needs_timer && !timer_enabled) {
hal_set_timer_enabled(1);
InterlockedExchange(&timer_enabled, 1);
}
else if (!needs_timer && timer_enabled) {
hal_set_timer_enabled(0);
InterlockedExchange(&timer_enabled, 0);
}
}
void
mrb_task_hal_init(mrb_state *mrb)
{
int i;
LONG idx;
/* Initialize task state */
for (i = 0; i < 4; i++) {
mrb->task.queues[i] = NULL;
}
mrb->task.tick = 0;
mrb->task.wakeup_tick = UINT32_MAX;
mrb->task.switching = FALSE;
/* Initialize critical section on first VM */
if (vm_count == 0) {
InitializeCriticalSection(&irq_lock);
}
EnterCriticalSection(&irq_lock);
/* Check if this VM is already registered */
idx = -1;
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
idx = i;
break;
}
}
/* Register new VM if not already present */
if (idx < 0) {
if (vm_count >= MRB_TASK_MAX_VMS) {
LeaveCriticalSection(&irq_lock);
mrb_raisef(mrb, E_RUNTIME_ERROR,
"too many mrb_states with task scheduler (max: %d)",
MRB_TASK_MAX_VMS);
}
vm_list[vm_count] = mrb;
vm_ready_counts[vm_count] = 0;
vm_waiting_counts[vm_count] = 0;
InterlockedIncrement(&vm_count);
}
/* Update timer state based on current task queues */
update_timer_state();
LeaveCriticalSection(&irq_lock);
}
void
mrb_task_enable_irq(void)
{
LeaveCriticalSection(&irq_lock);
}
void
mrb_task_disable_irq(void)
{
EnterCriticalSection(&irq_lock);
}
void
mrb_task_hal_idle_cpu(mrb_state *mrb)
{
(void)mrb;
/* On Windows, just sleep briefly */
Sleep(MRB_TICK_UNIT);
}
void
mrb_task_hal_final(mrb_state *mrb)
{
int i, j;
EnterCriticalSection(&irq_lock);
/* Find and remove this VM from the list */
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
/* Shift remaining VMs and counters down */
for (j = i; j < vm_count - 1; j++) {
vm_list[j] = vm_list[j + 1];
vm_ready_counts[j] = vm_ready_counts[j + 1];
vm_waiting_counts[j] = vm_waiting_counts[j + 1];
}
vm_list[vm_count - 1] = NULL;
vm_ready_counts[vm_count - 1] = 0;
vm_waiting_counts[vm_count - 1] = 0;
InterlockedDecrement(&vm_count);
break;
}
}
/* Update timer state based on remaining VMs */
update_timer_state();
/* Cleanup critical section if last VM */
if (vm_count == 0) {
LeaveCriticalSection(&irq_lock);
DeleteCriticalSection(&irq_lock);
}
else {
LeaveCriticalSection(&irq_lock);
}
}
#else
/* Stub implementation for unsupported platforms */
/* WARNING: Task scheduler requires platform-specific timer implementation */
/* This stub allows compilation but task scheduling will NOT work correctly */
/* Stub for task_count_update */
static void
task_count_update(mrb_state *mrb, uint8_t old_status, uint8_t new_status)
{
(void)mrb;
(void)old_status;
(void)new_status;
/* Platform-specific task counting for timer control not implemented */
}
/* Platform-specific timer control stub */
static void
hal_set_timer_enabled(int enable)
{
(void)enable;
/* TODO: Platform-specific timer control */
}
/* Stub for update_timer_state */
static void
update_timer_state(void)
{
/* TODO: Implement timer state management */
}
static int
should_timer_be_enabled(void)
{
return 0; /* Stub: always return false */
}
void
mrb_task_hal_init(mrb_state *mrb)
{
int i;
/* Initialize task state */
for (i = 0; i < 4; i++) {
mrb->task.queues[i] = NULL;
}
mrb->task.tick = 0;
mrb->task.wakeup_tick = UINT32_MAX;
mrb->task.switching = FALSE;
/* TODO: Platform-specific timer initialization */
}
void
mrb_task_enable_irq(void)
{
/* TODO: Platform-specific interrupt enable */
}
void
mrb_task_disable_irq(void)
{
/* TODO: Platform-specific interrupt disable */
}
void
mrb_task_hal_idle_cpu(mrb_state *mrb)
{
(void)mrb;
/* TODO: Platform-specific idle/sleep */
}
void
mrb_task_hal_final(mrb_state *mrb)
{
(void)mrb;
/* TODO: Platform-specific cleanup */
}
#endif
/*
* Task class methods
*/
@@ -1303,7 +722,6 @@ mrb_task_s_new(mrb_state *mrb, mrb_value self)
/* Insert into ready queue */
mrb_task_disable_irq();
q_insert_task(mrb, t);
task_count_update(mrb, MRB_TASK_STATUS_DORMANT, MRB_TASK_STATUS_READY);
mrb_task_enable_irq();
/* Trigger context switch if this task has higher priority than current */
@@ -1685,12 +1103,10 @@ mrb_task_terminate(mrb_state *mrb, mrb_value self)
mrb_task_disable_irq();
/* Move to dormant queue */
uint8_t old_status = t->status;
q_delete_task(mrb, t);
t->status = MRB_TASK_STATUS_DORMANT;
t->c.status = MRB_TASK_STOPPED;
q_insert_task(mrb, t);
task_count_update(mrb, old_status, MRB_TASK_STATUS_DORMANT);
mrb_task_enable_irq();
@@ -1735,7 +1151,6 @@ mrb_task_join(mrb_state *mrb, mrb_value self)
current->reason = MRB_TASK_REASON_JOIN;
current->join = t;
q_insert_task(mrb, current);
task_count_update(mrb, MRB_TASK_STATUS_READY, MRB_TASK_STATUS_WAITING);
mrb_task_enable_irq();
/* Trigger context switch */