wxing2008666 / s_task

awaitable coroutine library for C

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

s_task - awaitable coroutine library for C

中文版文档 (chinese version)

Table of content

Features

  • "s_task" is a coroutine library written in pure C and asm (from boost library), without C++ required.
  • supports various platforms, such as windows, linux, android, macos, stm32, stm8, arduino, etc.
  • supports keywords __await__ and __async__ . 🚩 For functions that may switch to other tasks, call it with 1st parameter __await__, for the caller function of which, define the 1st parameter as __async__, which make it is clear to know about context switching.
  • works with libuv for network programming.
  • "chan", "mutex" and "event" for communication between tasks.
  • Special features on embedded platfrom (stm32/stm8/m051)
    • no dynamical memory allocation
    • very small memory footprint ( increased by ROM<1.5K, RAM<128 bytes + task stack size)

Examples

Example 1 - simple task creation

#include <stdio.h>
#include "s_task.h"

void* g_stack_main[64 * 1024];
void* g_stack0[64 * 1024];
void* g_stack1[64 * 1024];

void sub_task(__async__, void* arg) {
    int i;
    int n = (int)(size_t)arg;
    for (i = 0; i < 5; ++i) {
        printf("task %d, delay seconds = %d, i = %d\n", n, n, i);
        s_task_msleep(__await__, n * 1000);
        //s_task_yield(__await__);
    }
}

void main_task(__async__, void* arg) {
    int i;
    s_task_create(g_stack0, sizeof(g_stack0), sub_task, (void*)1);
    s_task_create(g_stack1, sizeof(g_stack1), sub_task, (void*)2);

    for (i = 0; i < 4; ++i) {
        printf("task_main arg = %p, i = %d\n", arg, i);
        s_task_yield(__await__);
    }

    s_task_join(__await__, g_stack0);
    s_task_join(__await__, g_stack1);
}

int main(int argc, char* argv) {
    __init_async__;

    s_task_init_system();
    s_task_create(g_stack_main, sizeof(g_stack_main), main_task, (void*)(size_t)argc);
    s_task_join(__await__, g_stack_main);
    printf("all task is over\n");
    return 0;
}

Example 2 - asynchronized http client without callback function.

void main_task(__async__, void *arg) {
    uv_loop_t* loop = (uv_loop_t*)arg;

    const char *HOST = "baidu.com";
    const unsigned short PORT = 80;

    //<1> resolve host
    struct addrinfo* addr = s_uv_getaddrinfo(__await__,
        loop,
        HOST,
        NULL,
        NULL);
    if (addr == NULL) {
        fprintf(stderr, "can not resolve host %s\n", HOST);
        goto out0;
    }

    if (addr->ai_addr->sa_family == AF_INET) {
        struct sockaddr_in* sin = (struct sockaddr_in*)(addr->ai_addr);
        sin->sin_port = htons(PORT);
    }
    else if (addr->ai_addr->sa_family == AF_INET6) {
        struct sockaddr_in6* sin = (struct sockaddr_in6*)(addr->ai_addr);
        sin->sin6_port = htons(PORT);
    }

    //<2> connect
    uv_tcp_t tcp_client;
    int ret = uv_tcp_init(loop, &tcp_client);
    if (ret != 0)
        goto out1;
    ret = s_uv_tcp_connect(__await__, &tcp_client, addr->ai_addr);
    if (ret != 0)
        goto out2;

    //<3> send request
    const char *request = "GET / HTTP/1.0\r\n\r\n";
    uv_stream_t* tcp_stream = (uv_stream_t*)&tcp_client;
    s_uv_write(__await__, tcp_stream, request, strlen(request));

    //<4> read response
    ssize_t nread;
    char buf[1024];
    while (true) {
        ret = s_uv_read(__await__, tcp_stream, buf, sizeof(buf), &nread);
        if (ret != 0) break;

        // output response to console
        fwrite(buf, 1, nread, stdout);
    }

    //<5> close connections
out2:;
    s_uv_close(__await__, (uv_handle_t*)&tcp_client);
out1:;
    uv_freeaddrinfo(addr);
out0:;
}

Example 3 - control LED with multitasking on ardinuo

#include <stdio.h>
#include "src/s_task/s_task.h"

//This program demonstrates three tasks:
// 1) main_task - 
//    Wait 10 seconds and set flag g_exit. 
//    After all tasks finished, set LED on always.
// 2) sub_task_fast_blinking -
//    Set led blinking fast
// 3) sub_task_set_low -
//    Set led off for 1 second, and then blinking for 3 seconds.


