mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
be6413f0d8
Move hal-posix-task and hal-win-task into mruby-task/ports/posix/ and mruby-task/ports/win/. Remove HAL auto-detection logic from mrbgem.rake. Update cosmopolitan.rb to use conf.ports :posix instead of explicit hal-* gem references. Co-authored-by: Claude <noreply@anthropic.com>
235 lines
5.3 KiB
C
235 lines
5.3 KiB
C
/*
|
|
** 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
|
|
**
|
|
** Note: When compiled for Emscripten/WASM, the SIGALRM timer is disabled
|
|
** because JavaScript handles tick calls via setInterval. Using both would
|
|
** cause tick_ to increment twice as fast, making sleep wake up early.
|
|
*/
|
|
|
|
#include <mruby.h>
|
|
#include "task_hal.h"
|
|
#ifndef __EMSCRIPTEN__
|
|
#include <signal.h>
|
|
#include <sys/time.h>
|
|
#endif
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <stdint.h>
|
|
|
|
/* Time conversion constants */
|
|
#define NSEC_PER_MSEC 1000000ULL
|
|
#define NSEC_PER_SEC 1000000000ULL
|
|
#define USEC_PER_MSEC 1000ULL
|
|
|
|
#ifndef __EMSCRIPTEN__
|
|
/* 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]);
|
|
}
|
|
}
|
|
}
|
|
#endif /* __EMSCRIPTEN__ */
|
|
|
|
/*
|
|
* HAL Interface Implementation
|
|
*/
|
|
|
|
void
|
|
mrb_hal_task_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;
|
|
|
|
#ifndef __EMSCRIPTEN__
|
|
/* POSIX: Set up SIGALRM timer for tick handling */
|
|
struct sigaction sa;
|
|
struct itimerval timer;
|
|
int vm_index = -1;
|
|
|
|
/* 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);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
mrb_task_enable_irq(void)
|
|
{
|
|
#ifndef __EMSCRIPTEN__
|
|
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
mrb_task_disable_irq(void)
|
|
{
|
|
#ifndef __EMSCRIPTEN__
|
|
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
mrb_hal_task_idle_cpu(mrb_state *mrb)
|
|
{
|
|
(void)mrb;
|
|
/* On POSIX, just pause briefly */
|
|
usleep(MRB_TICK_UNIT * 1000);
|
|
}
|
|
|
|
void
|
|
mrb_hal_task_sleep_us(mrb_state *mrb, mrb_int usec)
|
|
{
|
|
struct timespec start, now, sleep_time;
|
|
int ret;
|
|
|
|
(void)mrb;
|
|
|
|
/* Validate input to prevent overflow */
|
|
if (usec < 0) {
|
|
return;
|
|
}
|
|
|
|
ret = clock_gettime(CLOCK_MONOTONIC, &start);
|
|
if (ret != 0) {
|
|
/* Fallback to simple usleep if clock_gettime fails */
|
|
usleep(usec);
|
|
return;
|
|
}
|
|
|
|
uint64_t target_ns = (uint64_t)usec * USEC_PER_MSEC;
|
|
|
|
/* Loop until enough real time has elapsed */
|
|
while (1) {
|
|
ret = clock_gettime(CLOCK_MONOTONIC, &now);
|
|
if (ret != 0) {
|
|
break; /* Clock failure - exit loop */
|
|
}
|
|
|
|
uint64_t elapsed_ns = (uint64_t)(now.tv_sec - start.tv_sec) * NSEC_PER_SEC +
|
|
(uint64_t)(now.tv_nsec - start.tv_nsec);
|
|
|
|
if (elapsed_ns >= target_ns) {
|
|
break;
|
|
}
|
|
|
|
/* Sleep for remaining time, but at least 1ms to allow timer interrupts */
|
|
uint64_t remaining_ns = target_ns - elapsed_ns;
|
|
if (remaining_ns > NSEC_PER_MSEC) {
|
|
sleep_time.tv_sec = remaining_ns / NSEC_PER_SEC;
|
|
sleep_time.tv_nsec = remaining_ns % NSEC_PER_SEC;
|
|
}
|
|
else {
|
|
sleep_time.tv_sec = 0;
|
|
sleep_time.tv_nsec = NSEC_PER_MSEC;
|
|
}
|
|
|
|
nanosleep(&sleep_time, NULL); /* Interrupted by signals - that's OK */
|
|
}
|
|
}
|
|
|
|
void
|
|
mrb_hal_task_final(mrb_state *mrb)
|
|
{
|
|
#ifndef __EMSCRIPTEN__
|
|
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);
|
|
#else
|
|
/* WASM: No timer cleanup needed */
|
|
(void)mrb;
|
|
#endif
|
|
}
|