Modules | Enumerations

# Pre-emptive, soft real-time scheduler
[Process schedulers (kernels)]

A simple mutithreading kernel. More...

## Modules
| Initialisation functions
| Process management functions

| This group of functions is responsible for the handling of processes.
| Event (signal) functions

| Events, or signals, are the simplest (and fastest) interprocess communication mechanism.
| Semaphore functions

| Semaphores are resource counters.
| Mutual exclusivity lock functions

| Locks are guaranteeing mutually exclusive access to some resource.
| Timer functions

| The kernel supports an unlimited number of software timers.
| Interrupt functions
## Enumerations
enum | BX_ERROR { BX_ERR_SUSPEND = -1, BX_ERR_CONTEXT = -2, BX_ERR_OBJECT = -3, BX_ERR_PARAM = -4, BX_ERR_INUSE = -5, BX_ERR_NOTOWNER = -6, BX_ERR_ISOWNER = -7, BX_ERR_INACTIVE = -8 }
| Error codes. More...
enum | BX_POLICY { BX_POLICY_FIFO = 0, BX_POLICY_PRIO, BX_POLICY_FPCL, BX_POLICY_FPIN, BX_POLICY_PPCL, BX_POLICY_PPIN, BX_POLICY_SRQF, BX_POLICY_LRQF }
| Waiting policies. More...

## Detailed Description

A simple mutithreading kernel.

This executive is a simple multithreading kernel. It gives you the ability to run separate threads of execution and provides the basic thread synchronisation mechanisms. In the kernel docs thread and process are used interchangeably. Since the target system has no MMU, there's no real distinction between the two.

The kernel has a single data structure that holds its internal state (it is in the uninitialised data segment). Everything else, including process stacks, kernel objects and so on are provided by you. The kernel doesn't know your memory situation so it leaves it to you to allocate your resources.

There is no limit on the number of resources the kernel can handle. As long as you have the memory to store them, the kernel will handle them. There are no compile-time configuration parameters or limits.

The kernel tries to keep interrupt latency as low as possible. This goal makes the kernel more complex, as it can't just disable interrupts and finish whatever needs to be done, no matter how long it takes. It actually disables interrupts only when really necessary and re-enabes them as soon as possible. Thus, it needs to invest some extra effort into keeping events ordered and the system state consistent. It does not come for free, the kernel is not as fast as it could be if it didn't care about the interrupts.

## Kernel basics

### Processor states

The ARM7TDMI processor has 7 distinct states. The list below explains the kernel's treatment of these states:

- User state
  The kernel does not support user state. It needs to control interrupts and those instructions are not available in user state. Therefore the kernel assumes that user processes are running in system state, which is the same as user state but without the instruction restrictions. If you really want to use the user state, you have to wrap the kernel calls with functions that switch to system state (via supervisor state) before calling the kernel.

- Supervisor state
  The kernel does not directly provide support for the supervisor state. It treats it the same as system state. You can call kernel functions from supervisor state, but the burden of maintaining the stacks is on your shoulders.

- Abort exception state and illegal instruction exception state
  The kernel does not support the abort and illegal instruction exceptions at all. Those states are entered when something really bad happened. Do not call the kernel from the exception handlers.

- Fast interrupt state
  The kernel is aware of but does not support the fast interrupt state directly. The fast interrupt is designed for interrupt routines that are very high priority. For that reason the processor provides a handful of registers that are saved by the hardware before entering the fast interrupt service routine. If your interrupt service routine is simple enough, that is all that you need. If you have a very high priority interrupt, you probably do not want to slow it down by going through a kernel, save registers and so on. Therefore the kernel assumes that the fast interrupt is outside of its domain. The kernel's awareness of the fast interrupt is limited to providing a functionality to enable and disable the fast interrupt. Note, however, that it is important not to change the fast interrupt enable state without the kernel knowing about it.
  In addition, functions are provided to atomicly swap a value in a memory word or byte, with which you can implement spinlocks. If you are in ARM mode (an interrupt handler always enters in ARM mode), then these functions will be simple inline assembly instructions. That way the compiler will not generate register saves and alike.
  In short, the kernel is aware of the fast interrupt mechanism, but you must not call any kernel function from your fast interrupt handler.

- Interrupt state
  This is a fully supported state. There is one important thing, though. The kernel does not support nested interrupts. Each interrupt must run to completion before the next one is accepted. If you enable interrupts inside an ISR, then the system most likely will crash. However, the kernel supports delaye interrupt handling, thus you can keep your ISRs short, minimising the interrupt latency.

- System state
  This is the state in which all user processes are running. In addition, the kernel's internal event processing is also executed in system state.

### Kernel states

The kernel itself can be in three states, depending on what is being executed by the CPU:

- A user process is running
- An interrupt service routine is running
- The kernel is doing some internal work or it has called a delayed ISR function. Being idle is also a kernel internal state.

