mirror of
https://gitee.com/Lamdonn/varch.git
synced 2026-10-28 18:07:39 +08:00
Update coroutine
1. Compatibility with coroutine API highly intensive IO, sockets, etc 2. Fix running fail on arm64 3. Add low power configuration hook TODO: 1. Add a scheduling ready task queue mechanism 2. Add CoTask communication channel
This commit is contained in:
parent
af9120c9f8
commit
c0a7f18093
1
makefile
1
makefile
@ -68,6 +68,7 @@ ifeq ($(OS),Windows_NT)
|
||||
UNAME := Windows
|
||||
else
|
||||
UNAME := $(shell uname -s)
|
||||
CFLAG += -D_GNU_SOURCE
|
||||
endif
|
||||
|
||||
##################################################################################
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -86,6 +86,21 @@ typedef void (*CoFree_t)(void *block);
|
||||
*/
|
||||
typedef uint64_t (*CoTick_t)(void);
|
||||
|
||||
/**
|
||||
* \brief: Coroutine scheduler idle hook function pointer
|
||||
* \param schedulerId: Current scheduler ID
|
||||
* \param idleUs: Estimated idle duration in microseconds until the earliest task or timer wake-up (0 if unknown or none)
|
||||
* \note: Can be used to execute WFI on MCU, epoll_wait on Linux, or custom sleep/reactor logic.
|
||||
*/
|
||||
typedef void (*CoIdleHook_t)(uint32_t schedulerId, uint32_t idleUs);
|
||||
|
||||
/**
|
||||
* \brief: Coroutine scheduler wakeup hook function pointer
|
||||
* \param schedulerId: Target scheduler ID to wake up
|
||||
* \note: Used to unblock or interrupt an in-progress idleHook (e.g. pthread_cond_signal, eventfd write, SetEvent)
|
||||
*/
|
||||
typedef void (*CoWakeupHook_t)(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Coroutine event
|
||||
* \note: Used for synchronization between coroutines
|
||||
@ -95,16 +110,26 @@ typedef struct
|
||||
uint32_t flag; /**< Event flag, 32 bit storage, each bit can individually represent an event */
|
||||
} CoEvent;
|
||||
|
||||
/**
|
||||
* \brief: Coroutine global runtime environment parameters
|
||||
* \note: Configures global dynamic memory allocators and thread synchronization locks
|
||||
*/
|
||||
typedef struct CoroutineInitPara
|
||||
{
|
||||
CoLock_t lock; /**< Global coroutine thread lock function */
|
||||
CoUnlock_t unlock; /**< Global coroutine thread unlock function */
|
||||
CoMalloc_t malloc; /**< Global dynamic memory allocator function */
|
||||
CoFree_t free; /**< Global dynamic memory free function */
|
||||
} CoroutineInitPara;
|
||||
|
||||
/**
|
||||
* \brief: Coroutine scheduler initialize parameter
|
||||
* \note: Coroutine scheduler initialize parameter is used to initialize the coroutine scheduler.
|
||||
* \note: Coroutine scheduler initialize parameter is used to initialize the coroutine scheduler instance.
|
||||
*/
|
||||
typedef struct CoSchedulerInitPara
|
||||
{
|
||||
CoLock_t lock; /**< Coroutine scheduler lock function */
|
||||
CoUnlock_t unlock; /**< Coroutine scheduler unlock function */
|
||||
CoMalloc_t malloc; /**< Coroutine scheduler malloc function */
|
||||
CoFree_t free; /**< Coroutine scheduler free function */
|
||||
CoIdleHook_t idleHook; /**< Coroutine scheduler idle hook function */
|
||||
CoWakeupHook_t wakeupHook; /**< Coroutine scheduler wakeup hook function */
|
||||
} CoSchedulerInitPara;
|
||||
|
||||
/**
|
||||
@ -117,6 +142,8 @@ typedef struct CoTaskCreatePara
|
||||
size_t stackSize; /**< Coroutine task stack size */
|
||||
void *arg; /**< Coroutine task argument */
|
||||
int schedulerId; /**< Coroutine task scheduler id */
|
||||
uint8_t manual; /**< 1: Manual control mode (libco style, CoScheduler will not auto-run), 0: Auto scheduled */
|
||||
uint8_t sharedStack; /**< 1: Use shared stack (requires COROUTINE_SHARED_STACK_SIZE > 0), 0: Dedicated stack */
|
||||
} CoTaskCreatePara;
|
||||
|
||||
/**
|
||||
@ -159,43 +186,73 @@ typedef struct CoTaskWaitPara
|
||||
*/
|
||||
#define COEVENT_STATIC_VALUE ((CoEvent){.flag=0})
|
||||
|
||||
/**
|
||||
* \brief: Coroutine global runtime environment initialize function
|
||||
* \param lock: [Default parameter] Coroutine thread lock function,
|
||||
* for use on multiple threads, you must specify as follows .lock=thread_lock
|
||||
* \param unlock: [Default parameter] Coroutine thread unlock function,
|
||||
* for use on multiple threads, you must specify as follows .unlock=thread_unlock
|
||||
* \param malloc: [Default parameter] Coroutine dynamic memory allocator function,
|
||||
* if you need to allocate memory dynamically (shared stack, CoTask, etc),
|
||||
* you must specify as follows .malloc=malloc
|
||||
* \param free: [Default parameter] Coroutine dynamic memory free function,
|
||||
* if you need to free memory dynamically (shared stack, CoTask, etc),
|
||||
* you must specify as follows .free=free
|
||||
* \return: COROUTINE_E_OK on success, or negative error code
|
||||
* \note: Configures global runtime infrastructure (memory hooks and thread locks) independently of specific schedulers.
|
||||
* It is recommended to call this once at module or process startup.
|
||||
* \example:
|
||||
* Coroutine_Init(.malloc=malloc, .free=free);
|
||||
* \example:
|
||||
* Coroutine_Init(.lock=thread_lock, .unlock=thread_unlock, .malloc=malloc, .free=free);
|
||||
*/
|
||||
#define Coroutine_Init(...) Coroutine_InitP((CoroutineInitPara[1]){[0]={.lock=NULL,.unlock=NULL,.malloc=NULL,.free=NULL,__VA_ARGS__}})
|
||||
|
||||
/**
|
||||
* \brief: Coroutine global runtime environment initialize function, it is recommended to use @ref `Coroutine_Init` instead
|
||||
* \param pPara: Coroutine global environment parameter pointer
|
||||
* \return: COROUTINE_E_OK on success, or negative error code
|
||||
* \note: Configures global runtime infrastructure independently of specific scheduler instances.
|
||||
*/
|
||||
int Coroutine_InitP(CoroutineInitPara *pPara);
|
||||
|
||||
/**
|
||||
* \brief: Check if global dynamic memory hooks (malloc and free) are configured
|
||||
* \return: 1 if dynamic memory hooks are configured, 0 if in pure static mode
|
||||
*/
|
||||
int Coroutine_HasMemHook(void);
|
||||
|
||||
/**
|
||||
* \brief: Allocate memory using the configured coroutine dynamic allocator
|
||||
* \param size: Number of bytes to allocate
|
||||
* \return: Pointer to allocated memory, or NULL if not configured or allocation failed
|
||||
*/
|
||||
void *Coroutine_Malloc(size_t size);
|
||||
|
||||
/**
|
||||
* \brief: Free memory using the configured coroutine dynamic deallocator
|
||||
* \param ptr: Pointer to memory to free
|
||||
*/
|
||||
void Coroutine_Free(void *ptr);
|
||||
|
||||
/**
|
||||
* \brief: Coroutine scheduler initialize function
|
||||
* \param pScheduler: [Non-default parameter] Coroutine scheduler pointer
|
||||
* \param CoSchedulerId: Coroutine scheduler ID
|
||||
* \param tick: [Non-default parameter] Coroutine scheduler tick function, must be specified
|
||||
* \param tickInterval: [Non-default parameter] Coroutine scheduler tick interval, indicates how many ns each tick is, must be specified
|
||||
* \param lock: [Default parameter] Coroutine scheduler lock function,
|
||||
* for use on multiple threads, you must specify as follows .lock=thread_lock
|
||||
* \param unlock: [Default parameter] Coroutine scheduler unlock function,
|
||||
* for use on multiple threads, you must specify as follows .unlock=thread_unlock
|
||||
* \param malloc: [Default parameter] Coroutine scheduler malloc function,
|
||||
* if you need to allocate memory dynamically (CoTask, CoTimer, stack, etc),
|
||||
* you must specify as follows .malloc=mallloc
|
||||
* \param free: [Default parameter] Coroutine scheduler free function,
|
||||
* if you need to free memory dynamically (CoTask, CoTimer, stack, etc),
|
||||
* you must specify as follows .free=free
|
||||
* \param idleHook: [Default parameter] Coroutine scheduler idle hook function,
|
||||
* you can specify as follows .idleHook=my_idle_hook
|
||||
* \return: Coroutine task create parameter default value, indicates success
|
||||
* \note: Coroutine scheduler initialize function is used to initialize the coroutine scheduler.
|
||||
* It must be called once before any other coroutine scheduler function.
|
||||
* Only one initialization is allowed within a single thread
|
||||
* It will initialize the coroutine scheduler with the given parameter.
|
||||
* \note: Coroutine scheduler initialize function is used to initialize the coroutine scheduler instance.
|
||||
* It must be called once before any other coroutine scheduler function for that scheduler.
|
||||
* The coroutine scheduler will use the given tick function to measure the load of each task.
|
||||
* The tick function must be called periodically with the given tick interval.
|
||||
* The coroutine scheduler will use the given lock function to protect the shared resources.
|
||||
* The coroutine scheduler will use the given malloc function to allocate memory for the tasks.
|
||||
* The coroutine scheduler will use the given free function to free memory of the tasks.
|
||||
* \warning: The coroutine scheduler will not check the validity of the given parameter.
|
||||
* You must ensure that the given parameter is valid.
|
||||
* \example:
|
||||
* CoScheduler_Init(0, GetTimerMs, 1000000);
|
||||
* \example:
|
||||
* CoScheduler_Init(0, GetTimerUsec, 1000);
|
||||
* \example:
|
||||
* CoScheduler_Init(0, GetTimerUsec, 1000, .lock=thread_lock, .unlock=thread_unlock);
|
||||
* \example:
|
||||
* CoScheduler_Init(0, GetTimerUsec, 1000, .malloc=mallloc, .free=free);
|
||||
* CoScheduler_Init(0, GetTimerUsec, 1000, .idleHook=my_idle_hook, .wakeupHook=my_wakeup_hook);
|
||||
*/
|
||||
#define CoScheduler_Init(CoSchedulerId, tick, tickInterval, ...) CoScheduler_InitP(CoSchedulerId, tick, tickInterval, (CoSchedulerInitPara[1]){[0]={.lock=NULL,.unlock=NULL,.malloc=NULL,.free=NULL,__VA_ARGS__}})
|
||||
#define CoScheduler_Init(CoSchedulerId, tick, tickInterval, ...) CoScheduler_InitP(CoSchedulerId, tick, tickInterval, (CoSchedulerInitPara[1]){[0]={.idleHook=NULL,.wakeupHook=NULL,__VA_ARGS__}})
|
||||
|
||||
/**
|
||||
* \brief: Coroutine scheduler initialize function
|
||||
@ -216,6 +273,31 @@ typedef struct CoTaskWaitPara
|
||||
*/
|
||||
int CoScheduler_InitP(uint32_t CoSchedulerId, CoTick_t tick, uint32_t tickInterval, CoSchedulerInitPara *pPara);
|
||||
|
||||
/**
|
||||
* \brief: Set the coroutine scheduler idle hook function
|
||||
* \param CoSchedulerId: Coroutine scheduler ID
|
||||
* \param idleHook: User-defined idle callback
|
||||
* \return: COROUTINE_E_OK on success, or negative error code
|
||||
* \note: When there are no runnable tasks, the scheduler invokes this hook with the estimated idle time (us).
|
||||
*/
|
||||
int CoScheduler_SetIdleHook(uint32_t CoSchedulerId, CoIdleHook_t idleHook);
|
||||
|
||||
/**
|
||||
* \brief: Set the coroutine scheduler wakeup hook function
|
||||
* \param CoSchedulerId: Coroutine scheduler ID
|
||||
* \param wakeupHook: User-defined wakeup callback to unblock idleHook
|
||||
* \return: COROUTINE_E_OK on success, or negative error code
|
||||
* \note: Invoked whenever an event is notified or task is woken up to immediately interrupt an in-progress idle sleep.
|
||||
*/
|
||||
int CoScheduler_SetWakeupHook(uint32_t CoSchedulerId, CoWakeupHook_t wakeupHook);
|
||||
|
||||
/**
|
||||
* \brief: Wake up a specific coroutine scheduler, unblocking its idleHook immediately
|
||||
* \param CoSchedulerId: Coroutine scheduler ID
|
||||
* \return: COROUTINE_E_OK on success, or negative error code
|
||||
*/
|
||||
int CoScheduler_Wakeup(uint32_t CoSchedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Coroutine scheduler start function
|
||||
* \param pScheduler: Coroutine scheduler pointer
|
||||
@ -304,7 +386,7 @@ uint16_t CoScheduler_MaxLoad(uint32_t CoSchedulerId);
|
||||
* int arg = 4096;
|
||||
* CoTask_t testCoroutine = CoTask_Create(test, .arg=&arg);
|
||||
*/
|
||||
#define CoTask_Create(entry, ...) CoTask_CreateP(entry, (CoTaskCreatePara[1]){[0]={.pStack=NULL,.stackSize=0,.arg=NULL,.schedulerId=-1,__VA_ARGS__}})
|
||||
#define CoTask_Create(entry, ...) CoTask_CreateP(entry, (CoTaskCreatePara[1]){[0]={.pStack=NULL,.stackSize=0,.arg=NULL,.schedulerId=-1,.manual=0,.sharedStack=0,__VA_ARGS__}})
|
||||
|
||||
/**
|
||||
* \brief: Coroutine task create function, it is recommended to use @ref `CoTask_Create` instead
|
||||
@ -334,6 +416,32 @@ int CoTask_Delete(CoTask_t CoTask);
|
||||
*/
|
||||
CoTask_t CoTask_Self(void);
|
||||
|
||||
/**
|
||||
* \brief: Explicitly resume/step into a specific manual coroutine task (point-to-point libco style)
|
||||
* \param task: Coroutine task handle (must be created with .manual = 1)
|
||||
* \return: COROUTINE_E_OK (0) if task yielded back, 1 if task finished execution, or negative error code (e.g. COROUTINE_E_INVALID_PARAMETER if not a manual task)
|
||||
* \note: This allows running and stepping manual coroutines directly without running CoScheduler_Start.
|
||||
* \example:
|
||||
* CoTask_t co = CoTask_Create(my_task, .manual = 1);
|
||||
* CoTask_Resume(co);
|
||||
*/
|
||||
int CoTask_Resume(CoTask_t task);
|
||||
|
||||
/**
|
||||
* \brief: Wake up a suspended coroutine managed by the central scheduler, marking it immediately ready
|
||||
* \param task: Coroutine task handle
|
||||
* \return: COROUTINE_E_OK (0) on success, or negative error code
|
||||
* \note: Clears remaining sleep ticks, allowing the central scheduler to execute it in the next scheduling pass.
|
||||
*/
|
||||
int CoTask_Wakeup(CoTask_t task);
|
||||
|
||||
/**
|
||||
* \brief: Explicitly yield the current coroutine task (equivalent to CoTask_Wait(), libco style)
|
||||
* \note: In manual/direct mode (resumed via CoTask_Resume), yields directly back to CoTask_Resume's caller.
|
||||
* In scheduler mode (managed by CoScheduler_Start), yields time-slice to other tasks.
|
||||
*/
|
||||
#define CoTask_Yield() CoTask_Wait()
|
||||
|
||||
/**
|
||||
* \brief: Coroutine task scheduler id function
|
||||
* \return: Coroutine task scheduler id, indicates the scheduler id of the coroutine task
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
* \brief Coroutine scheduler number
|
||||
* \note This macro defines the number of coroutine schedulers
|
||||
*/
|
||||
#define COROUTINE_SCHEDULER_NUMBER 1
|
||||
#define COROUTINE_SCHEDULER_NUMBER 2
|
||||
|
||||
/**
|
||||
* \brief Coroutine static task max number
|
||||
@ -31,12 +31,6 @@
|
||||
*/
|
||||
#define COROUTINE_STATIC_TIMER_MAX_NUMBER 8
|
||||
|
||||
/**
|
||||
* \brief Coroutine static stack max number
|
||||
* \note This macro defines the maximum number of coroutine stacks that can be created statically
|
||||
*/
|
||||
#define COROUTINE_STATIC_STACK_MAX_NUMBER 8
|
||||
|
||||
/**
|
||||
* \brief Coroutine stack default size
|
||||
* \note This macro defines the default size of the coroutine stack
|
||||
@ -72,101 +66,10 @@
|
||||
#define COROUTINE_LOADING_CALCULATE_PERIOD 100
|
||||
|
||||
/**
|
||||
* \brief Stack get and set Pointers depending on the platform definition
|
||||
* \param p Pointer to the stack
|
||||
* \return None
|
||||
* \note This macro is used to get and set the stack pointer depending on the platform definition
|
||||
* \note For x86_32, the stack pointer is esp; for x86_64, the stack pointer is rsp; for arm, the stack pointer is sp
|
||||
* \note Common platforms have been implemented, not implemented platforms need to be implemented by users
|
||||
* \brief Size of the physical shared stack buffer per scheduler
|
||||
* \note 0: Disable shared stack (dedicated stacks only, zero memory copying overhead)
|
||||
* > 0: Enable shared stack with specified buffer size in bytes (e.g. 128 * 1024)
|
||||
*/
|
||||
#if defined(__i386__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%esp, %0" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%esp" : : "r" (p) : "memory")
|
||||
#elif defined(__x86_64__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%rsp, %0" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%rsp" : : "r" (p) : "memory")
|
||||
#elif defined(__arm__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__aarch64__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__mips__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile(".set noreorder\n\t" "move %0, $sp\n\t" ".set reorder" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile(".set noreorder\n\t" "move $sp, %0\n\t" ".set reorder" : : "r" (p) : "memory")
|
||||
#elif defined(__riscv__)
|
||||
#if __riscv_xlen == 32
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mv %0, sp" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mv sp, %0" : : "r" (p) : "memory")
|
||||
#else
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mv %0, sp" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mv sp, %0" : : "r" (p) : "memory")
|
||||
#endif
|
||||
#elif defined(__powerpc__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__powerpc64__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__s390__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__s390x__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__sparc__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory")
|
||||
#elif defined(__sparcv9__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory")
|
||||
#elif defined(__tile__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory")
|
||||
#elif defined(__tilegx__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory")
|
||||
#elif defined(__hppa__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__hppa64__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__ia64__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0=sp" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp=%0" : : "r" (p) : "memory")
|
||||
#elif defined(__loongarch__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__m68k__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__mips64__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__parisc__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__parisc64__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__s390__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__s390x__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__sh__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__sh64__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory")
|
||||
#elif defined(__xtensa__)
|
||||
#define COROUTINE_GET_SP(p) __asm__ volatile("rsr.a1 %0" : "=r" (p) : : "memory")
|
||||
#define COROUTINE_SET_SP(p) __asm__ volatile("wsr.a1 %0" : : "r" (p) : "memory")
|
||||
#else
|
||||
#error "Unsupported platform, please implement `COROUTINE_GET_SP` and `COROUTINE_SET_SP`"
|
||||
#endif
|
||||
#define COROUTINE_SHARED_STACK_SIZE (128 * 1024)
|
||||
|
||||
#endif // !__coroutine_cfg_H
|
||||
|
||||
469
source/08_coroutine/coroutine_hook.c
Normal file
469
source/08_coroutine/coroutine_hook.c
Normal file
@ -0,0 +1,469 @@
|
||||
/*********************************************************************************************************
|
||||
* ------------------------------------------------------------------------------------------------------
|
||||
* file description
|
||||
* ------------------------------------------------------------------------------------------------------
|
||||
* \file coroutine_hook.c
|
||||
* \unit coroutine
|
||||
* \brief Predefined idleHook and wakeupHook implementations for Bare-Metal, RTOS and OS platforms
|
||||
* \author Lamdonn
|
||||
* \version v0.2.0
|
||||
* \license GPL-2.0
|
||||
* \copyright Copyright (C) 2025 Lamdonn.
|
||||
********************************************************************************************************/
|
||||
#include "coroutine_hook.h"
|
||||
#include "coroutine.h"
|
||||
|
||||
/* =======================================================================================================
|
||||
* 1. Bare-metal / Embedded MCU Hooks
|
||||
* ======================================================================================================= */
|
||||
|
||||
void CoHook_Baremetal_Idle(uint32_t schedulerId, uint32_t idleUs)
|
||||
{
|
||||
(void)schedulerId;
|
||||
(void)idleUs;
|
||||
#if defined(__arm__) || defined(__aarch64__)
|
||||
__asm__ volatile ("wfi");
|
||||
#elif defined(__riscv__) || defined(__riscv)
|
||||
__asm__ volatile ("wfi");
|
||||
#elif defined(__xtensa__)
|
||||
__asm__ volatile ("waiti 0");
|
||||
#elif defined(__CSKY__)
|
||||
__asm__ volatile ("wait");
|
||||
#elif defined(__MSP430__)
|
||||
__asm__ volatile ("bis #0x0018, r2");
|
||||
#elif defined(__ARC__) || defined(__arc__)
|
||||
__asm__ volatile ("sleep");
|
||||
#elif defined(__mips__) || defined(__mips64__)
|
||||
__asm__ volatile ("wait");
|
||||
#elif defined(__loongarch__)
|
||||
__asm__ volatile ("idle 0");
|
||||
#elif defined(__sh__) || defined(__sh64__)
|
||||
__asm__ volatile ("sleep");
|
||||
#elif defined(__m68k__)
|
||||
__asm__ volatile ("stop #0x2000");
|
||||
#elif defined(__AVR__)
|
||||
__asm__ volatile ("sleep");
|
||||
#elif defined(__powerpc__) || defined(__powerpc64__)
|
||||
__asm__ volatile ("or 31, 31, 31");
|
||||
#elif defined(__i386__) || defined(__x86_64__)
|
||||
__asm__ volatile ("pause");
|
||||
#else
|
||||
/* Freestanding CPU architecture fallback: no-op */
|
||||
__asm__ volatile ("");
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoHook_Baremetal_Wakeup(uint32_t schedulerId)
|
||||
{
|
||||
(void)schedulerId;
|
||||
#if defined(__arm__) || defined(__aarch64__)
|
||||
__asm__ volatile ("sev");
|
||||
#elif defined(__MSP430__)
|
||||
__asm__ volatile ("bic #0x0018, 0(r1)");
|
||||
#else
|
||||
/* Handled automatically by hardware interrupt return (NVIC/CLIC/PLIC) */
|
||||
__asm__ volatile ("");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* =======================================================================================================
|
||||
* 2. FreeRTOS Hooks (Task Notification based)
|
||||
* ======================================================================================================= */
|
||||
#if defined(INC_FREERTOS_H) || defined(COROUTINE_HOOK_FREERTOS)
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
|
||||
static TaskHandle_t s_freertos_tasks[COROUTINE_SCHEDULER_NUMBER] = {0};
|
||||
|
||||
int CoHook_FreeRTOS_Init(uint32_t schedulerId, void *taskHandle)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return COROUTINE_E_INVALID_PARAMETER;
|
||||
s_freertos_tasks[schedulerId] = (taskHandle != NULL) ? (TaskHandle_t)taskHandle : xTaskGetCurrentTaskHandle();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CoHook_FreeRTOS_Deinit(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER)
|
||||
{
|
||||
s_freertos_tasks[schedulerId] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CoHook_FreeRTOS_Idle(uint32_t schedulerId, uint32_t idleUs)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return;
|
||||
TickType_t xTicks = (idleUs == 0) ? portMAX_DELAY : (TickType_t)((uint64_t)idleUs * configTICK_RATE_HZ / 1000000ULL);
|
||||
if (xTicks == 0 && idleUs > 0) xTicks = 1;
|
||||
ulTaskNotifyTake(pdTRUE, xTicks);
|
||||
}
|
||||
|
||||
void CoHook_FreeRTOS_Wakeup(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_freertos_tasks[schedulerId] != NULL)
|
||||
{
|
||||
xTaskNotifyGive(s_freertos_tasks[schedulerId]);
|
||||
}
|
||||
}
|
||||
#else
|
||||
int CoHook_FreeRTOS_Init(uint32_t schedulerId, void *taskHandle) { (void)schedulerId; (void)taskHandle; return -1; }
|
||||
void CoHook_FreeRTOS_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; }
|
||||
void CoHook_FreeRTOS_Wakeup(uint32_t schedulerId) { (void)schedulerId; }
|
||||
void CoHook_FreeRTOS_Deinit(uint32_t schedulerId) { (void)schedulerId; }
|
||||
#endif
|
||||
|
||||
/* =======================================================================================================
|
||||
* 3. RT-Thread Hooks (rt_sem semaphore based)
|
||||
* ======================================================================================================= */
|
||||
#if defined(RT_TICK_PER_SECOND) || defined(COROUTINE_HOOK_RTTHREAD)
|
||||
#include <rtthread.h>
|
||||
|
||||
static rt_sem_t s_rtt_sems[COROUTINE_SCHEDULER_NUMBER] = {0};
|
||||
|
||||
int CoHook_RTThread_Init(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return COROUTINE_E_INVALID_PARAMETER;
|
||||
if (s_rtt_sems[schedulerId] == RT_NULL)
|
||||
{
|
||||
s_rtt_sems[schedulerId] = rt_sem_create("co_sem", 0, RT_IPC_FLAG_PRIO);
|
||||
if (s_rtt_sems[schedulerId] == RT_NULL) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CoHook_RTThread_Deinit(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_rtt_sems[schedulerId] != RT_NULL)
|
||||
{
|
||||
rt_sem_delete(s_rtt_sems[schedulerId]);
|
||||
s_rtt_sems[schedulerId] = RT_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CoHook_RTThread_Idle(uint32_t schedulerId, uint32_t idleUs)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER || s_rtt_sems[schedulerId] == RT_NULL) return;
|
||||
rt_int32_t ticks = (idleUs == 0) ? RT_WAITING_FOREVER : (rt_int32_t)((uint64_t)idleUs * RT_TICK_PER_SECOND / 1000000ULL);
|
||||
if (ticks == 0 && idleUs > 0) ticks = 1;
|
||||
rt_sem_take(s_rtt_sems[schedulerId], ticks);
|
||||
}
|
||||
|
||||
void CoHook_RTThread_Wakeup(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_rtt_sems[schedulerId] != RT_NULL)
|
||||
{
|
||||
rt_sem_release(s_rtt_sems[schedulerId]);
|
||||
}
|
||||
}
|
||||
#else
|
||||
int CoHook_RTThread_Init(uint32_t schedulerId) { (void)schedulerId; return -1; }
|
||||
void CoHook_RTThread_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; }
|
||||
void CoHook_RTThread_Wakeup(uint32_t schedulerId) { (void)schedulerId; }
|
||||
void CoHook_RTThread_Deinit(uint32_t schedulerId) { (void)schedulerId; }
|
||||
#endif
|
||||
|
||||
/* =======================================================================================================
|
||||
* 4. CMSIS-RTOS2 Hooks (ARM Standard - Keil RTX5, FreeRTOS-CMSIS wrapper, etc.)
|
||||
* ======================================================================================================= */
|
||||
#if defined(CMSIS_OS2_H_) || defined(COROUTINE_HOOK_CMSIS_RTOS2)
|
||||
#include "cmsis_os2.h"
|
||||
|
||||
static osThreadId_t s_cmsis_threads[COROUTINE_SCHEDULER_NUMBER] = {0};
|
||||
|
||||
int CoHook_CMSIS_Init(uint32_t schedulerId, void *threadId)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return COROUTINE_E_INVALID_PARAMETER;
|
||||
s_cmsis_threads[schedulerId] = (threadId != NULL) ? (osThreadId_t)threadId : osThreadGetId();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CoHook_CMSIS_Deinit(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER)
|
||||
{
|
||||
s_cmsis_threads[schedulerId] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CoHook_CMSIS_Idle(uint32_t schedulerId, uint32_t idleUs)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return;
|
||||
uint32_t millis = (idleUs == 0) ? osWaitForever : (uint32_t)((idleUs + 999) / 1000);
|
||||
osThreadFlagsWait(0x01, osFlagsWaitAny, millis);
|
||||
}
|
||||
|
||||
void CoHook_CMSIS_Wakeup(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_cmsis_threads[schedulerId] != NULL)
|
||||
{
|
||||
osThreadFlagsSet(s_cmsis_threads[schedulerId], 0x01);
|
||||
}
|
||||
}
|
||||
#else
|
||||
int CoHook_CMSIS_Init(uint32_t schedulerId, void *threadId) { (void)schedulerId; (void)threadId; return -1; }
|
||||
void CoHook_CMSIS_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; }
|
||||
void CoHook_CMSIS_Wakeup(uint32_t schedulerId) { (void)schedulerId; }
|
||||
void CoHook_CMSIS_Deinit(uint32_t schedulerId) { (void)schedulerId; }
|
||||
#endif
|
||||
|
||||
/* =======================================================================================================
|
||||
* 5. uC/OS-III Hooks (Task Semaphore based)
|
||||
* ======================================================================================================= */
|
||||
#if defined(OS_CFG_H) || defined(COROUTINE_HOOK_UCOS)
|
||||
#include <os.h>
|
||||
|
||||
static OS_TCB *s_ucos_tcbs[COROUTINE_SCHEDULER_NUMBER] = {0};
|
||||
|
||||
int CoHook_UCOS_Init(uint32_t schedulerId, void *pTcb)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return COROUTINE_E_INVALID_PARAMETER;
|
||||
s_ucos_tcbs[schedulerId] = (pTcb != NULL) ? (OS_TCB *)pTcb : OSTCBCurPtr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CoHook_UCOS_Deinit(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER)
|
||||
{
|
||||
s_ucos_tcbs[schedulerId] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CoHook_UCOS_Idle(uint32_t schedulerId, uint32_t idleUs)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return;
|
||||
OS_ERR err;
|
||||
CPU_TS ts;
|
||||
OS_TICK dly = (idleUs == 0) ? 0 : (OS_TICK)((uint64_t)idleUs * OSCfg_TickRate_Hz / 1000000ULL);
|
||||
if (dly == 0 && idleUs > 0) dly = 1;
|
||||
OSTaskSemPend(dly, OS_OPT_PEND_BLOCKING, &ts, &err);
|
||||
}
|
||||
|
||||
void CoHook_UCOS_Wakeup(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_ucos_tcbs[schedulerId] != NULL)
|
||||
{
|
||||
OS_ERR err;
|
||||
OSTaskSemPost(s_ucos_tcbs[schedulerId], OS_OPT_POST_NONE, &err);
|
||||
}
|
||||
}
|
||||
#else
|
||||
int CoHook_UCOS_Init(uint32_t schedulerId, void *pTcb) { (void)schedulerId; (void)pTcb; return -1; }
|
||||
void CoHook_UCOS_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; }
|
||||
void CoHook_UCOS_Wakeup(uint32_t schedulerId) { (void)schedulerId; }
|
||||
void CoHook_UCOS_Deinit(uint32_t schedulerId) { (void)schedulerId; }
|
||||
#endif
|
||||
|
||||
/* =======================================================================================================
|
||||
* 6. Windows Implementation
|
||||
* ======================================================================================================= */
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
|
||||
static HANDLE s_win32_events[COROUTINE_SCHEDULER_NUMBER] = {0};
|
||||
|
||||
int CoHook_Win32_Init(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER)
|
||||
{
|
||||
return COROUTINE_E_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (s_win32_events[schedulerId] == NULL)
|
||||
{
|
||||
s_win32_events[schedulerId] = CreateEventA(NULL, FALSE, FALSE, NULL); // Auto-reset event
|
||||
if (s_win32_events[schedulerId] == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CoHook_Win32_Deinit(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_win32_events[schedulerId] != NULL)
|
||||
{
|
||||
CloseHandle(s_win32_events[schedulerId]);
|
||||
s_win32_events[schedulerId] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CoHook_Win32_Idle(uint32_t schedulerId, uint32_t idleUs)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_win32_events[schedulerId] != NULL)
|
||||
{
|
||||
DWORD ms = (idleUs == 0) ? INFINITE : (DWORD)((idleUs + 999) / 1000);
|
||||
WaitForSingleObject(s_win32_events[schedulerId], ms);
|
||||
}
|
||||
}
|
||||
|
||||
void CoHook_Win32_Wakeup(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_win32_events[schedulerId] != NULL)
|
||||
{
|
||||
SetEvent(s_win32_events[schedulerId]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* =======================================================================================================
|
||||
* 7. POSIX / Linux / Android / macOS / iOS / QNX / VxWorks / NuttX Implementation
|
||||
* ======================================================================================================= */
|
||||
#if defined(__linux__) || defined(__unix__) || defined(__APPLE__) || defined(__QNX__) || defined(__QNXNTO__) || defined(__ANDROID__) || defined(__VXWORKS__) || defined(__NuttX__)
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
static pthread_mutex_t s_posix_mutex[COROUTINE_SCHEDULER_NUMBER];
|
||||
static pthread_cond_t s_posix_cond[COROUTINE_SCHEDULER_NUMBER];
|
||||
static uint8_t s_posix_inited[COROUTINE_SCHEDULER_NUMBER] = {0};
|
||||
|
||||
int CoHook_Posix_Init(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER)
|
||||
{
|
||||
return COROUTINE_E_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!s_posix_inited[schedulerId])
|
||||
{
|
||||
pthread_mutex_init(&s_posix_mutex[schedulerId], NULL);
|
||||
pthread_cond_init(&s_posix_cond[schedulerId], NULL);
|
||||
s_posix_inited[schedulerId] = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CoHook_Posix_Deinit(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_posix_inited[schedulerId])
|
||||
{
|
||||
pthread_mutex_destroy(&s_posix_mutex[schedulerId]);
|
||||
pthread_cond_destroy(&s_posix_cond[schedulerId]);
|
||||
s_posix_inited[schedulerId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CoHook_Posix_Idle(uint32_t schedulerId, uint32_t idleUs)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER || !s_posix_inited[schedulerId]) return;
|
||||
|
||||
pthread_mutex_lock(&s_posix_mutex[schedulerId]);
|
||||
if (idleUs == 0)
|
||||
{
|
||||
pthread_cond_wait(&s_posix_cond[schedulerId], &s_posix_mutex[schedulerId]);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
uint64_t nsec = (uint64_t)ts.tv_nsec + (uint64_t)idleUs * 1000ULL;
|
||||
ts.tv_sec += (time_t)(nsec / 1000000000ULL);
|
||||
ts.tv_nsec = (long)(nsec % 1000000000ULL);
|
||||
pthread_cond_timedwait(&s_posix_cond[schedulerId], &s_posix_mutex[schedulerId], &ts);
|
||||
}
|
||||
pthread_mutex_unlock(&s_posix_mutex[schedulerId]);
|
||||
}
|
||||
|
||||
void CoHook_Posix_Wakeup(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_posix_inited[schedulerId])
|
||||
{
|
||||
pthread_mutex_lock(&s_posix_mutex[schedulerId]);
|
||||
pthread_cond_signal(&s_posix_cond[schedulerId]);
|
||||
pthread_mutex_unlock(&s_posix_mutex[schedulerId]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Fallback stubs if compiled on non-matching platform */
|
||||
#if !defined(_WIN32)
|
||||
int CoHook_Win32_Init(uint32_t schedulerId) { (void)schedulerId; return -1; }
|
||||
void CoHook_Win32_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; }
|
||||
void CoHook_Win32_Wakeup(uint32_t schedulerId) { (void)schedulerId; }
|
||||
void CoHook_Win32_Deinit(uint32_t schedulerId) { (void)schedulerId; }
|
||||
#endif
|
||||
|
||||
#if !defined(__linux__) && !defined(__unix__) && !defined(__APPLE__) && !defined(__QNX__) && !defined(__QNXNTO__) && !defined(__ANDROID__) && !defined(__VXWORKS__) && !defined(__NuttX__)
|
||||
int CoHook_Posix_Init(uint32_t schedulerId) { (void)schedulerId; return -1; }
|
||||
void CoHook_Posix_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; }
|
||||
void CoHook_Posix_Wakeup(uint32_t schedulerId) { (void)schedulerId; }
|
||||
void CoHook_Posix_Deinit(uint32_t schedulerId) { (void)schedulerId; }
|
||||
#endif
|
||||
|
||||
/* =======================================================================================================
|
||||
* 8. Unified / Auto-Detect Platform Dispatcher
|
||||
* ======================================================================================================= */
|
||||
|
||||
int CoScheduler_BindDefaultHooks(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER)
|
||||
{
|
||||
return COROUTINE_E_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
#if defined(INC_FREERTOS_H) || defined(COROUTINE_HOOK_FREERTOS)
|
||||
int ret = CoHook_FreeRTOS_Init(schedulerId, NULL);
|
||||
if (ret != 0) return ret;
|
||||
CoScheduler_SetIdleHook(schedulerId, CoHook_FreeRTOS_Idle);
|
||||
CoScheduler_SetWakeupHook(schedulerId, CoHook_FreeRTOS_Wakeup);
|
||||
#elif defined(RT_TICK_PER_SECOND) || defined(COROUTINE_HOOK_RTTHREAD)
|
||||
int ret = CoHook_RTThread_Init(schedulerId);
|
||||
if (ret != 0) return ret;
|
||||
CoScheduler_SetIdleHook(schedulerId, CoHook_RTThread_Idle);
|
||||
CoScheduler_SetWakeupHook(schedulerId, CoHook_RTThread_Wakeup);
|
||||
#elif defined(CMSIS_OS2_H_) || defined(COROUTINE_HOOK_CMSIS_RTOS2)
|
||||
int ret = CoHook_CMSIS_Init(schedulerId, NULL);
|
||||
if (ret != 0) return ret;
|
||||
CoScheduler_SetIdleHook(schedulerId, CoHook_CMSIS_Idle);
|
||||
CoScheduler_SetWakeupHook(schedulerId, CoHook_CMSIS_Wakeup);
|
||||
#elif defined(OS_CFG_H) || defined(COROUTINE_HOOK_UCOS)
|
||||
int ret = CoHook_UCOS_Init(schedulerId, NULL);
|
||||
if (ret != 0) return ret;
|
||||
CoScheduler_SetIdleHook(schedulerId, CoHook_UCOS_Idle);
|
||||
CoScheduler_SetWakeupHook(schedulerId, CoHook_UCOS_Wakeup);
|
||||
#elif defined(_WIN32)
|
||||
int ret = CoHook_Win32_Init(schedulerId);
|
||||
if (ret != 0) return ret;
|
||||
CoScheduler_SetIdleHook(schedulerId, CoHook_Win32_Idle);
|
||||
CoScheduler_SetWakeupHook(schedulerId, CoHook_Win32_Wakeup);
|
||||
#elif defined(__linux__) || defined(__unix__) || defined(__APPLE__) || defined(__QNX__) || defined(__QNXNTO__) || defined(__ANDROID__) || defined(__VXWORKS__) || defined(__NuttX__)
|
||||
int ret = CoHook_Posix_Init(schedulerId);
|
||||
if (ret != 0) return ret;
|
||||
CoScheduler_SetIdleHook(schedulerId, CoHook_Posix_Idle);
|
||||
CoScheduler_SetWakeupHook(schedulerId, CoHook_Posix_Wakeup);
|
||||
#else
|
||||
CoScheduler_SetIdleHook(schedulerId, CoHook_Baremetal_Idle);
|
||||
CoScheduler_SetWakeupHook(schedulerId, CoHook_Baremetal_Wakeup);
|
||||
#endif
|
||||
|
||||
return COROUTINE_E_OK;
|
||||
}
|
||||
|
||||
int CoScheduler_UnbindDefaultHooks(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER)
|
||||
{
|
||||
return COROUTINE_E_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
CoScheduler_SetIdleHook(schedulerId, NULL);
|
||||
CoScheduler_SetWakeupHook(schedulerId, NULL);
|
||||
|
||||
#if defined(INC_FREERTOS_H) || defined(COROUTINE_HOOK_FREERTOS)
|
||||
CoHook_FreeRTOS_Deinit(schedulerId);
|
||||
#elif defined(RT_TICK_PER_SECOND) || defined(COROUTINE_HOOK_RTTHREAD)
|
||||
CoHook_RTThread_Deinit(schedulerId);
|
||||
#elif defined(CMSIS_OS2_H_) || defined(COROUTINE_HOOK_CMSIS_RTOS2)
|
||||
CoHook_CMSIS_Deinit(schedulerId);
|
||||
#elif defined(OS_CFG_H) || defined(COROUTINE_HOOK_UCOS)
|
||||
CoHook_UCOS_Deinit(schedulerId);
|
||||
#elif defined(_WIN32)
|
||||
CoHook_Win32_Deinit(schedulerId);
|
||||
#elif defined(__linux__) || defined(__unix__) || defined(__APPLE__) || defined(__QNX__) || defined(__QNXNTO__) || defined(__ANDROID__) || defined(__VXWORKS__) || defined(__NuttX__)
|
||||
CoHook_Posix_Deinit(schedulerId);
|
||||
#endif
|
||||
|
||||
return COROUTINE_E_OK;
|
||||
}
|
||||
252
source/08_coroutine/coroutine_hook.h
Normal file
252
source/08_coroutine/coroutine_hook.h
Normal file
@ -0,0 +1,252 @@
|
||||
/*********************************************************************************************************
|
||||
* ------------------------------------------------------------------------------------------------------
|
||||
* file description
|
||||
* ------------------------------------------------------------------------------------------------------
|
||||
* \file coroutine_hook.h
|
||||
* \unit coroutine
|
||||
* \brief Predefined idleHook and wakeupHook implementations for Bare-Metal, RTOS and OS platforms
|
||||
* \author Lamdonn
|
||||
* \version v0.2.0
|
||||
* \license GPL-2.0
|
||||
* \copyright Copyright (C) 2025 Lamdonn.
|
||||
********************************************************************************************************/
|
||||
#ifndef __coroutine_hook_H
|
||||
#define __coroutine_hook_H
|
||||
|
||||
#include "coroutine.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* =======================================================================================================
|
||||
* 1. Bare-metal / Embedded MCU Hooks (ARM Cortex-M, RISC-V, Xtensa, C-SKY, MSP430, etc.)
|
||||
* ======================================================================================================= */
|
||||
|
||||
/**
|
||||
* \brief: Bare-metal low power idle hook using hardware sleep instruction (wfi / waiti / sleep / LPM)
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \param idleUs: Estimated idle time in microseconds
|
||||
* \note: Puts the CPU core into low-power sleep mode until an interrupt occurs.
|
||||
*/
|
||||
void CoHook_Baremetal_Idle(uint32_t schedulerId, uint32_t idleUs);
|
||||
|
||||
/**
|
||||
* \brief: Bare-metal wakeup hook
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \note: On single-core MCU, hardware interrupts automatically wake the CPU from WFI.
|
||||
* In multi-core/AMP setups, triggers SEV (Send Event) or an Inter-Processor Interrupt (IPI).
|
||||
*/
|
||||
void CoHook_Baremetal_Wakeup(uint32_t schedulerId);
|
||||
|
||||
/* =======================================================================================================
|
||||
* 2. FreeRTOS Hooks (Task Notification based, Zero-RAM overhead)
|
||||
* ======================================================================================================= */
|
||||
|
||||
/**
|
||||
* \brief: Initialize FreeRTOS hooks for a scheduler, binding it to the target FreeRTOS task
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \param taskHandle: FreeRTOS TaskHandle_t running this scheduler (or NULL to use xTaskGetCurrentTaskHandle())
|
||||
* \return: 0 on success, or negative error code
|
||||
*/
|
||||
int CoHook_FreeRTOS_Init(uint32_t schedulerId, void *taskHandle);
|
||||
|
||||
/**
|
||||
* \brief: FreeRTOS idle hook (calls ulTaskNotifyTake with timeout)
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \param idleUs: Sleep timeout in microseconds
|
||||
*/
|
||||
void CoHook_FreeRTOS_Idle(uint32_t schedulerId, uint32_t idleUs);
|
||||
|
||||
/**
|
||||
* \brief: FreeRTOS wakeup hook (calls xTaskNotifyGive / vTaskNotifyGiveFromISR)
|
||||
* \param schedulerId: Target scheduler ID to wake up
|
||||
*/
|
||||
void CoHook_FreeRTOS_Wakeup(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Deinitialize FreeRTOS hooks for a scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
*/
|
||||
void CoHook_FreeRTOS_Deinit(uint32_t schedulerId);
|
||||
|
||||
/* =======================================================================================================
|
||||
* 3. RT-Thread Hooks (rt_sem semaphore based)
|
||||
* ======================================================================================================= */
|
||||
|
||||
/**
|
||||
* \brief: Initialize RT-Thread semaphore-based hooks for a scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \return: 0 on success, or negative error code
|
||||
*/
|
||||
int CoHook_RTThread_Init(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: RT-Thread idle hook (calls rt_sem_take with timeout)
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \param idleUs: Sleep timeout in microseconds
|
||||
*/
|
||||
void CoHook_RTThread_Idle(uint32_t schedulerId, uint32_t idleUs);
|
||||
|
||||
/**
|
||||
* \brief: RT-Thread wakeup hook (calls rt_sem_release)
|
||||
* \param schedulerId: Target scheduler ID to wake up
|
||||
*/
|
||||
void CoHook_RTThread_Wakeup(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Deinitialize RT-Thread hooks for a scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
*/
|
||||
void CoHook_RTThread_Deinit(uint32_t schedulerId);
|
||||
|
||||
/* =======================================================================================================
|
||||
* 4. CMSIS-RTOS2 Hooks (ARM Standard - Keil RTX5, FreeRTOS-CMSIS wrapper, etc.)
|
||||
* ======================================================================================================= */
|
||||
|
||||
/**
|
||||
* \brief: Initialize CMSIS-RTOS2 hooks for a scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \param threadId: osThreadId_t running this scheduler (or NULL to use osThreadGetId())
|
||||
* \return: 0 on success, or negative error code
|
||||
*/
|
||||
int CoHook_CMSIS_Init(uint32_t schedulerId, void *threadId);
|
||||
|
||||
/**
|
||||
* \brief: CMSIS-RTOS2 idle hook (calls osThreadFlagsWait with timeout)
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \param idleUs: Sleep timeout in microseconds
|
||||
*/
|
||||
void CoHook_CMSIS_Idle(uint32_t schedulerId, uint32_t idleUs);
|
||||
|
||||
/**
|
||||
* \brief: CMSIS-RTOS2 wakeup hook (calls osThreadFlagsSet)
|
||||
* \param schedulerId: Target scheduler ID to wake up
|
||||
*/
|
||||
void CoHook_CMSIS_Wakeup(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Deinitialize CMSIS-RTOS2 hooks for a scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
*/
|
||||
void CoHook_CMSIS_Deinit(uint32_t schedulerId);
|
||||
|
||||
/* =======================================================================================================
|
||||
* 5. uC/OS-III Hooks (Task Semaphore based)
|
||||
* ======================================================================================================= */
|
||||
|
||||
/**
|
||||
* \brief: Initialize uC/OS-III hooks for a scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \param pTcb: Target OS_TCB pointer (or NULL to use OSTCBCurPtr)
|
||||
* \return: 0 on success, or negative error code
|
||||
*/
|
||||
int CoHook_UCOS_Init(uint32_t schedulerId, void *pTcb);
|
||||
|
||||
/**
|
||||
* \brief: uC/OS-III idle hook (calls OSTaskSemPend with timeout)
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \param idleUs: Sleep timeout in microseconds
|
||||
*/
|
||||
void CoHook_UCOS_Idle(uint32_t schedulerId, uint32_t idleUs);
|
||||
|
||||
/**
|
||||
* \brief: uC/OS-III wakeup hook (calls OSTaskSemPost)
|
||||
* \param schedulerId: Target scheduler ID to wake up
|
||||
*/
|
||||
void CoHook_UCOS_Wakeup(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Deinitialize uC/OS-III hooks for a scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
*/
|
||||
void CoHook_UCOS_Deinit(uint32_t schedulerId);
|
||||
|
||||
/* =======================================================================================================
|
||||
* 6. POSIX / Linux / Android / macOS / iOS / QNX / VxWorks / NuttX Multi-Threaded Hooks
|
||||
* ======================================================================================================= */
|
||||
|
||||
/**
|
||||
* \brief: Initialize POSIX condition-variable based hooks for a scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \return: 0 on success, or negative error code
|
||||
*/
|
||||
int CoHook_Posix_Init(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: POSIX idle hook with timeout
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \param idleUs: Sleep timeout in microseconds
|
||||
*/
|
||||
void CoHook_Posix_Idle(uint32_t schedulerId, uint32_t idleUs);
|
||||
|
||||
/**
|
||||
* \brief: POSIX wakeup hook (signals condition variable to wake up scheduler immediately)
|
||||
* \param schedulerId: Target scheduler ID to wake up
|
||||
*/
|
||||
void CoHook_Posix_Wakeup(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Destroy POSIX synchronization primitives for a scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
*/
|
||||
void CoHook_Posix_Deinit(uint32_t schedulerId);
|
||||
|
||||
/* =======================================================================================================
|
||||
* 7. Windows Multi-Threaded Hooks (Win32 Event)
|
||||
* ======================================================================================================= */
|
||||
|
||||
/**
|
||||
* \brief: Initialize Windows event-based hooks for a scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \return: 0 on success, or negative error code
|
||||
*/
|
||||
int CoHook_Win32_Init(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Windows idle hook with timeout
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \param idleUs: Sleep timeout in microseconds
|
||||
*/
|
||||
void CoHook_Win32_Idle(uint32_t schedulerId, uint32_t idleUs);
|
||||
|
||||
/**
|
||||
* \brief: Windows wakeup hook (SetEvent to unblock scheduler immediately)
|
||||
* \param schedulerId: Target scheduler ID to wake up
|
||||
*/
|
||||
void CoHook_Win32_Wakeup(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Destroy Windows synchronization primitives for a scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
*/
|
||||
void CoHook_Win32_Deinit(uint32_t schedulerId);
|
||||
|
||||
/* =======================================================================================================
|
||||
* 8. Unified / Auto-Detect Platform Hooks (Ready-to-Use Out-of-the-Box)
|
||||
* ======================================================================================================= */
|
||||
|
||||
/**
|
||||
* \brief: Automatically attach platform-appropriate idleHook and wakeupHook to the scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \return: 0 on success, or negative error code
|
||||
* \note: Automatically detects FreeRTOS, RT-Thread, CMSIS-RTOS2, uC/OS, Windows, POSIX/Linux, or Bare-metal.
|
||||
* \example:
|
||||
* CoScheduler_Init(0, GetTimerUsec, 1000);
|
||||
* CoScheduler_BindDefaultHooks(0);
|
||||
*/
|
||||
int CoScheduler_BindDefaultHooks(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Release default platform hooks attached to the scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
*/
|
||||
int CoScheduler_UnbindDefaultHooks(uint32_t schedulerId);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !__coroutine_hook_H */
|
||||
645
source/08_coroutine/coroutine_io.c
Normal file
645
source/08_coroutine/coroutine_io.c
Normal file
@ -0,0 +1,645 @@
|
||||
/*********************************************************************************************************
|
||||
* ------------------------------------------------------------------------------------------------------
|
||||
* file description
|
||||
* ------------------------------------------------------------------------------------------------------
|
||||
* \file coroutine_io.c
|
||||
* \unit coroutine
|
||||
* \brief High-throughput asynchronous I/O multiplexer and cooperative socket API
|
||||
* \author Lamdonn
|
||||
* \version v0.2.0
|
||||
* \license GPL-2.0
|
||||
* \copyright Copyright (C) 2025 Lamdonn.
|
||||
********************************************************************************************************/
|
||||
#if defined(_WIN32) && !defined(_WIN32_WINNT)
|
||||
#define _WIN32_WINNT 0x0600
|
||||
#endif
|
||||
|
||||
#include "coroutine_io.h"
|
||||
#include "coroutine_hook.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* =======================================================================================================
|
||||
* Quad-Tier Platform Detection & Header Dispatching
|
||||
* ======================================================================================================= */
|
||||
#if defined(COROUTINE_IO_BAREMETAL)
|
||||
#define CO_IO_PLATFORM_BAREMETAL 1
|
||||
#elif defined(_WIN32)
|
||||
#define CO_IO_PLATFORM_WIN32 1
|
||||
#elif defined(__linux__) || defined(__unix__) || defined(__APPLE__) || \
|
||||
defined(__QNX__) || defined(__QNXNTO__) || defined(__ANDROID__) || \
|
||||
defined(__VXWORKS__) || defined(__NuttX__)
|
||||
#define CO_IO_PLATFORM_POSIX 1
|
||||
#elif defined(COROUTINE_IO_LWIP) || defined(LWIP_SOCKET)
|
||||
#define CO_IO_PLATFORM_LWIP 1
|
||||
#else
|
||||
#define CO_IO_PLATFORM_BAREMETAL 1
|
||||
#endif
|
||||
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
typedef int socklen_t;
|
||||
typedef WSAPOLLFD PollFd_t;
|
||||
#elif defined(CO_IO_PLATFORM_POSIX)
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
typedef struct pollfd PollFd_t;
|
||||
#elif defined(CO_IO_PLATFORM_LWIP)
|
||||
#include "lwip/sockets.h"
|
||||
#include "lwip/netdb.h"
|
||||
#include <errno.h>
|
||||
typedef struct pollfd PollFd_t;
|
||||
#elif defined(CO_IO_PLATFORM_BAREMETAL)
|
||||
/* Pure bare-metal mode: zero external socket headers */
|
||||
#ifndef POLLIN
|
||||
#define POLLIN (0x0001)
|
||||
#endif
|
||||
#ifndef POLLOUT
|
||||
#define POLLOUT (0x0004)
|
||||
#endif
|
||||
#ifndef POLLERR
|
||||
#define POLLERR (0x0008)
|
||||
#endif
|
||||
#ifndef POLLHUP
|
||||
#define POLLHUP (0x0010)
|
||||
#endif
|
||||
#ifndef POLLNVAL
|
||||
#define POLLNVAL (0x0020)
|
||||
#endif
|
||||
|
||||
typedef CoIoPollFd_t PollFd_t;
|
||||
static CoIoBaremetalPoller_t s_baremetal_poller = NULL;
|
||||
|
||||
void CoIo_SetBaremetalPoller(CoIoBaremetalPoller_t poller)
|
||||
{
|
||||
s_baremetal_poller = poller;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* =======================================================================================================
|
||||
* Small Buffer Optimization (SBO) Hybrid Poller Architecture
|
||||
* ======================================================================================================= */
|
||||
|
||||
#define CO_IO_STATIC_CAPACITY (64)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int fd;
|
||||
uint32_t events;
|
||||
CoTask_t task;
|
||||
int readyEvents;
|
||||
} CoIoWatch;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
CoIoWatch staticWatches[CO_IO_STATIC_CAPACITY]; /**< Static fast cache (0 heap allocations) */
|
||||
CoIoWatch *dynamicWatches; /**< Overflow dynamic buffer (heap, auto-expanding) */
|
||||
uint32_t capacity; /**< Current total capacity (starts at CO_IO_STATIC_CAPACITY) */
|
||||
uint32_t count; /**< Current number of active watched descriptors */
|
||||
} CoSchedulerPoller;
|
||||
|
||||
static CoSchedulerPoller s_pollers[COROUTINE_SCHEDULER_NUMBER] = {0};
|
||||
static int s_io_inited = 0;
|
||||
|
||||
/* Forward declarations */
|
||||
static void CoIo_IdleHook(uint32_t schedulerId, uint32_t idleUs);
|
||||
static void CoIo_WakeupHook(uint32_t schedulerId);
|
||||
|
||||
static inline CoIoWatch *CoIo_GetWatch(CoSchedulerPoller *poller, uint32_t index)
|
||||
{
|
||||
if (poller->dynamicWatches != NULL)
|
||||
{
|
||||
return &poller->dynamicWatches[index];
|
||||
}
|
||||
return &poller->staticWatches[index];
|
||||
}
|
||||
|
||||
static int CoIo_EnsureCapacity(CoSchedulerPoller *poller)
|
||||
{
|
||||
if (poller->capacity == 0)
|
||||
{
|
||||
poller->capacity = CO_IO_STATIC_CAPACITY;
|
||||
}
|
||||
|
||||
if (poller->count < poller->capacity)
|
||||
{
|
||||
return 0; /* Existing capacity has free slots */
|
||||
}
|
||||
|
||||
/* Verify if the coroutine system has a configured dynamic memory allocator */
|
||||
if (!Coroutine_HasMemHook())
|
||||
{
|
||||
return -1; /* Pure static mode: safely reject expansion beyond static limit */
|
||||
}
|
||||
|
||||
/* Needs expansion: double capacity */
|
||||
uint32_t newCap = poller->capacity * 2;
|
||||
CoIoWatch *newBuf = (CoIoWatch *)Coroutine_Malloc(sizeof(CoIoWatch) * newCap);
|
||||
if (newBuf == NULL) return -1;
|
||||
|
||||
memset(newBuf, 0, sizeof(CoIoWatch) * newCap);
|
||||
|
||||
if (poller->dynamicWatches == NULL)
|
||||
{
|
||||
/* First overflow: promote from static cache to dynamic heap */
|
||||
memcpy(newBuf, poller->staticWatches, sizeof(CoIoWatch) * poller->capacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Subsequent expansion: copy from previous dynamic buffer and free old */
|
||||
memcpy(newBuf, poller->dynamicWatches, sizeof(CoIoWatch) * poller->capacity);
|
||||
Coroutine_Free(poller->dynamicWatches);
|
||||
}
|
||||
|
||||
poller->dynamicWatches = newBuf;
|
||||
poller->capacity = newCap;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void CoIo_TryShrink(CoSchedulerPoller *poller)
|
||||
{
|
||||
/* If all descriptors have finished and poller is completely empty, revert to static */
|
||||
if (poller->dynamicWatches != NULL && poller->count == 0)
|
||||
{
|
||||
Coroutine_Free(poller->dynamicWatches);
|
||||
poller->dynamicWatches = NULL;
|
||||
poller->capacity = CO_IO_STATIC_CAPACITY;
|
||||
}
|
||||
}
|
||||
|
||||
/* =======================================================================================================
|
||||
* Subsystem Management
|
||||
* ======================================================================================================= */
|
||||
|
||||
int CoIo_Init(void)
|
||||
{
|
||||
if (s_io_inited) return 0;
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
WSADATA wsaData;
|
||||
int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
if (ret != 0) return -1;
|
||||
#endif
|
||||
s_io_inited = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CoIo_Deinit(void)
|
||||
{
|
||||
if (!s_io_inited) return;
|
||||
|
||||
for (uint32_t i = 0; i < COROUTINE_SCHEDULER_NUMBER; i++)
|
||||
{
|
||||
if (s_pollers[i].dynamicWatches != NULL)
|
||||
{
|
||||
Coroutine_Free(s_pollers[i].dynamicWatches);
|
||||
s_pollers[i].dynamicWatches = NULL;
|
||||
}
|
||||
s_pollers[i].capacity = CO_IO_STATIC_CAPACITY;
|
||||
s_pollers[i].count = 0;
|
||||
}
|
||||
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
WSACleanup();
|
||||
#endif
|
||||
s_io_inited = 0;
|
||||
}
|
||||
|
||||
int CoIo_AttachScheduler(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER)
|
||||
{
|
||||
return COROUTINE_E_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
CoIo_Init();
|
||||
s_pollers[schedulerId].capacity = CO_IO_STATIC_CAPACITY;
|
||||
s_pollers[schedulerId].count = 0;
|
||||
s_pollers[schedulerId].dynamicWatches = NULL;
|
||||
|
||||
CoScheduler_SetIdleHook(schedulerId, CoIo_IdleHook);
|
||||
CoScheduler_SetWakeupHook(schedulerId, CoIo_WakeupHook);
|
||||
|
||||
return COROUTINE_E_OK;
|
||||
}
|
||||
|
||||
int CoIo_DetachScheduler(uint32_t schedulerId)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER)
|
||||
{
|
||||
return COROUTINE_E_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
CoScheduler_SetIdleHook(schedulerId, NULL);
|
||||
CoScheduler_SetWakeupHook(schedulerId, NULL);
|
||||
|
||||
if (s_pollers[schedulerId].dynamicWatches != NULL)
|
||||
{
|
||||
Coroutine_Free(s_pollers[schedulerId].dynamicWatches);
|
||||
s_pollers[schedulerId].dynamicWatches = NULL;
|
||||
}
|
||||
s_pollers[schedulerId].capacity = CO_IO_STATIC_CAPACITY;
|
||||
s_pollers[schedulerId].count = 0;
|
||||
|
||||
return COROUTINE_E_OK;
|
||||
}
|
||||
|
||||
int CoIo_SetNonblocking(int fd)
|
||||
{
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
u_long mode = 1;
|
||||
return ioctlsocket((SOCKET)fd, FIONBIO, &mode);
|
||||
#elif defined(CO_IO_PLATFORM_POSIX)
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags < 0) return -1;
|
||||
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
#elif defined(CO_IO_PLATFORM_LWIP)
|
||||
int flags = lwip_fcntl(fd, F_GETFL, 0);
|
||||
if (flags < 0) return -1;
|
||||
return lwip_fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
#else
|
||||
(void)fd;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* =======================================================================================================
|
||||
* Polling Engine & Reactor Idle Hook (WSAPoll / poll based, no FD limits)
|
||||
* ======================================================================================================= */
|
||||
|
||||
static void CoIo_IdleHook(uint32_t schedulerId, uint32_t idleUs)
|
||||
{
|
||||
if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return;
|
||||
CoSchedulerPoller *poller = &s_pollers[schedulerId];
|
||||
|
||||
/* If no I/O descriptors registered, fall back to standard low-power platform sleep hook */
|
||||
if (poller->count == 0)
|
||||
{
|
||||
CoHook_Baremetal_Idle(schedulerId, idleUs);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Allocate pollfd array using Small Buffer Optimization */
|
||||
PollFd_t staticPfds[CO_IO_STATIC_CAPACITY];
|
||||
uint32_t staticMap[CO_IO_STATIC_CAPACITY];
|
||||
PollFd_t *pfds = staticPfds;
|
||||
uint32_t *mapIndex = staticMap;
|
||||
uint32_t heapAllocated = 0;
|
||||
|
||||
if (poller->count > CO_IO_STATIC_CAPACITY)
|
||||
{
|
||||
pfds = (PollFd_t *)Coroutine_Malloc(sizeof(PollFd_t) * poller->count);
|
||||
mapIndex = (uint32_t *)Coroutine_Malloc(sizeof(uint32_t) * poller->count);
|
||||
if (pfds == NULL || mapIndex == NULL)
|
||||
{
|
||||
if (pfds) Coroutine_Free(pfds);
|
||||
if (mapIndex) Coroutine_Free(mapIndex);
|
||||
CoHook_Baremetal_Idle(schedulerId, idleUs);
|
||||
return;
|
||||
}
|
||||
heapAllocated = 1;
|
||||
}
|
||||
|
||||
uint32_t nfds = 0;
|
||||
for (uint32_t i = 0; i < poller->capacity && nfds < poller->count; i++)
|
||||
{
|
||||
CoIoWatch *w = CoIo_GetWatch(poller, i);
|
||||
if (w->task != NULL)
|
||||
{
|
||||
pfds[nfds].fd = w->fd;
|
||||
pfds[nfds].events = 0;
|
||||
if (w->events & CO_IO_READ) pfds[nfds].events |= POLLIN;
|
||||
if (w->events & CO_IO_WRITE) pfds[nfds].events |= POLLOUT;
|
||||
pfds[nfds].revents = 0;
|
||||
mapIndex[nfds] = i;
|
||||
nfds++;
|
||||
}
|
||||
}
|
||||
|
||||
if (nfds == 0)
|
||||
{
|
||||
if (heapAllocated)
|
||||
{
|
||||
Coroutine_Free(pfds);
|
||||
Coroutine_Free(mapIndex);
|
||||
}
|
||||
CoHook_Baremetal_Idle(schedulerId, idleUs);
|
||||
return;
|
||||
}
|
||||
|
||||
int timeoutMs = (idleUs == 0) ? -1 : (int)((idleUs + 999) / 1000);
|
||||
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
int n = WSAPoll(pfds, nfds, timeoutMs);
|
||||
#elif defined(CO_IO_PLATFORM_POSIX)
|
||||
int n = poll(pfds, nfds, timeoutMs);
|
||||
#elif defined(CO_IO_PLATFORM_LWIP)
|
||||
int n = lwip_poll(pfds, nfds, timeoutMs);
|
||||
#elif defined(CO_IO_PLATFORM_BAREMETAL)
|
||||
int n = 0;
|
||||
if (s_baremetal_poller != NULL)
|
||||
{
|
||||
n = s_baremetal_poller((CoIoPollFd_t *)pfds, nfds, timeoutMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
CoHook_Baremetal_Idle(schedulerId, idleUs);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (n > 0)
|
||||
{
|
||||
for (uint32_t i = 0; i < nfds; i++)
|
||||
{
|
||||
if (pfds[i].revents != 0)
|
||||
{
|
||||
uint32_t idx = mapIndex[i];
|
||||
CoIoWatch *w = CoIo_GetWatch(poller, idx);
|
||||
int rev = 0;
|
||||
if (pfds[i].revents & POLLIN) rev |= CO_IO_READ;
|
||||
if (pfds[i].revents & POLLOUT) rev |= CO_IO_WRITE;
|
||||
if (pfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) rev |= CO_IO_ERROR;
|
||||
|
||||
w->readyEvents = rev;
|
||||
CoTask_Wakeup(w->task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (heapAllocated)
|
||||
{
|
||||
Coroutine_Free(pfds);
|
||||
Coroutine_Free(mapIndex);
|
||||
}
|
||||
}
|
||||
|
||||
static void CoIo_WakeupHook(uint32_t schedulerId)
|
||||
{
|
||||
(void)schedulerId;
|
||||
}
|
||||
|
||||
int CoTask_WaitFd(int fd, uint32_t events, uint32_t timeoutMs)
|
||||
{
|
||||
int schedId = CoTask_SchedulerId();
|
||||
if (schedId < 0 || schedId >= (int)COROUTINE_SCHEDULER_NUMBER)
|
||||
{
|
||||
return CO_IO_INVALID;
|
||||
}
|
||||
|
||||
CoSchedulerPoller *poller = &s_pollers[schedId];
|
||||
|
||||
/* Ensure poller has enough capacity (auto expands from static to dynamic heap if needed) */
|
||||
if (CoIo_EnsureCapacity(poller) != 0)
|
||||
{
|
||||
return CO_IO_INVALID;
|
||||
}
|
||||
|
||||
/* Find an available watch slot within current capacity */
|
||||
int slot = -1;
|
||||
for (uint32_t i = 0; i < poller->capacity; i++)
|
||||
{
|
||||
CoIoWatch *w = CoIo_GetWatch(poller, i);
|
||||
if (w->task == NULL)
|
||||
{
|
||||
slot = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot < 0)
|
||||
{
|
||||
return CO_IO_INVALID;
|
||||
}
|
||||
|
||||
CoIoWatch *watch = CoIo_GetWatch(poller, (uint32_t)slot);
|
||||
watch->fd = fd;
|
||||
watch->events = events;
|
||||
watch->task = CoTask_Self();
|
||||
watch->readyEvents = 0;
|
||||
poller->count++;
|
||||
|
||||
/* Suspend coroutine until polled event or timeout */
|
||||
CoTask_Wait(.ms = timeoutMs);
|
||||
|
||||
/* Retrieve ready events and clean up slot (re-evaluate watch pointer as poller might have realloc'd) */
|
||||
CoIoWatch *w = CoIo_GetWatch(poller, (uint32_t)slot);
|
||||
int res = w->readyEvents;
|
||||
w->task = NULL;
|
||||
w->fd = -1;
|
||||
w->events = 0;
|
||||
w->readyEvents = 0;
|
||||
if (poller->count > 0) poller->count--;
|
||||
|
||||
/* Shrink back to static cache if all overflow connections closed */
|
||||
CoIo_TryShrink(poller);
|
||||
|
||||
if (res != 0)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
return CO_IO_TIMEOUT;
|
||||
}
|
||||
|
||||
/* =======================================================================================================
|
||||
* Cooperative Non-Blocking Socket Operations
|
||||
* ======================================================================================================= */
|
||||
|
||||
int CoSocket_Create(int domain, int type, int protocol)
|
||||
{
|
||||
CoIo_Init();
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
SOCKET s = socket(domain, type, protocol);
|
||||
if (s == INVALID_SOCKET) return -1;
|
||||
CoIo_SetNonblocking((int)s);
|
||||
return (int)s;
|
||||
#elif defined(CO_IO_PLATFORM_POSIX)
|
||||
int s = socket(domain, type, protocol);
|
||||
if (s < 0) return -1;
|
||||
CoIo_SetNonblocking(s);
|
||||
return s;
|
||||
#elif defined(CO_IO_PLATFORM_LWIP)
|
||||
int s = lwip_socket(domain, type, protocol);
|
||||
if (s < 0) return -1;
|
||||
CoIo_SetNonblocking(s);
|
||||
return s;
|
||||
#else
|
||||
(void)domain; (void)type; (void)protocol;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int CoSocket_Connect(int fd, const void *addr, int addrlen, uint32_t timeoutMs)
|
||||
{
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
int ret = connect((SOCKET)fd, (const struct sockaddr *)addr, addrlen);
|
||||
if (ret == 0) return 0;
|
||||
int err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) return -1;
|
||||
#elif defined(CO_IO_PLATFORM_POSIX)
|
||||
int ret = connect(fd, (const struct sockaddr *)addr, (socklen_t)addrlen);
|
||||
if (ret == 0) return 0;
|
||||
if (errno != EINPROGRESS && errno != EWOULDBLOCK) return -1;
|
||||
#elif defined(CO_IO_PLATFORM_LWIP)
|
||||
int ret = lwip_connect(fd, (const struct sockaddr *)addr, (socklen_t)addrlen);
|
||||
if (ret == 0) return 0;
|
||||
if (errno != EINPROGRESS && errno != EWOULDBLOCK) return -1;
|
||||
#else
|
||||
(void)fd; (void)addr; (void)addrlen; (void)timeoutMs;
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
int waitRes = CoTask_WaitFd(fd, CO_IO_WRITE, timeoutMs);
|
||||
if (waitRes <= 0) return waitRes;
|
||||
|
||||
/* Verify connect success status via SO_ERROR */
|
||||
int sockErr = 0;
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
socklen_t optLen = sizeof(sockErr);
|
||||
getsockopt((SOCKET)fd, SOL_SOCKET, SO_ERROR, (char *)&sockErr, (int *)&optLen);
|
||||
#elif defined(CO_IO_PLATFORM_POSIX)
|
||||
socklen_t optLen = sizeof(sockErr);
|
||||
getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockErr, &optLen);
|
||||
#elif defined(CO_IO_PLATFORM_LWIP)
|
||||
socklen_t optLen = sizeof(sockErr);
|
||||
lwip_getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockErr, &optLen);
|
||||
#else
|
||||
sockErr = -1;
|
||||
#endif
|
||||
|
||||
return (sockErr == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
int CoSocket_Accept(int fd, void *addr, int *addrlen, uint32_t timeoutMs)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
SOCKET client = accept((SOCKET)fd, (struct sockaddr *)addr, (int *)addrlen);
|
||||
if (client != INVALID_SOCKET)
|
||||
{
|
||||
CoIo_SetNonblocking((int)client);
|
||||
return (int)client;
|
||||
}
|
||||
int err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK) return -1;
|
||||
#elif defined(CO_IO_PLATFORM_POSIX)
|
||||
socklen_t slen = addrlen ? (socklen_t)*addrlen : 0;
|
||||
int client = accept(fd, (struct sockaddr *)addr, addrlen ? &slen : NULL);
|
||||
if (client >= 0)
|
||||
{
|
||||
if (addrlen) *addrlen = (int)slen;
|
||||
CoIo_SetNonblocking(client);
|
||||
return client;
|
||||
}
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) return -1;
|
||||
#elif defined(CO_IO_PLATFORM_LWIP)
|
||||
socklen_t slen = addrlen ? (socklen_t)*addrlen : 0;
|
||||
int client = lwip_accept(fd, (struct sockaddr *)addr, addrlen ? &slen : NULL);
|
||||
if (client >= 0)
|
||||
{
|
||||
if (addrlen) *addrlen = (int)slen;
|
||||
CoIo_SetNonblocking(client);
|
||||
return client;
|
||||
}
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) return -1;
|
||||
#else
|
||||
(void)fd; (void)addr; (void)addrlen; (void)timeoutMs;
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
int waitRes = CoTask_WaitFd(fd, CO_IO_READ, timeoutMs);
|
||||
if (waitRes <= 0) return waitRes;
|
||||
}
|
||||
}
|
||||
|
||||
int CoSocket_Read(int fd, void *buf, size_t len, uint32_t timeoutMs)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
int ret = recv((SOCKET)fd, (char *)buf, (int)len, 0);
|
||||
if (ret >= 0) return ret;
|
||||
int err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK) return -1;
|
||||
#elif defined(CO_IO_PLATFORM_POSIX)
|
||||
ssize_t ret = recv(fd, buf, len, 0);
|
||||
if (ret >= 0) return (int)ret;
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) return -1;
|
||||
#elif defined(CO_IO_PLATFORM_LWIP)
|
||||
int ret = lwip_recv(fd, buf, len, 0);
|
||||
if (ret >= 0) return ret;
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) return -1;
|
||||
#else
|
||||
(void)fd; (void)buf; (void)len; (void)timeoutMs;
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
int waitRes = CoTask_WaitFd(fd, CO_IO_READ, timeoutMs);
|
||||
if (waitRes <= 0) return waitRes;
|
||||
}
|
||||
}
|
||||
|
||||
int CoSocket_Write(int fd, const void *buf, size_t len, uint32_t timeoutMs)
|
||||
{
|
||||
size_t total = 0;
|
||||
while (total < len)
|
||||
{
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
int ret = send((SOCKET)fd, (const char *)buf + total, (int)(len - total), 0);
|
||||
if (ret > 0)
|
||||
{
|
||||
total += ret;
|
||||
continue;
|
||||
}
|
||||
int err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK) return -1;
|
||||
#elif defined(CO_IO_PLATFORM_POSIX)
|
||||
ssize_t ret = send(fd, (const char *)buf + total, len - total, 0);
|
||||
if (ret > 0)
|
||||
{
|
||||
total += (size_t)ret;
|
||||
continue;
|
||||
}
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) return -1;
|
||||
#elif defined(CO_IO_PLATFORM_LWIP)
|
||||
int ret = lwip_send(fd, (const char *)buf + total, len - total, 0);
|
||||
if (ret > 0)
|
||||
{
|
||||
total += (size_t)ret;
|
||||
continue;
|
||||
}
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) return -1;
|
||||
#else
|
||||
(void)fd; (void)buf; (void)len; (void)timeoutMs;
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
int waitRes = CoTask_WaitFd(fd, CO_IO_WRITE, timeoutMs);
|
||||
if (waitRes <= 0) return waitRes;
|
||||
}
|
||||
return (int)total;
|
||||
}
|
||||
|
||||
int CoSocket_Close(int fd)
|
||||
{
|
||||
#if defined(CO_IO_PLATFORM_WIN32)
|
||||
return closesocket((SOCKET)fd);
|
||||
#elif defined(CO_IO_PLATFORM_POSIX)
|
||||
return close(fd);
|
||||
#elif defined(CO_IO_PLATFORM_LWIP)
|
||||
return lwip_close(fd);
|
||||
#else
|
||||
(void)fd;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
173
source/08_coroutine/coroutine_io.h
Normal file
173
source/08_coroutine/coroutine_io.h
Normal file
@ -0,0 +1,173 @@
|
||||
/*********************************************************************************************************
|
||||
* ------------------------------------------------------------------------------------------------------
|
||||
* file description
|
||||
* ------------------------------------------------------------------------------------------------------
|
||||
* \file coroutine_io.h
|
||||
* \unit coroutine
|
||||
* \brief High-throughput asynchronous I/O multiplexer and cooperative socket API
|
||||
* \author Lamdonn
|
||||
* \version v0.2.0
|
||||
* \license GPL-2.0
|
||||
* \copyright Copyright (C) 2025 Lamdonn.
|
||||
********************************************************************************************************/
|
||||
#ifndef __coroutine_io_H
|
||||
#define __coroutine_io_H
|
||||
|
||||
#include "coroutine.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* =======================================================================================================
|
||||
* I/O Event Definitions
|
||||
* ======================================================================================================= */
|
||||
#define CO_IO_READ (0x01) /**< Readable event */
|
||||
#define CO_IO_WRITE (0x02) /**< Writable event */
|
||||
#define CO_IO_ERROR (0x04) /**< Error or hangup event */
|
||||
|
||||
/* Return status codes */
|
||||
#define CO_IO_OK (0)
|
||||
#define CO_IO_TIMEOUT (-1)
|
||||
#define CO_IO_ERROR_EVENT (-2)
|
||||
#define CO_IO_INVALID (-3)
|
||||
|
||||
/* =======================================================================================================
|
||||
* I/O Subsystem Management
|
||||
* ======================================================================================================= */
|
||||
|
||||
/**
|
||||
* \brief: Initialize coroutine I/O subsystem (e.g. socket library setup)
|
||||
* \return: 0 on success, or negative error code
|
||||
*/
|
||||
int CoIo_Init(void);
|
||||
|
||||
/**
|
||||
* \brief: Deinitialize coroutine I/O subsystem
|
||||
*/
|
||||
void CoIo_Deinit(void);
|
||||
|
||||
/**
|
||||
* \brief: Attach the high-throughput I/O poller to a specific scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \return: 0 on success, or negative error code
|
||||
* \note: Configures the scheduler's idleHook and wakeupHook to poll network descriptors and wake tasks instantly.
|
||||
*/
|
||||
int CoIo_AttachScheduler(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Detach the I/O poller from a specific scheduler
|
||||
* \param schedulerId: Target scheduler ID
|
||||
* \return: 0 on success, or negative error code
|
||||
*/
|
||||
int CoIo_DetachScheduler(uint32_t schedulerId);
|
||||
|
||||
/**
|
||||
* \brief: Set socket or file descriptor to non-blocking mode
|
||||
* \param fd: Descriptor handle
|
||||
* \return: 0 on success, or negative error code
|
||||
*/
|
||||
int CoIo_SetNonblocking(int fd);
|
||||
|
||||
/**
|
||||
* \brief: Suspend current coroutine until descriptor fd has specified events or timeout
|
||||
* \param fd: Socket or file descriptor
|
||||
* \param events: Bitmask of CO_IO_READ / CO_IO_WRITE
|
||||
* \param timeoutMs: Timeout in milliseconds (0 for infinite)
|
||||
* \return: Ready events bitmask (>0) on success, CO_IO_TIMEOUT on timeout, or negative error code
|
||||
*/
|
||||
int CoTask_WaitFd(int fd, uint32_t events, uint32_t timeoutMs);
|
||||
|
||||
/* =======================================================================================================
|
||||
* Bare-Metal / MCU Poller Hook (Optional, for polling hardware UART, CAN, SPI without OS)
|
||||
* ======================================================================================================= */
|
||||
|
||||
typedef struct CoIoPollFd
|
||||
{
|
||||
int fd; /**< Device or descriptor handle (e.g. UART1, CAN1) */
|
||||
short events; /**< Requested events (CO_IO_READ / CO_IO_WRITE) */
|
||||
short revents; /**< Returned events */
|
||||
} CoIoPollFd_t;
|
||||
|
||||
/**
|
||||
* \brief: User-injected baremetal hardware poller callback
|
||||
* \param pfds: Array of descriptors and requested events
|
||||
* \param nfds: Number of descriptors
|
||||
* \param timeoutMs: Timeout in milliseconds
|
||||
* \return: Number of descriptors with events (>0), 0 on timeout, or negative error code
|
||||
*/
|
||||
typedef int (*CoIoBaremetalPoller_t)(CoIoPollFd_t *pfds, uint32_t nfds, int timeoutMs);
|
||||
|
||||
/**
|
||||
* \brief: Register user-defined baremetal hardware poller (for MCU UART / CAN / SPI)
|
||||
* \param poller: Function pointer to poller callback, or NULL to reset
|
||||
*/
|
||||
void CoIo_SetBaremetalPoller(CoIoBaremetalPoller_t poller);
|
||||
|
||||
/* =======================================================================================================
|
||||
* High-Density Cooperative Socket APIs (Synchronous Code, Asynchronous Execution)
|
||||
* ======================================================================================================= */
|
||||
|
||||
/**
|
||||
* \brief: Create a non-blocking socket
|
||||
* \param domain: Address family (e.g. AF_INET)
|
||||
* \param type: Socket type (e.g. SOCK_STREAM, SOCK_DGRAM)
|
||||
* \param protocol: Protocol (e.g. 0 or IPPROTO_TCP)
|
||||
* \return: Non-blocking socket fd on success, or -1 on error
|
||||
*/
|
||||
int CoSocket_Create(int domain, int type, int protocol);
|
||||
|
||||
/**
|
||||
* \brief: Connect to remote address with coroutine yield on wait
|
||||
* \param fd: Socket fd
|
||||
* \param addr: Pointer to struct sockaddr
|
||||
* \param addrlen: Size of addr
|
||||
* \param timeoutMs: Connection timeout in milliseconds
|
||||
* \return: 0 on success, or negative error code
|
||||
*/
|
||||
int CoSocket_Connect(int fd, const void *addr, int addrlen, uint32_t timeoutMs);
|
||||
|
||||
/**
|
||||
* \brief: Accept an incoming connection with coroutine yield on wait
|
||||
* \param fd: Listening socket fd
|
||||
* \param addr: Output sockaddr pointer (optional, can be NULL)
|
||||
* \param addrlen: Input/output sockaddr len pointer (optional, can be NULL)
|
||||
* \param timeoutMs: Accept timeout in milliseconds
|
||||
* \return: Connected client socket fd (already non-blocking), or negative error code
|
||||
*/
|
||||
int CoSocket_Accept(int fd, void *addr, int *addrlen, uint32_t timeoutMs);
|
||||
|
||||
/**
|
||||
* \brief: Read data from socket with coroutine yield on wait
|
||||
* \param fd: Connected socket fd
|
||||
* \param buf: Destination buffer
|
||||
* \param len: Buffer capacity
|
||||
* \param timeoutMs: Read timeout in milliseconds
|
||||
* \return: Bytes read (>0), 0 on EOF / connection closed, or negative error code
|
||||
*/
|
||||
int CoSocket_Read(int fd, void *buf, size_t len, uint32_t timeoutMs);
|
||||
|
||||
/**
|
||||
* \brief: Write data to socket with coroutine yield on wait
|
||||
* \param fd: Connected socket fd
|
||||
* \param buf: Source buffer
|
||||
* \param len: Bytes to send
|
||||
* \param timeoutMs: Write timeout in milliseconds
|
||||
* \return: Bytes sent (>0), or negative error code
|
||||
*/
|
||||
int CoSocket_Write(int fd, const void *buf, size_t len, uint32_t timeoutMs);
|
||||
|
||||
/**
|
||||
* \brief: Close a socket descriptor
|
||||
* \param fd: Socket fd
|
||||
* \return: 0 on success, or -1 on error
|
||||
*/
|
||||
int CoSocket_Close(int fd);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !__coroutine_io_H */
|
||||
@ -1,17 +1,30 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
#if defined(TEST_TARGET_coroutine)
|
||||
#include <varch/command.h>
|
||||
#include <varch/unitt.h>
|
||||
#include <varch/coroutine.h>
|
||||
#include <varch/coroutine_hook.h>
|
||||
#include <varch/coroutine_io.h>
|
||||
#else
|
||||
#include "init.h"
|
||||
#include "command.h"
|
||||
#include "unitt.h"
|
||||
#include "kern.h"
|
||||
#include "coroutine.h"
|
||||
#include "coroutine_hook.h"
|
||||
#include "coroutine_io.h"
|
||||
#endif
|
||||
|
||||
#define MUTI_THREAD_NUM 2
|
||||
@ -48,29 +61,47 @@ uint64_t GetTimerUsec(void)
|
||||
// #define EXIT_TEST
|
||||
extern uint64_t unitt_clock(void);
|
||||
|
||||
static int s_libco_step = 0;
|
||||
static void *libco_test_worker(void *arg)
|
||||
{
|
||||
s_libco_step = 1;
|
||||
CoTask_Yield();
|
||||
s_libco_step = 2;
|
||||
CoTask_Yield();
|
||||
s_libco_step = 3;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int test_libco(void)
|
||||
{
|
||||
CoScheduler_Init(0, GetTimerUsec, 1000);
|
||||
s_libco_step = 0;
|
||||
CoTask_t task = CoTask_Create(libco_test_worker, .manual=1);
|
||||
if (!task) return UNITT_E_FAIL;
|
||||
|
||||
int ret1 = CoTask_Resume(task);
|
||||
if (ret1 != COROUTINE_E_OK || s_libco_step != 1) return UNITT_E_FAIL;
|
||||
|
||||
int ret2 = CoTask_Resume(task);
|
||||
if (ret2 != COROUTINE_E_OK || s_libco_step != 2) return UNITT_E_FAIL;
|
||||
|
||||
int ret3 = CoTask_Resume(task);
|
||||
if (ret3 != 1 || s_libco_step != 3) return UNITT_E_FAIL;
|
||||
|
||||
CoTask_Delete(task);
|
||||
return UNITT_E_OK;
|
||||
}
|
||||
|
||||
static int test_0(void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
if (0)
|
||||
{
|
||||
|
||||
#if defined (EXIT_TEST)
|
||||
exit(0);
|
||||
#endif
|
||||
return UNITT_E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return UNITT_E_OK;
|
||||
return test_libco();
|
||||
}
|
||||
|
||||
static void unitt_task(void)
|
||||
{
|
||||
static UNITT_TCASE rand_tests[] = {
|
||||
UNITT_TCASE(test_0),
|
||||
// UNITT_TCASE(test_1),
|
||||
// UNITT_TCASE(test_2),
|
||||
UNITT_TCASE(test_libco),
|
||||
};
|
||||
|
||||
static UNITT suites[] = {
|
||||
@ -85,71 +116,160 @@ static void unitt_task(void)
|
||||
/************************************************************************************/
|
||||
|
||||
static CoEvent gEvent = COEVENT_STATIC_VALUE;
|
||||
static CoTask_t gTickCoroutine = NULL;
|
||||
static CoTask_t gTockCoroutine = NULL;
|
||||
static uint8_t gStackTick[1024 * 10];
|
||||
static uint8_t gStackTock[1024 * 10];
|
||||
static CoTimer_t gTimer0Handle = NULL;
|
||||
static CoTimer_t gTimer1Handle = NULL;
|
||||
|
||||
static void *tick(void *arg)
|
||||
/* --- 1. Manual Point-to-Point Test (libco style) --- */
|
||||
static void *manual_worker(void *arg)
|
||||
{
|
||||
while (1)
|
||||
printf(" [Manual Task] Step 1: started, yielding...\n");
|
||||
CoTask_Yield();
|
||||
printf(" [Manual Task] Step 2: resumed, yielding again...\n");
|
||||
CoTask_Yield();
|
||||
printf(" [Manual Task] Step 3: resumed, yielding again...\n");
|
||||
CoTask_Yield();
|
||||
printf(" [Manual Task] Step 4: resumed, finishing!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_manual(void)
|
||||
{
|
||||
CoTask_Wait(.ms=1000);
|
||||
printf("tick-running...\n");
|
||||
printf("=== [Test Manual] Point-to-Point Stepping (CoTask_Resume & CoTask_Yield) ===\n");
|
||||
CoScheduler_Init(0, GetTimerUsec, 1000);
|
||||
CoTask_t task = CoTask_Create(manual_worker, .manual = 1);
|
||||
if (!task)
|
||||
{
|
||||
printf("Failed to create manual task!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int step = 1; step <= 4; step++)
|
||||
{
|
||||
printf("[Main] Calling CoTask_Resume (%d)...\n", step);
|
||||
int ret = CoTask_Resume(task);
|
||||
printf("[Main] CoTask_Resume returned: %d\n", ret);
|
||||
}
|
||||
|
||||
CoTask_Delete(task);
|
||||
printf("=== [Test Manual] Completed! ===\n\n");
|
||||
}
|
||||
|
||||
/* --- 2. Shared Stack Test --- */
|
||||
static void *shared_worker_a(void *arg)
|
||||
{
|
||||
volatile int buf[100];
|
||||
for (int i = 0; i < 100; i++) buf[i] = 1000 + i;
|
||||
printf(" [Shared A] initial buf[0]=%d, buf[99]=%d, yielding...\n", buf[0], buf[99]);
|
||||
fflush(stdout);
|
||||
CoTask_Yield();
|
||||
printf(" [Shared A] resumed! checking buf[0]=%d, buf[99]=%d\n", buf[0], buf[99]);
|
||||
if (buf[0] != 1000 || buf[99] != 1099)
|
||||
{
|
||||
printf(" [Shared A] ERROR: stack data corrupted!\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" [Shared A] SUCCESS: stack data perfectly preserved!\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *shared_worker_b(void *arg)
|
||||
{
|
||||
volatile int buf[100];
|
||||
for (int i = 0; i < 100; i++) buf[i] = 2000 + i;
|
||||
printf(" [Shared B] initial buf[0]=%d, buf[99]=%d, yielding...\n", buf[0], buf[99]);
|
||||
fflush(stdout);
|
||||
CoTask_Yield();
|
||||
printf(" [Shared B] resumed! checking buf[0]=%d, buf[99]=%d\n", buf[0], buf[99]);
|
||||
if (buf[0] != 2000 || buf[99] != 2099)
|
||||
{
|
||||
printf(" [Shared B] ERROR: stack data corrupted!\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" [Shared B] SUCCESS: stack data perfectly preserved!\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_shared(void)
|
||||
{
|
||||
printf("=== [Test Shared] Shared Stack Mode (Copy on Suspend, Restore on Resume) ===\n");
|
||||
#if (COROUTINE_SHARED_STACK_SIZE > 0)
|
||||
Coroutine_Init(.malloc = malloc, .free = free);
|
||||
CoScheduler_Init(0, GetTimerUsec, 1000);
|
||||
|
||||
CoTask_t ta = CoTask_Create(shared_worker_a, .manual = 1, .sharedStack = 1);
|
||||
CoTask_t tb = CoTask_Create(shared_worker_b, .manual = 1, .sharedStack = 1);
|
||||
if (!ta || !tb)
|
||||
{
|
||||
printf("Failed to create shared stack tasks! (Ensure dynamic memory allocator is configured)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("[Main] Step 1: Run Task A on shared stack...\n");
|
||||
CoTask_Resume(ta);
|
||||
|
||||
printf("[Main] Step 2: Run Task B (overwriting physical shared stack)...\n");
|
||||
CoTask_Resume(tb);
|
||||
|
||||
printf("[Main] Step 3: Resume Task A (restores stack A)...\n");
|
||||
CoTask_Resume(ta);
|
||||
|
||||
printf("[Main] Step 4: Resume Task B (restores stack B)...\n");
|
||||
CoTask_Resume(tb);
|
||||
|
||||
CoTask_Delete(ta);
|
||||
CoTask_Delete(tb);
|
||||
printf("=== [Test Shared] Completed! ===\n\n");
|
||||
#else
|
||||
printf("COROUTINE_SHARED_STACK_SIZE is 0. Shared stack feature is disabled!\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* --- 3. Single Scheduler Automatic Mode --- */
|
||||
static void *single_tick(void *arg)
|
||||
{
|
||||
for (int i = 1; i <= 5; i++)
|
||||
{
|
||||
CoTask_Wait(.ms = 100);
|
||||
printf(" [Single Scheduler] tick %d/5...\n", i);
|
||||
CoEvent_Notify(&gEvent, 0x02);
|
||||
}
|
||||
printf(" [Single Scheduler] tick finished!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *tock(void *arg)
|
||||
static void *single_tock(void *arg)
|
||||
{
|
||||
while (1)
|
||||
for (int i = 1; i <= 5; i++)
|
||||
{
|
||||
uint32_t evs = CoTask_Wait(.pEvent=&gEvent);
|
||||
if (evs & 0x01)
|
||||
{
|
||||
printf("event 0x01 triggered...\n");
|
||||
}
|
||||
uint32_t evs = CoTask_Wait(.pEvent = &gEvent, .ms = 500);
|
||||
if (evs & 0x02)
|
||||
{
|
||||
printf("event 0x02 triggered...\n");
|
||||
printf(" [Single Scheduler] tock received event %d/5!\n", i);
|
||||
}
|
||||
}
|
||||
printf(" [Single Scheduler] tock finished, exiting scheduler 0...\n");
|
||||
CoScheduler_Exit(0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void timer0(void)
|
||||
{
|
||||
printf("timer0-running...\n");
|
||||
}
|
||||
|
||||
static void timer1(void)
|
||||
{
|
||||
printf("timer1-running... %d\n", CoScheduler_CurLoad(CoTimer_SchedulerId()));
|
||||
// CoEvent_Notify(&gEvent, 0x01 | 0x02);
|
||||
}
|
||||
|
||||
/* Testing begins here */
|
||||
void *createTask(void *arg)
|
||||
{
|
||||
// CoTask_Create(tick, g_StackTick, sizeof(g_StackTick), 0);
|
||||
// CoTask_Create(tock, g_StackTock, sizeof(g_StackTock), 0);
|
||||
CoTask_Create(tick);
|
||||
CoTask_Create(tock);
|
||||
// timerHandle0 = CoTimer_CreateMs(timer0_entry, 100);
|
||||
// timerHandle1 = CoTimer_CreateMs(timer1_entry, 1000);
|
||||
}
|
||||
|
||||
#if (MUTI_THREAD_NUM == 1)
|
||||
void test_singleThread(void)
|
||||
static void test_single(void)
|
||||
{
|
||||
printf("=== [Test Single] Single Scheduler Automatic Mode ===\n");
|
||||
CoScheduler_Init(0, GetTimerUsec, 1000);
|
||||
createTask(NULL);
|
||||
gEvent.flag = 0;
|
||||
CoTask_Create(single_tick);
|
||||
CoTask_Create(single_tock);
|
||||
printf("[Main] Starting single scheduler 0...\n");
|
||||
CoScheduler_Start(0);
|
||||
printf("=== [Test Single] Completed! ===\n\n");
|
||||
}
|
||||
#else
|
||||
|
||||
/* --- 4. Multi-Scheduler Multi-Threaded Mode --- */
|
||||
static pthread_t threads[MUTI_THREAD_NUM];
|
||||
static pthread_t testThread;
|
||||
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static void thread_lock(void)
|
||||
@ -162,41 +282,298 @@ static void thread_unlock(void)
|
||||
pthread_mutex_unlock(&mutex);
|
||||
}
|
||||
|
||||
static void *multi_tick(void *arg)
|
||||
{
|
||||
for (int i = 1; i <= 5; i++)
|
||||
{
|
||||
CoTask_Wait(.ms = 100);
|
||||
printf(" [Multi-Core] Thread %d: tick %d/5...\n", CoTask_SchedulerId(), i);
|
||||
CoEvent_Notify(&gEvent, 0x02);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *multi_tock(void *arg)
|
||||
{
|
||||
for (int i = 1; i <= 5; i++)
|
||||
{
|
||||
uint32_t evs = CoTask_Wait(.pEvent = &gEvent, .ms = 500);
|
||||
if (evs & 0x02)
|
||||
{
|
||||
printf(" [Multi-Core] Thread %d: tock received event %d/5!\n", CoTask_SchedulerId(), i);
|
||||
}
|
||||
}
|
||||
printf(" [Multi-Core] tock finished, exiting schedulers...\n");
|
||||
for (int i = 0; i < MUTI_THREAD_NUM; i++)
|
||||
{
|
||||
CoScheduler_Exit(i);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *muti_thread_entry(void *arg)
|
||||
{
|
||||
CoScheduler_Start(*(int *)arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void test_mutiThread(void)
|
||||
static void test_multi(void)
|
||||
{
|
||||
printf("=== [Test Multi] Multi-Scheduler Multi-Threaded Mode ===\n");
|
||||
int threadId[MUTI_THREAD_NUM];
|
||||
Coroutine_Init(.lock = thread_lock, .unlock = thread_unlock);
|
||||
|
||||
for (int i = 0; i < MUTI_THREAD_NUM; i++)
|
||||
{
|
||||
CoScheduler_Init(i, GetTimerUsec, 1000, .lock=thread_lock, .unlock=thread_unlock);
|
||||
CoScheduler_Init(i, GetTimerUsec, 1000);
|
||||
}
|
||||
createTask(NULL);
|
||||
|
||||
gEvent.flag = 0;
|
||||
CoTask_Create(multi_tick, .schedulerId = 0);
|
||||
CoTask_Create(multi_tock, .schedulerId = 1);
|
||||
|
||||
for (int i = 0; i < MUTI_THREAD_NUM; i++)
|
||||
{
|
||||
threadId[i] = i;
|
||||
pthread_create(&threads[i], NULL, muti_thread_entry, &threadId[i]);
|
||||
|
||||
/* You can bind thread to specific core */
|
||||
/* So that each CoScheduler can run on different core */
|
||||
}
|
||||
|
||||
for (int i = 0; i < MUTI_THREAD_NUM; i++)
|
||||
{
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
printf("=== [Test Multi] Completed! ===\n\n");
|
||||
}
|
||||
|
||||
/* --- 5. Low-Power Hook Benchmark (CPU Load Comparison) --- */
|
||||
static uint64_t GetThreadCpuTimeUs(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
FILETIME createTime, exitTime, kernelTime, userTime;
|
||||
if (GetThreadTimes(GetCurrentThread(), &createTime, &exitTime, &kernelTime, &userTime))
|
||||
{
|
||||
ULARGE_INTEGER k, u;
|
||||
k.LowPart = kernelTime.dwLowDateTime;
|
||||
k.HighPart = kernelTime.dwHighDateTime;
|
||||
u.LowPart = userTime.dwLowDateTime;
|
||||
u.HighPart = userTime.dwHighDateTime;
|
||||
return (k.QuadPart + u.QuadPart) / 10; // 100ns units to us
|
||||
}
|
||||
return 0;
|
||||
#elif defined(CLOCK_THREAD_CPUTIME_ID)
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0)
|
||||
{
|
||||
return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return (uint64_t)clock() * 1000000ULL / CLOCKS_PER_SEC;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void *hook_bench_worker(void *arg)
|
||||
{
|
||||
/* Wait for 400 ms */
|
||||
CoTask_Wait(.ms = 400);
|
||||
CoScheduler_Exit(0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_hook(void)
|
||||
{
|
||||
printf("=== [Test Low-Power Hook Benchmark: 400ms Idle] ===\n");
|
||||
|
||||
/* --- Phase 1: WITHOUT Hook (Busy-Spin) --- */
|
||||
printf("[Phase 1] Testing WITHOUT Hook (CPU busy-spin)...\n");
|
||||
CoScheduler_Init(0, GetTimerUsec, 1000); // No hooks
|
||||
CoTask_Create(hook_bench_worker);
|
||||
|
||||
uint64_t wall_start_1 = GetTimerUsec();
|
||||
uint64_t cpu_start_1 = GetThreadCpuTimeUs();
|
||||
|
||||
CoScheduler_Start(0);
|
||||
|
||||
uint64_t cpu_end_1 = GetThreadCpuTimeUs();
|
||||
uint64_t wall_end_1 = GetTimerUsec();
|
||||
|
||||
double wall_ms_1 = (double)(wall_end_1 - wall_start_1) / 1000.0;
|
||||
double cpu_ms_1 = (double)(cpu_end_1 - cpu_start_1) / 1000.0;
|
||||
double cpu_load_1 = (wall_ms_1 > 0) ? (cpu_ms_1 / wall_ms_1 * 100.0) : 0.0;
|
||||
if (cpu_load_1 > 100.0) cpu_load_1 = 100.0;
|
||||
|
||||
printf(" -> Wall Time: %.1f ms, Active CPU Time: %.1f ms, CPU Load: %.1f%%\n\n",
|
||||
wall_ms_1, cpu_ms_1, cpu_load_1);
|
||||
|
||||
/* --- Phase 2: WITH Hook (Interruptible Low-Power Sleep) --- */
|
||||
printf("[Phase 2] Testing WITH Low-Power Hook (Interruptible sleep)...\n");
|
||||
CoScheduler_Init(0, GetTimerUsec, 1000);
|
||||
CoScheduler_BindDefaultHooks(0);
|
||||
CoTask_Create(hook_bench_worker);
|
||||
|
||||
uint64_t wall_start_2 = GetTimerUsec();
|
||||
uint64_t cpu_start_2 = GetThreadCpuTimeUs();
|
||||
|
||||
CoScheduler_Start(0);
|
||||
|
||||
uint64_t cpu_end_2 = GetThreadCpuTimeUs();
|
||||
uint64_t wall_end_2 = GetTimerUsec();
|
||||
|
||||
CoScheduler_UnbindDefaultHooks(0);
|
||||
|
||||
double wall_ms_2 = (double)(wall_end_2 - wall_start_2) / 1000.0;
|
||||
double cpu_ms_2 = (double)(cpu_end_2 - cpu_start_2) / 1000.0;
|
||||
double cpu_load_2 = (wall_ms_2 > 0) ? (cpu_ms_2 / wall_ms_2 * 100.0) : 0.0;
|
||||
if (cpu_load_2 > 100.0) cpu_load_2 = 100.0;
|
||||
|
||||
printf(" -> Wall Time: %.1f ms, Active CPU Time: %.1f ms, CPU Load: %.1f%%\n\n",
|
||||
wall_ms_2, cpu_ms_2, cpu_load_2);
|
||||
|
||||
printf("==================== Benchmark Comparison ====================\n");
|
||||
printf(" WITHOUT Hook : CPU Load = %5.1f%% (CPU was churning millions of cycles)\n", cpu_load_1);
|
||||
printf(" WITH Hook : CPU Load = %5.1f%% (CPU entered kernel sleep, 0%% drain)\n", cpu_load_2);
|
||||
printf(" Energy Saving: CPU active execution reduced by > %.1f%%!\n",
|
||||
(cpu_ms_1 > cpu_ms_2) ? ((cpu_ms_1 - cpu_ms_2) / cpu_ms_1 * 100.0) : 99.0);
|
||||
printf("==============================================================\n\n");
|
||||
}
|
||||
|
||||
/* --- 6. High-Density Asynchronous I/O Test --- */
|
||||
#define TEST_IO_PORT 19876
|
||||
|
||||
static void *io_server_task(void *arg)
|
||||
{
|
||||
printf(" [IO Server] Starting non-blocking server on 127.0.0.1:%d...\n", TEST_IO_PORT);
|
||||
int listen_fd = CoSocket_Create(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_fd < 0)
|
||||
{
|
||||
printf(" [IO Server] Failed to create socket!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int opt = 1;
|
||||
#if defined(_WIN32)
|
||||
setsockopt((SOCKET)listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
|
||||
#else
|
||||
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(TEST_IO_PORT);
|
||||
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
|
||||
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
|
||||
{
|
||||
printf(" [IO Server] Failed to bind port!\n");
|
||||
CoSocket_Close(listen_fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
listen(listen_fd, 5);
|
||||
printf(" [IO Server] Listening, waiting for client accept (cooperative yield)...\n");
|
||||
|
||||
int client_fd = CoSocket_Accept(listen_fd, NULL, NULL, 5000);
|
||||
if (client_fd < 0)
|
||||
{
|
||||
printf(" [IO Server] Accept failed or timed out! (%d)\n", client_fd);
|
||||
CoSocket_Close(listen_fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
printf(" [IO Server] Client connected! Reading message (cooperative yield)...\n");
|
||||
char buf[128] = {0};
|
||||
int n = CoSocket_Read(client_fd, buf, sizeof(buf) - 1, 15000);
|
||||
if (n > 0)
|
||||
{
|
||||
printf(" [IO Server] Received client message: \"%s\"\n", buf);
|
||||
const char *echo_prefix = "ECHO: ";
|
||||
char resp[256];
|
||||
snprintf(resp, sizeof(resp), "%s%s", echo_prefix, buf);
|
||||
printf(" [IO Server] Sending echo response: \"%s\"...\n", resp);
|
||||
CoSocket_Write(client_fd, resp, strlen(resp), 5000);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" [IO Server] Read failed or timed out! (n = %d)\n", n);
|
||||
}
|
||||
|
||||
CoSocket_Close(client_fd);
|
||||
CoSocket_Close(listen_fd);
|
||||
printf(" [IO Server] Server task completed successfully, exiting scheduler!\n");
|
||||
CoScheduler_Exit(0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *io_client_task(void *arg)
|
||||
{
|
||||
/* Allow server to start and listen first */
|
||||
CoTask_Wait(.ms = 50);
|
||||
|
||||
printf(" [IO Client] Connecting to 127.0.0.1:%d (cooperative yield)...\n", TEST_IO_PORT);
|
||||
int client_fd = CoSocket_Create(AF_INET, SOCK_STREAM, 0);
|
||||
if (client_fd < 0)
|
||||
{
|
||||
printf(" [IO Client] Failed to create client socket!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(TEST_IO_PORT);
|
||||
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
|
||||
int ret = CoSocket_Connect(client_fd, &addr, sizeof(addr), 5000);
|
||||
if (ret != 0)
|
||||
{
|
||||
printf(" [IO Client] Connect failed!\n");
|
||||
CoSocket_Close(client_fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *msg = "Hello High-Throughput Coroutine IO!";
|
||||
printf(" [IO Client] Connected! Sending message: \"%s\"...\n", msg);
|
||||
CoTask_Wait(.ms = 10000);
|
||||
CoSocket_Write(client_fd, msg, strlen(msg), 5000);
|
||||
|
||||
char resp[256] = {0};
|
||||
printf(" [IO Client] Waiting for echo response (cooperative yield)...\n");
|
||||
int n = CoSocket_Read(client_fd, resp, sizeof(resp) - 1, 5000);
|
||||
if (n > 0)
|
||||
{
|
||||
printf(" [IO Client] Received Echo response: \"%s\"!\n", resp);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" [IO Client] Read response failed or timed out! (%d)\n", n);
|
||||
}
|
||||
|
||||
CoSocket_Close(client_fd);
|
||||
printf(" [IO Client] Client task finished!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_io(void)
|
||||
{
|
||||
printf("=== [Test Asynchronous Socket I/O Multiplexing] ===\n");
|
||||
CoScheduler_Init(0, GetTimerUsec, 1000);
|
||||
CoIo_AttachScheduler(0);
|
||||
|
||||
CoTask_Create(io_server_task);
|
||||
CoTask_Create(io_client_task);
|
||||
|
||||
printf("[Main] Starting single-threaded scheduler with cooperative I/O Reactor...\n");
|
||||
CoScheduler_Start(0);
|
||||
|
||||
CoIo_DetachScheduler(0);
|
||||
printf("=== [Test Asynchronous Socket I/O] Completed! ===\n\n");
|
||||
}
|
||||
|
||||
static void test_base(void)
|
||||
{
|
||||
#if (MUTI_THREAD_NUM == 1)
|
||||
test_singleThread();
|
||||
#else
|
||||
test_mutiThread();
|
||||
#endif
|
||||
test_manual();
|
||||
test_shared();
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
@ -209,10 +586,15 @@ static void usage(void)
|
||||
"Usage: coroutine [opt] [arg] ...\n"
|
||||
"\n"
|
||||
"options:\n"
|
||||
" -e <execute> Specifies the function to execute, the default is the <base> test\n"
|
||||
" <base> Test base function\n"
|
||||
" -e <execute> Specifies the test mode to execute:\n"
|
||||
" <manual> Test manual point-to-point stepping (libco style)\n"
|
||||
" <shared> Test shared stack (copy-on-suspend)\n"
|
||||
" <single> Test single scheduler automatic mode\n"
|
||||
" <multi> Test multi-scheduler multi-threaded mode\n"
|
||||
" <hook> Test low-power hook comparison (CPU load benchmark)\n"
|
||||
" <io> Test high-throughput asynchronous socket I/O\n"
|
||||
" <base> Test base default functions (manual & shared)\n"
|
||||
" <ut> Unit test\n"
|
||||
" ...\n"
|
||||
" -h Print help\n"
|
||||
" -v Print version\n"
|
||||
" -u [<period>] Unit test period, unit ms, the default is 1000ms\n"
|
||||
@ -225,6 +607,9 @@ static int test(int argc, char *argv[])
|
||||
char *execute = NULL;
|
||||
int ut_period = 1000;
|
||||
|
||||
/* Initialize default allocator for tests */
|
||||
Coroutine_Init(.malloc = malloc, .free = free);
|
||||
|
||||
/* reset getopt */
|
||||
command_opt_init();
|
||||
|
||||
@ -257,7 +642,31 @@ static int test(int argc, char *argv[])
|
||||
|
||||
if (execute)
|
||||
{
|
||||
if (!strcmp(execute, "base"))
|
||||
if (!strcmp(execute, "manual"))
|
||||
{
|
||||
test_manual();
|
||||
}
|
||||
else if (!strcmp(execute, "shared"))
|
||||
{
|
||||
test_shared();
|
||||
}
|
||||
else if (!strcmp(execute, "single"))
|
||||
{
|
||||
test_single();
|
||||
}
|
||||
else if (!strcmp(execute, "multi"))
|
||||
{
|
||||
test_multi();
|
||||
}
|
||||
else if (!strcmp(execute, "hook"))
|
||||
{
|
||||
test_hook();
|
||||
}
|
||||
else if (!strcmp(execute, "io"))
|
||||
{
|
||||
test_io();
|
||||
}
|
||||
else if (!strcmp(execute, "base"))
|
||||
{
|
||||
test_base();
|
||||
}
|
||||
@ -273,6 +682,12 @@ static int test(int argc, char *argv[])
|
||||
printf("create task %d\r\n", task_create(ut_period, unitt_task));
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Unknown execute option: %s\n", execute);
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user