void setup() {
    // put your setup code here, to run once:
    pinMode(LED_BUILTIN, OUTPUT);
}


char g_stack0[384];
char g_stack1[384];
volatile bool g_is_low = false;
volatile bool g_exit = false;

void sub_task_fast_blinking(__async__, void* arg) {
    while(!g_exit) {
        if(!g_is_low)
            digitalWrite(LED_BUILTIN, HIGH); // turn the LED on

        s_task_msleep(__await__, 50);        // wait for 50 milliseconds
        digitalWrite(LED_BUILTIN, LOW);      // turn the LED off
        s_task_msleep(__await__, 50);        // wait for 50 milliseconds
    }    
}

void sub_task_set_low(__async__, void* arg) {
    while(!g_exit) {
        g_is_low = true;                     // stop fast blinking
        digitalWrite(LED_BUILTIN, LOW);      // turn the LED off
        s_task_sleep(__await__, 1);          // wait for 1 second
        g_is_low = false;                    // start fast blinking
        s_task_sleep(__await__, 3);          // wait for 3 seconds
    }    
}

void main_task(__async__, void* arg) {
    // create two sub tasks
    s_task_create(g_stack0, sizeof(g_stack0), sub_task_fast_blinking, NULL);
    s_task_create(g_stack1, sizeof(g_stack1), sub_task_set_low, NULL);

    // wait for 10 seconds
    s_task_sleep(__await__, 10);
    g_exit = true;

    // wait two sub tasks return
    s_task_join(__await__, g_stack0);
    s_task_join(__await__, g_stack1);
}

void loop() {
    __init_async__;
    
    s_task_init_system();
    main_task(__await__, NULL);

    // turn the LED on always
    digitalWrite(LED_BUILTIN, HIGH);
    while(1);
}

Compatibility

"s_task" can run as standalone coroutine library, or work with library libuv (compiling with macro USE_LIBUV).

