Task

A task, along with the scheduler, forms the basis of the Mynewt OS. A task consists of two basic elements: a task stack and a task function. The task function is basically a forever loop, waiting for some "event" to wake it up. There are two methods used to signal a task that it has work to do: event queues and semaphores .

The Mynewt OS is a multi-tasking, preemptive OS. Every task is assigned a task priority (from 0 to 255), with 0 being the highest priority task. If a higher priority task than the current task wants to run, the scheduler preempts the currently running task and switches context to the higher priority task. This is just a fancy way of saying that the processor stack pointer now points to the stack of the higher priority task and the task resumes execution where it left off.

Tasks run to completion unless they are preempted by a higher priority task. The developer must ensure that tasks eventually "sleep"; otherwise lower priority tasks will never get a chance to run (actually, any task lower in priority than the task that never sleeps). A task will be put to sleep in the following cases: it puts itself to sleep using os_time_delay(), it waits on an event queue
which is empty or attempts to obtain a mutex or a semaphore that is currently owned by another task.

Note that other sections of the manual describe these OS features in more detail.

Description

In order to create a task two data structures need to be defined: the task object (struct os_task) and its associated stack. Determining the stack size can be a bit tricky; generally developers should not declare large local variables on the stack so that task stacks can be of limited size. However, all applications are different and the developer must choose the stack size accordingly. NOTE: be careful when declaring your stack! The stack is in units of os_stack_t sized elements (generally 32-bits). Looking at the example given below and assuming os_stack_t is defined to be a 32-bit unsigned value, "my_task_stack" will use 256 bytes.

A task must also have an associated "task function". This is the function that will be called when the task is first run. This task function should never return!

In order to inform the Mynewt OS of the new task and to have it added to the scheduler, the os_task_init() function is called. Once os_task_init() is called, the task is made ready to run and is added to the active task list. Note that a task can be initialized (started) before or after the os has started (i.e. before os_start() is called) but must be initialized after the os has been initialized (i.e. 'os_init()' has been called). In most of the examples and current Mynewt projects, the os is initialized, tasks are initialized, and the the os is started. Once the os has started, the highest priority task will be the first task set to run.

Information about a task can be obtained using the os_task_info_get_next() API. Developers can walk the list of tasks to obtain information on all created tasks. This information is of type os_task_info and is described below.

The following is a very simple example showing a single application task. This task simply toggles an LED at a one second interval.

/* Create a simple "project" with a task that blinks a LED every second */

/* Define task stack and task object */
#define MY_TASK_PRI         (OS_TASK_PRI_HIGHEST) 
#define MY_STACK_SIZE       (64) 
struct os_task my_task; 
os_stack_t my_task_stack[MY_STACK_SIZE]; 

/* This is the task function */
void my_task_func(void *arg) {
    /* Set the led pin as an output */
    hal_gpio_init_out(LED_BLINK_PIN, 1);

    /* The task is a forever loop that does not return */
    while (1) {
        /* Wait one second */ 
        os_time_delay(1000);

        /* Toggle the LED */ 
        hal_gpio_toggle(LED_BLINK_PIN);
    }
}

/* This is the main function for the project */
int main(int argc, char **argv) 
{

    /* Perform system and package initialization */
    sysinit();

    /* Initialize the task */
    os_task_init(&my_task, "my_task", my_task_func, NULL, MY_TASK_PRIO, 
                 OS_WAIT_FOREVER, my_task_stack, MY_STACK_SIZE);

    /*  Process events from the default event queue.  */
    while (1) {
       os_eventq_run(os_eventq_dflt_get());
    }
    /* main never returns */  
}

Data structures

/* The highest and lowest task priorities */
#define OS_TASK_PRI_HIGHEST         (0)
#define OS_TASK_PRI_LOWEST          (0xff)

/* Task states */
typedef enum os_task_state {
    OS_TASK_READY = 1, 
    OS_TASK_SLEEP = 2
} os_task_state_t;

/* Task flags */
#define OS_TASK_FLAG_NO_TIMEOUT     (0x0001U)
#define OS_TASK_FLAG_SEM_WAIT       (0x0002U)
#define OS_TASK_FLAG_MUTEX_WAIT     (0x0004U)

typedef void (*os_task_func_t)(void *);

#define OS_TASK_MAX_NAME_LEN (32)


struct os_task {
    os_stack_t *t_stackptr;
    os_stack_t *t_stacktop;

    uint16_t t_stacksize;
    uint16_t t_flags;

    uint8_t t_taskid;
    uint8_t t_prio;
    uint8_t t_state;
    uint8_t t_pad;

    char *t_name;
    os_task_func_t t_func;
    void *t_arg;

    void *t_obj;

    struct os_sanity_check t_sanity_check; 

    os_time_t t_next_wakeup;
    os_time_t t_run_time;
    uint32_t t_ctx_sw_cnt;

    /* Global list of all tasks, irrespective of run or sleep lists */
    STAILQ_ENTRY(os_task) t_os_task_list;

    /* Used to chain task to either the run or sleep list */ 
    TAILQ_ENTRY(os_task) t_os_list;

    /* Used to chain task to an object such as a semaphore or mutex */
    SLIST_ENTRY(os_task) t_obj_list;
};
Element Description
t_stackptr Current stack pointer
t_stacktop The address of the top of the task stack. The stack grows downward
t_stacksize The size of the stack, in units of os_stack_t (not bytes!)
t_flags Task flags (see flag definitions)
t_taskid A numeric id assigned to each task
t_prio The priority of the task. The lower the number, the higher the priority
t_state The task state (see state definitions)
t_pad padding (for alignment)
t_name Name of task
t_func Pointer to task function
t_obj Generic object used by mutexes and semaphores when the task is waiting on a mutex or semaphore
t_sanity_check Sanity task data structure
t_next_wakeup OS time when task is next scheduled to wake up
t_run_time The amount of os time ticks this task has been running
t_ctx_sw_cnt The number of times that this task has been run
t_os_task_list List pointer for global task list. All tasks are placed on this list
t_os_list List pointer used by either the active task list or the sleeping task list
t_obj_list List pointer for tasks waiting on a semaphore or mutex


struct os_task_info {
    uint8_t oti_prio;
    uint8_t oti_taskid;
    uint8_t oti_state;
    uint8_t oti_flags;
    uint16_t oti_stkusage;
    uint16_t oti_stksize;
    uint32_t oti_cswcnt;
    uint32_t oti_runtime;
    os_time_t oti_last_checkin;
    os_time_t oti_next_checkin;

    char oti_name[OS_TASK_MAX_NAME_LEN];
};
Element Description
oti_prio Task priority
oti_taskid Task id
oti_state Task state
oti_flags Task flags
oti_stkusage Amount of stack used by the task (in os_stack_t units)
oti_stksize The size of the stack (in os_stack_t units)
oti_cswcnt The context switch count
oti_runtime The amount of time that the task has run (in os time ticks)
oti_last_checkin The time (os time) at which this task last checked in to the sanity task
oti_next_checkin The time (os time) at which this task last checked in to the sanity task
oti_name Name of the task


List of Functions

The functions available in task are:

Function Description
os_task_init Called to create a task. This adds the task object to the list of ready to run tasks.
os_task_count Returns the number of tasks that have been created.
os_task_info_get_next Populates the os task info structure given with task information.
os_task_remove Removes a task from the task list.