Threads

A thread is simply a normal C function that never returns. It has its own stack, and is executed until it allows the kernel to run another thread. Threads are executed in the order they were created.

The kernel related functions are declared in thread.h and kernel.h.

When the kernel switches the execution to another thread, it is called a context switch. You can force a context switch by calling the yield() function, therefore a forced context switch is often called yielding. A function that may cause a context switch is often called a blocking function, or a function that blocks.

The thread context

The thread context consists of the CPU registers that are normally preserved in normal C calls, plus the stack pointer. The "scratch registers" are not included. Also, no extra registers, such as MAC accumulators or status registers are preserved.

Thread functions

Prototype: Creates a thread. It is immediately inserted in the list of threads, ready to execute. A thread function must notint create_thread(void* function, void* stack, int stack_size, const char *name) return. You need to use the remove_thread() function to remove a thread. The thread number is returned by create_thread. It is generally of no interest, but is is needed if you want to remove a thread.

Description: Creates a thread. It is immediately inserted in the list of threads, ready to execute. A thread function must not return. You need to use the remove_thread() function to remove a thread. The thread number is returned by create_thread. It is generally of no interest, but is is needed if you want to remove a thread.

void remove_thread(int threadnum)

Removes a thread from the kernel. The threadnum parameter is the thread number returned by create_thread().

void yield(void)

Forces a context switch. The next thread in the list will be executed.

void sleep(int ticks)

Sleeps for ticks number of system ticks, allowing other threads to execute.

Complete thread example

You create a thread in three steps:

Write a C function. The thread function should not take any arguments, and obviously not return anything.

Reserve some stack. A normal thread should not use that much stack. If in doubt, use the DEFAULT_STACK_SIZE macro, which should be large enough for simple threads. Most CPU's want the stack long-aligned, so it might be a good idea to use the 'long' type.

int tick_add_task(void (*f)(void))

int tick_remove_task(void (*f)(void))

Removes a tick task from the kernel. Returns 0 if it was found and removed or -1 if the task wasn't found in the kernel list.

Event queues

Threads can communicate using event queues. An event queue is a circular buffer of events. A thread can wait for messages to arrive in the queue while other threads are executing. When an event arrives, the thread will receive the event the next time it is scheduled.

An event looks like this:

struct event
{
long id;
void *data;
};

The id is what identifies this particular event. It can be any 32-bit positive number. Negative numbers are reserved for system events. The data can be anything you want to pass along with the event.

If you want to receive events in your thread, declare a global event queue and you're set.

struct event_queue my_queue;

Before using the queue, you have to initialize it.

queue_init(&my_queue);

Queue functions

Here are the functions for using event queues.

void queue_init(struct event_queue *queue)

Initializes a queue. This must be done before using it.

Example:

struct event_queue my_queue;
queue_init(&my_queue)

void queue_delete(struct event_queue *q)

Removes q from the queue list. You should use this function if the queue could be destroyed (like in plugins), otherwise calling of queue_broadcast() or sending an event to this queue could/will do a very bad thing (tm).

void queue_wait(struct event_queue *q, struct event *ev)

Waits for an event to arrive, removes the event from the queue and copies it to the event structure pointed to by ev.

Waits for an event to arrive, removes the event from the queue and copies it to the event structure pointed to by ev. If ticks system ticks have passed, it returns anyway with ev.id set to SYS_TIMEOUT.

void queue_post(struct event_queue *q, long id, void *data)

int queue_broadcast(long id, void *data)

Sends the event to all threads in the system, including the sending thread.

Protecting your shared data

To protect your shared data, there is a mechanism called a mutex. It is a simple locking mechanism to allow exclusive access to a resource. Mutexes can only be used in a thread context and not in an interrupt handler.

Mutex functions

void mutex_init(struct mutex *m)

Initializes a mutex before it can be used.

void mutex_lock(struct mutex *m)

Attempts to lock a mutex. If it is already locked, it blocks until the mutex is unlocked.

Guidelines for developers

When you develop Rockbox code, you have to be nice to the other threads in the system.

Don't spend time in an interrupt handler (or tick task)

Yield or sleep every so often. Many threads in the system rely on being executed regularly. If your code performs lengthy operations, like Mandelbrot calculations, you must call yield() once in a while, so the other threads can run.

Sleeping is often better than yielding, because the kernel can execute a SLEEP instruction (architecture specific) if all threads are asleep, which will reduce the power consumption.