![]()
|
About this document:
The aim of this document is to give a quick overview about what ISOS is and to rapidly present the principles behind it. Some knowledge about operating systems principles is assumed. This document aims at presenting ISOS to people who already have some knowledge about operating systems, not to explain how operating systems work. It is however very simple for an operating system and it can be helpful for people interested in operating systems to read its source and understand it. For the API documentation, a doxygen generated documentation is included in the package that can be found on the download page. 1. Technical overview: 1.1. Threads: A thread is a block of executable code with its own stack. It is similar to a task although there is no separate memory space for each thread. In ISOS, the kernel maintains a static array of 32 thread structures. Since one thread is automatically created and used by the kernel (the idle thread), there is a maximum of 31 threads left to be created by the user. The multi-threading in ISOS is preemptive meaning that each thread is assigned an amount of time to run before it is interrupted to leave the CPU to another thread. The thread switching is done every 1/100th of a second. The scheduler called each time is responsible for choosing the next thread to run. If there is no other thread to run the scheduler will run the idle thread which does nothing. Threads do not have priorities. All threads are run in a sequential order (round-robin) except the idle thread which is only run when there is no other thread to run. The thread structure is defined as below: struct thread_t { This structure contains the information necessary for the kernel to manage threads. In particular "stack_bottom" is a pointer to the thread's stack where its execution context and local variables are stored. The variable "state" defines the current state of the thread. If "state" is equal to DEAD, there is no thread assigned to this structure and the rest of the structure's data is meaningless. The following diagram illustrates the different thread's states and the possible transitions between them:
Note: There is no "RUNNING" state. Only one of the READY threads is running at a time, it is identified in the kernel by the variable "s_running_thread" which is an index in the threads structures table. 1.2. Alarms: Alarms are a mechanism by which a function
execution can be programmed at a later point in time. The alarm_add()
function must be called to program an alarm. It takes three arguments: Alarms are used internally by ISOS be program the de-allocation of threads stacks after they have returned. 1.3. Condition locks: A condition lock is an object that can be used to synchronize threads executions. Several threads can be blocked on a condition and all unblocked by another thread. A cond_lock_t structure must be declared and accessible for all the threads that will use the condition lock. This structure MUST be initialized before being used by calling cond_lock_create(cond_lock_t *cond) on it. When a thread calls cond_lock_wait(cond_lock_t *cond), its execution is stopped here and its state changed to BLOCKED. It will no longer be executed by the kernel until it is unblocked. Several threads can be blocked at the same time on a condition lock. A thread can't be blocked on several condition lock at the same time. The only way for the blocked thread(s) to be unblocked is an other thread calling cond_lock_signal(cond_lock_t *cond) on the condition lock. Doing so will unblock ALL the threads blocked on the condition lock. Condition locks can be used by any thread, in ISOS they are internally used to manage messages queues. 1.4. Semaphores: A semaphore is an object that can be used to protect the access to a resource. A semaphore_t structure needs to be declared and accessible by all the threads that will use the semaphore. This structure MUST be initialized before it can be used by calling semaphore_create(semaphore_t *sem, int32 initial_count). “initial_count” is the number of threads that can access the resource at any one time. To access the protected resource, a thread has to call semaphore_acquire(semaphore_t *sem). If the resource is available, the thread's execution will continue. If the semaphore has already been acquired by the maximum number of threads specified at the semaphore's initialization, the thread's execution will be stopped here and its state changed to BLOCKED. The thread will no longer be executed until it is unblocked. This is done automatically when the resource becomes available, that is, when a thread which had previously acquired the semaphore releases it by calling semaphore_release(semaphore_t *sem). If you don’t want your thread to be blocked when the semaphore is not available, you can use semaphore_acquire_try(semaphore_t *sem). This function will return true if the semaphore was successfully acquired, false if the resource was not available. 1.5. Message Queues: Message queues provide a way for threads to communicate with each other by sending and receiving messages. This can be used to transfer information from one thread to another and also to synchronize threads. A message is defined like this: struct msg_t { The number can be used to send simple information while the void pointer can be used to send more complex information by sending a pointer to a data structure. Message queues need to be created by calling
msg_queue_create(msg_queue_t *queue, size_t size). The msg_queue_t
structure must already being allocated; “size” memory space will be
allocated to store the queue’s messages. Then,
msg_queue_add(msg_queue_t *queue, msg_t msg, bool blocking = true) can
be called to add a message to the queue and
msg_queue_remove(msg_queue_t *queue, msg_t *msg, bool blocking = true)
can be used to collect a message from the queue. Typically, the
msg_queue_t structure will be visible from two thread functions; one
will add messages to the queue while the other will collect them. Each
of these two function is blocking by default, that is, the add function
will wait until the message queue is not full to add its message and
the remove function will wait that there is a message to collect to
return it. If “blocking” is set to false, the function will not block
and return false in the two previous cases. Otherwise, they return true. 2. Development model: In this section you will find the basics of application development under ISOS. Two application examples are provided. The first one show how to create a thread to run an application, the second one shows how to use interruptions in an application. 2.1. Simple “Hello world” application: The thread function should have a prototype of this form: void my_application(void *data); To illustrate argument passing to a thread function, we will declare a “Hello world” string in the main function and pass a pointer to it to the thread that will display it. In the “main.cpp” file: #include “Hello.h” [...] // Declare the hello
world string In the main() function: // Create the thread
that will display it on the serial interface “ Hello.h” file: #ifndef HELLO_H // Declare the thread
function #endif “ Hello.cpp” file: #include “Hello.h” // Implement the thread
function 2.2. Application using interruptions: ISOS differentiates two types of interruptions: Timer 0 interruptions are handled internally while the other interruptions sources can be controlled by the user. All other interruptions are handled in the function “irq_dispacher” in the file “Interrupts.cpp”. By default it contains handler for the serial interface and interrupt button interruptions. If you want to add an handler for example for the timer 1 interruption you can add the following to the “irq_dispacher” function: if (TEST_BITS(INTPND,
TIMER1_INT)) { Where “irq_timer1()” is the function in which you handle timer 1 interruptions. |
© 2003 Wilhem Meignan. |