Skip to content

Processes

Overview

MiraOS applications are typically written by using processes. Processes in MiraOS are built on top of a light-weight multi-threading mechanism called protothreads. Protothreads are very light-weight with low process-switching overhead, however each process does not have its own stack, so all variables that need to preserve their content over process switches need to be declared as static.

Processes are scheduled cooperatively, which means that each process is responsible for voluntarily passing control back to the operating system without executing for too long. Hence, the application developer must make sure that operations that are long (more than a few ms) are split into multiple process schedulings, allowing such operations to resume after the operating system has executed other processes.

Defining a process

Before a process can be defined, it must first be declared at the top of the source file.

A process is declared using the PROCESS() macro that takes two arguments: one is the process' symbol, used to refer to the process later on, and the other is a string with the name of the process.

#include "mira.h" // Main include file for MiraOS.
#include <stdio.h> // We want printf later.

PROCESS(my_proc, "My process");

We can now define our process:

PROCESS_THREAD(my_proc, ev, data)
{
  PROCESS_BEGIN();

  printf("This is my process\n");

  PROCESS_END();
}

The PROCESS_THREAD() macro takes the identifier of the processes specified by the PROCESS() declaration we made earlier. The ev and data arguments are for the event handling (the data argument can also be used when starting a process for passing data into the process by the process_start() system call), more about event handling in the Events section. For now, we just ignore the event handling, since it is not needed for this simple example.

All processes must have the PROCESS_BEGIN() statement, it marks the start of the process execution. While it's possible to place code before this statement, developers should typically not do so except for variable definitions.

The process shall end with the PROCESS_END() statement. All code will go between these statements. In this simple example, the process just calls to printf then implicitly stops the process.

Starting a process

A process can be started using the process_start() system call. This system call takes two arguments: first the process handler, then a data pointer that can be used to pass data to the newly started process.

process_start(&my_proc, NULL);

This starts the previously declared process.

Stopping a process

A process can be stopped in any of three different ways:

  1. The process may implicitly exit by reaching the PROCESS_END() statement.
  2. The process exit explicitly by calling PROCESS_EXIT() in the PROCESS_THREAD body.
  3. Another process kills the process by calling the process_exit() system call.

After a process has been stopped, it may be started again by calling the process_start() system call.

Yielding and pausing

A process may voluntarily release control to the scheduler. This can be done by calling PROCESS_PAUSE(). When PROCESS_PAUSE() is called, the scheduler immediately reschedules the paused process for execution and delivers any unprocessed events to other processes. Execution of the paused process is resumed after the execution of any other scheduled processes has been performed.

Alternatively, a process can yield using the PROCESS_YIELD() call. This will also return control to the scheduler, except this time the process will not be executed again until an event is sent to the process.

PROCESS_THREAD(some_proc, ev, data)
{
  PROCESS_BEGIN();

while (1) {
  do_some_operations();
  PROCESS_PAUSE();
  // execution will resume as soon as scheduler
  // has executed other processes

  do_some_more_operations();
  PROCESS_YIELD();
  // execution will resume when an event
  // is delivered to the process
}

PROCESS_END();
}

Waiting for event or condition

There also exists two macros, PROCESS_WAIT_UNTIL(c)and PROCESS_WAIT_WHILE(c) which will make a process wait for a condition to be true. However it is not guaranteed that the process will yield control back to the scheduler when using these macros. These functions are not intended to check the event parameter ev. To check the event, PROCESS_YIELD_UNTIL() can be used since it always yields before checking.

The example below will end up in a blocking busy/wait until the condition becomes true if the PROCESS_WAIT_UNTIL(c)call never yields control to the scheduler, the same goes for the PROCESS_WAIT_WHILE(c) function. This can cause the system to not function properly. It is therefore important that the condition is cleared during execution.

PROCESS_THREAD(some_proc, ev, data)
{
 PROCESS_BEGIN();

 while(1) {
     PROCESS_WAIT_UNTIL(cond);
     do_some_operations();
     cond = 0;
 }
 PROCESS_END();
}

Defines

PROCESS_ERR_OK

#define PROCESS_ERR_OK

Return value indicating that an operation was successful.

This value is returned to indicate that an operation was successful.

PROCESS_ERR_FULL

#define PROCESS_ERR_FULL

Return value indicating that the event queue was full.

This value is returned from process_post() to indicate that the event queue was full and that an event could not be posted.

PROCESS_CONF_NUMEVENTS

#define PROCESS_CONF_NUMEVENTS

PROCESS_EVENT_NONE

#define PROCESS_EVENT_NONE

PROCESS_EVENT_INIT

#define PROCESS_EVENT_INIT

PROCESS_EVENT_POLL

#define PROCESS_EVENT_POLL

PROCESS_EVENT_EXIT

#define PROCESS_EVENT_EXIT

PROCESS_EVENT_SERVICE_REMOVED

#define PROCESS_EVENT_SERVICE_REMOVED