Platform coroutine libuv
Windows ✔️ ✔️
Linux ✔️ ✔️
MacOS ✔️ ✔️
Android ✔️ ✔️
MingW (https://www.msys2.org/) ✔️ ✔️
ARMv6-M (M051) ✔️
ARMv7-M (STM32F103, STM32F302) ✔️
STM8 (STM8S103, STM8L051F3) ✔️
Arduino UNO (AVR MEGA328P) ✔️

linux tested on

  • i686 (ubuntu-16.04)
  • x86_64 (centos-8.1)
  • arm (raspiberry 32bit)
  • aarch64 (① raspiberry 64bit, ② ubuntu 14.04 on huawei Kunpeng920)
  • mipsel (openwrt ucLinux 3.10.14 for MT7628)
  • mips64 (fedora for loongson 3A-4000)

Build

Linux / MacOS / MingW(MSYS2)

cd build
cmake .
make

Windows and other platforms

Platform Project Tool chain
Windows build\windows\s_task.sln visual studio 2019
Android build\android\cross_build_arm*.sh android ndk 20, API level 21 (test in termux)
STM8S103 build\stm8s103\Project.eww IAR workbench for STM8
STM8L051F3 build\stm8l05x\Project.eww IAR workbench for STM8
STM32F103 build\stm32f103\Project.uvproj Keil uVision5
STM32F302 build\stm32f302\Project.uvporj Keil uVision5
M051 build\m051\Project.uvporj Keil uVision5
ATmega328P build\atmega328p\atmega328p.atsln Atmel Studio 7.0
Arduino UNO build\arduino\arduino.ino Arduino IDE

How to use in your project?

On linux/unix like system, after cmake build, you may get the libraries for your project

  • add libs_task.a to your project
  • #include "s_task.h"
  • build with predefined macro USE_LIBUV

On arduino, copy all source files (*.h, *.c) in folder "include" and "src" into your arduino project's subfolder src/s_task/. Please take a look at the folder structure of "build/arduino/".

On windows or other system, please find the project in folder "build" as the project template.

API

Task

/*
 * Return values -- 
 * For all functions marked by __async__ and hava an int return value, will
 *     return 0 on waiting successfully,
 *     return -1 on waiting cancalled by s_task_cancel_wait() called by other task.
 */

/* Function type for task entrance */
typedef void(*s_task_fn_t)(__async__, void *arg);

/* Create a new task */
void s_task_create(void *stack, size_t stack_size, s_task_fn_t entry, void *arg);

/* Wait a task to exit */
int s_task_join(__async__, void *stack);

/* Sleep in milliseconds */
int s_task_msleep(__async__, uint32_t msec);

/* Sleep in seconds */
int s_task_sleep(__async__, uint32_t sec);

/* Yield current task */
void s_task_yield(__async__);

/* Cancel task waiting and make it running */
void s_task_cancel_wait(void* stack);

Chan

/* 
 * macro: Declare the chan variable
 *    name: name of the chan
 *    TYPE: type of element in the chan
 *    count: max count of element buffer in the chan
 */
s_chan_declare(name,TYPE,count);

/* 
 * macro: Initialize the chan (parameters same as what's in s_declare_chan).
 * To make a chan, we need to use "s_chan_declare" and then call "s_chan_init".
 */
s_chan_init(name,TYPE,count);

/* Put element into chan */
void s_chan_put(__async__, s_chan_t *chan, const void *in_object);

/* Get element from chan */
void s_chan_get(__async__, s_chan_t *chan, void *out_object);

Mutex

/* Initialize a mutex */
void s_mutex_init(s_mutex_t *mutex);

/* Lock the mutex */
int s_mutex_lock(__async__, s_mutex_t *mutex);

/* Unlock the mutex */
void s_mutex_unlock(s_mutex_t *mutex);

Event

/* Initialize a wait event */
void s_event_init(s_event_t *event);

/* Wait event */
int s_event_wait(__async__, s_event_t *event);

/* Set event */
void s_event_set(s_event_t *event);

/* Wait event with timeout */
int s_event_wait_msec(__async__, s_event_t *event, uint32_t msec);

/* Wait event with timeout */
int s_event_wait_sec(__async__, s_event_t *event, uint32_t sec);

Specials on embedded platform

API on embedded platform

Chan for interrupt (for embedded only, STM8/STM32/M051/Arduino)

/* Put element into chan and wait interrupt to read the chan */
void s_chan_put__to_irq(__async__, s_chan_t *chan, const void *in_object);

/* Wait interrupt to write the chan and then get element from chan */
void s_chan_get__from_irq(__async__, s_chan_t *chan, void *out_object);

/*
 * Interrupt writes element into the chan
 *  return 0 on chan element was written
 *  return -1 on chan is full
 */
int s_chan_put__in_irq(s_chan_t *chan, const void *in_object);

/*
 * Interrupt reads element from chan
 *  return 0 on chan element was read
 *  return -1 on chan is empty
 */
int s_chan_get__in_irq(s_chan_t *chan, void *out_object);

Event for interrupt (for embedded only, STM8/STM32/M051/Arduino)

/* Set event in interrupt */
void s_event_set__in_irq(s_event_t *event);

/* 
 * Wait event from irq, disable irq before call this function!
 *   S_IRQ_DISABLE()
 *   ...
 *   s_event_wait__from_irq(...)
 *   ...
 *   S_IRQ_ENABLE()
 */
int s_event_wait__from_irq(__async__, s_event_t *event);

/* 
 * Wait event from irq, disable irq before call this function!
 *   S_IRQ_DISABLE()
 *   ...
 *   s_event_wait_msec__from_irq(...)
 *   ...
 *   S_IRQ_ENABLE()
 */
int s_event_wait_msec__from_irq(__async__, s_event_t *event, uint32_t msec);

/* 
 * Wait event from irq, disable irq before call this function!
 *   S_IRQ_DISABLE()
 *   ...
 *   s_event_wait_sec__from_irq(...)
 *   ...
 *   S_IRQ_ENABLE()
 */
int s_event_wait_sec__from_irq(__async__, s_event_t *event, uint32_t sec);
Low power mode

If there's no code in function "my_on_idle", the program will run in busy wait mode, which may cause CPU 100% occupied. But we can avoid this and support low power mode by adding correct sleeping instructions in function my_on_idle.

Now we have do that on Windows/Linux/MacOS and Android.

On embedded platform without OS, we may not fully implement low power mode. Please check function "my_on_idle" for the corresponding platform if you want to optimize the power consumption.

void my_on_idle(uint64_t max_idle_ms) {
    /* Add code here to make CPU run into sleep mode,
       the maximum sleeping time is "max_idle_ms" milliseconds. */
}

How to make port?

Please find document here

Contact

使用中有任何问题或建议,欢迎QQ加群 567780316 交流。

s_task交流群

VS.

About

awaitable coroutine library for C

License:Other


Languages

Language:C 83.5%Language:Assembly 7.3%Language:C++ 4.6%Language:HTML 2.5%Language:Rich Text Format 1.2%Language:Python 0.3%Language:Makefile 0.2%Language:CMake 0.1%Language:Shell 0.1%Language:M4 0.1%Language:Batchfile 0.1%Language:Objective-C 0.0%Language:JavaScript 0.0%