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... | |
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.
The ARM7TDMI processor has 7 distinct states. The list below explains the kernel's treatment of these states:
The kernel itself can be in three states, depending on what is being executed by the CPU:
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.
A process, or thread, is a thread of execution. It has its own stack (provided by you). It can be in four major states:
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.
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:
| enum BX_ERROR |
Error codes.
| enum BX_POLICY |
Waiting policies.
1.6.3