PROCESS_EVENT_CONTINUE

#define PROCESS_EVENT_CONTINUE

PROCESS_EVENT_MSG

#define PROCESS_EVENT_MSG

PROCESS_EVENT_EXITED

#define PROCESS_EVENT_EXITED

PROCESS_EVENT_TIMER

#define PROCESS_EVENT_TIMER

PROCESS_EVENT_COM

#define PROCESS_EVENT_COM

PROCESS_EVENT_MAX

#define PROCESS_EVENT_MAX

PROCESS_BROADCAST

#define PROCESS_BROADCAST

PROCESS_ZOMBIE

#define PROCESS_ZOMBIE

PROCESS_BEGIN

#define PROCESS_BEGIN()

Define the beginning of a process.

This macro defines the beginning of a process, and must always appear in a PROCESS_THREAD() definition. The PROCESS_END() macro must come at the end of the process.

PROCESS_END

#define PROCESS_END()

Define the end of a process.

This macro defines the end of a process. It must appear in a PROCESS_THREAD() definition and must always be included. The process exits when the PROCESS_END() macro is reached.

PROCESS_WAIT_EVENT

#define PROCESS_WAIT_EVENT()

Wait for an event to be posted to the process.

This macro blocks the currently running process until the process receives an event.

PROCESS_WAIT_EVENT_UNTIL

#define PROCESS_WAIT_EVENT_UNTIL(c)

Wait for an event to be posted to the process, with an extra condition.

This macro is similar to PROCESS_WAIT_EVENT() in that it blocks the currently running process until the process receives an event. But PROCESS_WAIT_EVENT_UNTIL() takes an extra condition which must be true for the process to continue.

Parameters

Name Description
c The condition that must be true for the process to continue.

PROCESS_YIELD

#define PROCESS_YIELD()

Yield the currently running process.

PROCESS_YIELD_UNTIL

#define PROCESS_YIELD_UNTIL(c)

Yield the currently running process until a condition occurs.

This macro is different from PROCESS_WAIT_UNTIL() in that PROCESS_YIELD_UNTIL() is guaranteed to always yield at least once. This ensures that the process does not end up in an infinite loop and monopolizing the CPU.

Parameters

Name Description
c The condition to wait for.

PROCESS_WAIT_UNTIL

#define PROCESS_WAIT_UNTIL(c)

Wait for a condition to occur.

This macro does not guarantee that the process yields, and should therefore be used with care. In most cases, PROCESS_WAIT_EVENT(), PROCESS_WAIT_EVENT_UNTIL(), PROCESS_YIELD() or PROCESS_YIELD_UNTIL() should be used instead.

Parameters

Name Description
c The condition to wait for.

PROCESS_WAIT_WHILE

#define PROCESS_WAIT_WHILE(c)

PROCESS_EXIT

#define PROCESS_EXIT()

Exit the currently running process.

PROCESS_PT_SPAWN

#define PROCESS_PT_SPAWN(pt, thread)

Spawn a protothread from the process.

Parameters

Name Description
pt The protothread state (struct pt) for the new protothread
thread The call to the protothread function.

PROCESS_PAUSE

#define PROCESS_PAUSE()

Yield the process for a short while.

This macro yields the currently running process for a short while, thus letting other processes run before the process continues.

PROCESS_POLLHANDLER

#define PROCESS_POLLHANDLER(handler)

Specify an action when a process is polled.

Note

This declaration must come immediately before the PROCESS_BEGIN() macro.

Parameters

Name Description
handler The action to be performed.

PROCESS_EXITHANDLER

#define PROCESS_EXITHANDLER(handler)

Specify an action when a process exits.

Note

This declaration must come immediately before the PROCESS_BEGIN() macro.

Parameters

Name Description
handler The action to be performed.

PROCESS_THREAD

#define PROCESS_THREAD(name, ev, data)

Define the body of a process.

This macro is used to define the body (protothread) of a process. The process is called whenever an event occurs in the system, A process always start with the PROCESS_BEGIN() macro and end with the PROCESS_END() macro.

PROCESS_NAME

#define PROCESS_NAME(name)

Declare the name of a process.

This macro is typically used in header files to declare the name of a process that is implemented in the C file.

PROCESS

#define PROCESS(name, strname)

Declare a process.

This macro declares a process. The process has two names: the variable of the process structure, which is used by the C program, and a human readable string name, which is used when debugging. A configuration option allows removal of the readable name to save RAM.

Parameters

Name Description
name The variable name of the process structure.
strname The string representation of the process' name.

PROCESS_CURRENT

#define PROCESS_CURRENT()

Get a pointer to the currently running process.

This macro get a pointer to the currently running process. Typically, this macro is used to post an event to the current process with process_post().

PROCESS_CONTEXT_BEGIN

#define PROCESS_CONTEXT_BEGIN(p)

Switch context to another process.