Since the kernel knows its own state, there are no separate functions to call from interrupt service routines and from user routines. The kernel, if it has to, will differentiate between the calling contexts inside the function.

### Processes

A process, or thread, is a thread of execution. It has its own stack (provided by you). It can be in four major states:

- Running
  The process is currently being executed by the processor.

- Interrupted
  The process would be executed by the processor but the processor is taken by an interrupt service routine or by the kernel itself.

- Ready
  The process is ready to run, but there is an other user process that is occupying the CPU, either because it has higher priority or because it is in a protected mode.

- Suspended
  The process is waiting for some event or resource and can not run.

Processes have a priority associated with them. At any given time, the kernel selects the highest priority ready process to run. The kernel is pre-emptive, meaning that is a higher priority process becomes ready, the currently running process will be kicked off the processor and the higher priority one will start to run immediately. The kernel provides mechanisms to change the process priorities as well as to protect a process from being pre-empted.

The kernel does not provide a way to kill a process. Once a process is started, it will remain alive until it decides to die. There is no way to kill a process. All you can do is to ask the process to commit suicide.

### Kernel event queue

Normally, you can not afford to have the kernel's internal state to be in an inconsistent state when an interrupt occurs because interrupt routines can call kernel functions that need to operate on the kernel state. If the kernel disables the interrupts while it processes a request, then the problem is solved. However, some operations, in particular, the evaluation of the priority inheritance chain if the resource in question uses priority inheritance or priority ceiling, might take considerable time as the kernel has to visit every member of the chain and re-evaluate priorities. To keep interrupt latencies low, the kernel employs a different technique. For simple operations which has no consequence on objects other that the one is operated on, the kernel simply disables the interrupts for the duration of the operation. If, however, it needs to do something that involves multiple objects or evaluating priority queues, it uses an event queue.
The event queue is simply a queue of actions that should be performed by the kernel. When the kernel decides that an operation can not be done immediately for any reason, it places the operation into the queue and makes note that the queue is not empty. When the kernel reaches the point of returning to a user process, then instead of doing that it will go and execute the actions noted in the event queue. When the event queue is empty, then it retruns to the current user process.
While the kernel processes the event queue it usually keeps the interrupts enabled (except for short periods of time). If an interrupt comes during queue processing and it calls a kernel function, that function just deposits an other event into the queue. That way the order of events is maintained.
Naturally, since the event queue is finite in size, if you have a burst of interrupts, it could overflow. To avoid that the kernel, when the event queue grows over a watermark level, disables interrupts and does not enable them again until the event queue dips below the watermark.
Processing the event queue is similar to a process in the sense that it has its own stack and that it executes in system mode (like all threads). However, it is a special task because context switches to and from that particular task are very cheap.
You can exploit the event queue in your code in two ways:

- Delayed interrupt services
  If you can split your interrupt routine to a part that keeps the hardware happy and an other part that processes all the software aspects of the interrupt, you can delay the software side of things until the actual interrupt routine returns. You split your interrupt service routine into two functions. One, that deals with the hardware, is registered as your actual ISR. That function does the hardware related bits then instructs the kernel to queue the calling of the second half of the function. The ISR the returns and the kernel will call the second half of your service routine. However, thet routine will be called in a system context, with interrupts enabled (although you can disable them in your function) thus your interrupt latency will not be affected by this second part of the service.
- Software interrupts
  Nothing stops you to call the function that deposits an action in the event queue from a normal thread. If you do that, then the kernel queues the activity and immediately switches to event processing mode and does not return to you until it finished with the activity. Thus, it is just a convoluted form of calling a function. However, the called function will run in the kernel's context, with certain privileges (and limitations) that you may want to expoit.

## Enumeration Type Documentation

enum BX_ERROR

Error codes.

Enumerator:

  BX_ERR_SUSPEND | Caller should, but can not be suspended.
  BX_ERR_CONTEXT | This function can not be called from ISR.
  BX_ERR_OBJECT | Invalid or uninitialised object.
  BX_ERR_PARAM | A function parameter had an illegal value.
  BX_ERR_INUSE | The object is in use.
  BX_ERR_NOTOWNER | The object is not owned by the caller.
  BX_ERR_ISOWNER | The caller already owns the object.
  BX_ERR_INACTIVE | The object is not active.

enum BX_POLICY

Waiting policies.

Enumerator:

  BX_POLICY_FIFO | First in, first out.
  BX_POLICY_PRIO | Highest priority first.
  BX_POLICY_FPCL | FIFO order, priority ceiling.
  BX_POLICY_FPIN | FIFO order, priority inheritance.
  BX_POLICY_PPCL | Priority order, priority ceiling.
  BX_POLICY_PPIN | Priority order, priority inheritance.
  BX_POLICY_SRQF | Smallest request first.
  BX_POLICY_LRQF | Largest request first.