This function switch context to the specified process and executes the code as if run by that process. Typical use of this function is to switch context in services, called by other processes. Each PROCESS_CONTEXT_BEGIN() must be followed by the PROCESS_CONTEXT_END() macro to end the context switch.

Example:

PROCESS_CONTEXT_BEGIN(&test_process);
etimer_set(&timer, CLOCK_SECOND);
PROCESS_CONTEXT_END(&test_process);

Parameters

Name Description
p The process to use as context.

PROCESS_CONTEXT_END

#define PROCESS_CONTEXT_END(p)

End a context switch

This function ends a context switch and changes back to the previous process.

Parameters

Name Description
p The process used in the context switch.

Types

process_event_t

typedef unsigned char process_event_t;

process_data_t

typedef void* process_data_t;

process_num_events_t

typedef unsigned char process_num_events_t;

Structs

process

Name Type Description
next struct process *
name const char *
pt struct pt
state unsigned char
needspoll unsigned char

Functions

process_start

void process_start(struct process *p, process_data_t data);

Start a process.

Note

Starting a process A in another process B when handling the exit event of process A is not supported.

Parameters

Name Description
p A pointer to a process structure.
data An argument pointer that can be passed to the new process.

process_post

int process_post(struct process *p, process_event_t ev, process_data_t data);

Post an asynchronous event.

This function posts an asynchronous event to one or more processes. The handling of the event is deferred until the target process is scheduled by the kernel. An event can be broadcasted to all processes, in which case all processes in the system will be scheduled to handle the event.

When posting an event, the event will be put into an event queue of limited size, defined by PROCESS_CONF_NUMEVENTS. Exceeding the number of events in the queue will result in undefined behavior.

The asynchronous nature of events means that if many events are enqueued, there is no guarantee of when the CPU will be available to execute the event. Also, if a process is terminated before the event is scheduled for execution, the event may be lost, including the data pointer.

Care should be taken when relying on the arrival time of events in a process. There is no mechanism to postpone an event arriving at a process, so if an event arrives during a secondary yield (for example, PROCESS_PAUSE() or PROCESS_WAIT_UNTIL()), it will be lost and not caught if later expected in a PROCESS_WAIT_EVENT_UNTIL(ev == my_event);.

Note

PROCESS_BROADCAST is defined as NULL. Therefore, passing a null pointer as the process pointer will result in sending the event to all processes.

Note

Not safe to use in interrupt handlers. Use process_poll() to wake up a process from an interrupt handler.

Parameters

Name Description
ev The event to be posted.
data The auxiliary data to be sent with the event.
p The process to which the event should be posted, or PROCESS_BROADCAST if the event should be posted to all processes.

Return value

Name Description
PROCESS_ERR_OK The event could be posted.
PROCESS_ERR_FULL The event queue was full, and the event could not be posted. This error should be treated as a major fault since the integrity of the system cannot then be guaranteed.

process_post_synch

void process_post_synch(struct process *p, process_event_t ev, process_data_t data);

Post a synchronous event to a process.

An event will be posted to a target process, where the target process will be executed for one iteration recursively within the process_post_synch call.

This is useful when the data paremeter needs to be freed after a single iteration. But at the cost of increased stack requirement.

The calling process will then still be in called state, and may not be called again. So if process A uses process_post_synch to call process B, then process B can not call process A using process_post_synch back in the same iteration.

It is recommended in most cases to use processs_post and keep track of data storage using other data structures, due to impact on stack size.

Note

process_post_synch can not be used to post to PROCESS_BROADCAST

Note

Not safe to use in interrupt handlers. Use process_poll() to wake a process up from an interrupt handler.

Parameters

Name Description
p A pointer to the process' process structure.
ev The event to be posted.
data A pointer to additional data that is posted together with the event.

process_exit

void process_exit(struct process *p);

Cause a process to exit.

Parameters

Name Description
p The process that is to be exited.

This function causes a process to exit. The process can either be the currently executing process, or another process that is currently running.

process_alloc_event

process_event_t process_alloc_event(void);

Allocate a global event number.

Return value

The allocated event number

In MiraOS, event numbers above 128 are global and may be posted from one process to another. This function allocates one such event number.

Note

There currently is no way to deallocate an allocated event number.

process_poll

void process_poll(struct process *p);

Request a process to be polled.

The polled process wakes up with the event PROCESS_EVENT_POLL. Use global variables to pass information from the interrupt handler to the process.

It is not possible to poll the special process PROCESS_BROADCAST.

Note

Use in interrupt handlers to wake processes up. Outside of interrupt handlers, use process_post() instead.

Parameters

Name Description
p A pointer to the process' process structure.

process_is_running

int process_is_running(struct process *p);

Check if a process is running.

This function checks if a specific process is running.

Parameters

Name Description
p The process.

Return value

Name Description
Non-zero if the process is running.
Zero if the process is not running.

process_debug_dump

void process_debug_dump(void);

Print all running processes to stdout, if debugging is enabled. Otherwise no-op.

Back to top