- Overview
- Quick-start for Linux and Windows
- Quick-start for ARMv7m
- Quick-start for PowerPC
- Documentation
- Software model
- Common development tasks
- Repository structure
- Task management
- Project conventions
- Design considerations
If you have any questions, send us an e-mail to [email protected] or tweet at us @echronosrtos.
If there is something in the project that you think is worth improving, please create a github issue.
Of course, we are also keen on your changes and contributions if you have any - here is a primer.
The eChronos RTOS is a real-time operating system (RTOS) originally developed by NICTA and Breakaway Consulting Pty. Ltd.
It is intended for tightly resource-constrained devices without memory protection. To this end, the RTOS code base is designed to be highly modular and configurable on multiple levels, so that only the minimal amount of code necessary is ever compiled into a given system image.
Available implementations currently target ARM Cortex-M4 and PowerPC e500. The RTOS also runs on POSIX platforms (e.g., Linux, MacOS-X, Windows with cygwin or MinGW) for rapid prototyping.
Get a first impression of how the RTOS is being used without delving into the details of any embedded hardware platform. This section covers how to build and run a simple application on top of the RTOS as a regular Linux or Windows process.
Download the latest posix release and unpack it in a directory of your choice.
The project makes frequent releases, based on improvements flowing into the master branch of the code repository. You'll notice that releases are not just snapshots of the code repository. Rather, they provide just the bits and pieces needed for individual target platforms, with some of the code and tools already pre-built.
The posix release is aimed at running the RTOS on top of any POSIX host operating system, such as Linux or Windows (with Cygwin or MinGW).
You need the following tools installed and ready to go:
- Python 3
- GCC compiler + GNU binutils
On Linux, use your package manager to install these tools. On Windows, obtain and install Python 3 from python.org and install either Cygwin or MSys2 including the GCC compiler.
The following commands build a simple version of the eChronos RTOS together with a small example application:
cd eChronos-posix-VERSION
./bin/prj.sh build posix.acamar
On Windows, run prj\prj
instead of ./prj/prj.sh
.
This produces the binary out/posix/acamar/system
(system.exe
on Windows).
Run the sample system with the command ./out/posix/acamar/system
.
It prints task a
and task b
to the screen until you stop it by pressing ctrl-c
.
If you have GDB installed, you can also run this RTOS system in a debugger.
Start GDB with gdb out/posix/acamar/system
and
- set a break point with
b debug_print
- start the system with
run
- gdb now stops the system just before it prints
task a
ortask b
, allowing you to inspect the system state or to continue withcontinue
To give you an idea of what goes on when building an running an RTOS system as above, here is a quick overview of what happens under the hood.
The prj
tool is the build tool of the RTOS.
Its primary purpose is to
- find and load the system configuration,
- generate RTOS code specifically for the system configuration,
- and build the RTOS and application code into a single system binary.
The command ./prj/prj.sh build posix.acamar
makes prj
first search for a system configuration file with a .prx
file name extension.
Think of PRX files as make files.
prj
finds that system configuration file at share/packages/posix/acamar.prx
, based on the search paths set up in project.prj
.
acamar.prx
lists all the files that go into building this system plus some configuration information.
For example, this system is configured with two tasks, a and b, that have given entry point functions and stack sizes:
<task>
<name>a</name>
<function>fn_a</function>
<stack_size>8192</stack_size>
</task>
<task>
<name>b</name>
<function>fn_b</function>
<stack_size>8192</stack_size>
</task>
In the second step, prj
uses this configuration information to generate a custom copy of the RTOS source code.
This makes the RTOS itself as small as possible to leave more resources for the application.
The third step is to invoke the compiler and linker to build all the source code files listed in the system configuration into a binary.
prj
invokes the POSIX-specific build module to achieve that.
The file share/packages/rtos-example/acamar-test.c
contains the main application code of the example system (the PRX file refers to it as rtos-example.acamar-test
).
This file implements two tasks that perpetually print their name and yield to each other.
You will notice that this file also contains the standard main()
function found in all C programs.
If necessary, it could, for example, initialize some hardware before starting the RTOS, which in turn starts the two tasks.
The eChronos project is not a single RTOS, but provides a family of RTOS variants with different feature sets. Acamar is the name of the smallest one, but the POSIX release comes with a number of other, more powerful variants. Those provide "proper" RTOS features, such as mutexes, interrupts, and timers. The Variants and Components section has more information on this topic.
The rest of this README covers ARM and PowerPC quick-starts as well as all basic RTOS concepts and how to make use of them. It makes use of the full source code repository, not just a release as the Quick-start Guide did above.
Obtain the source code with the command
git clone --depth=1 https://github.com/echronos/echronos.git
To obtain the arm-none-eabi
GNU toolchain and gdb-arm-none-eabi
for building and debugging the RTOS for ARMv7-M, run:
sudo apt-get install gcc-arm-none-eabi gdb-arm-none-eabi
The packages will have slightly different names across different linux distributions and versions.
Note: Older versions of Ubuntu have a known bug with ARM gdb package installation (see here). If you are unable to install it due to a conflict, try adding a dpkg diversion for the gdb man pages first:
sudo dpkg-divert --package gdb --divert /usr/share/man/man1/arm-none-eabi-gdbserver.1.gz --rename /usr/share/man/man1/gdbserver.1.gz
sudo dpkg-divert --package gdb --divert /usr/share/man/man1/arm-none-eabi-gdb.1.gz --rename /usr/share/man/man1/gdb.1.gz
And then retry the above installation command for gdb-arm-none-eabi
.
To obtain echronos-qemu-system-arm
for emulating ARM systems, read the README.md for our QEMU fork (make sure to use the echronos-qemu
branch).
On most linux distributions, it will be simplest to use the binaries included in the QEMU fork repository - see 'Using the binaries' section of the QEMU README.md.
Build and run an example system for the RTOS variant Gatria on QEMU-emulated ARMv7-M (STM32):
cd echronos
prj/app/prj.py build machine-stm32f4-discovery.example.gatria-system
# Run the generated system in qemu (press `ctrl-c` to close QEMU after it is finished)
echronos-qemu-system-arm -mcu STM32F407VG -semihosting -S -s --kernel `pwd`/out/machine-stm32f4-discovery/example/gatria-system/system
# To connect and view debug output run gdb in another shell prompt
arm-none-eabi-gdb -ex "target remote :1234" out/machine-stm32f4-discovery/example/gatria-system/system
(gdb) b fn_a
Breakpoint 1 at 0x800065c: file packages/rtos-example/gatria-test.c, line 41.
(gdb) c
Continuing.
Breakpoint 1, fn_a () at packages/rtos-example/gatria-test.c:41
41 {
(gdb) n
43 rtos_unblock(0);
(gdb) n
44 rtos_unblock(1);
(gdb) c
Continuing.
task a -- lock
task b -- try lock
task a -- unlock
...
Obtain the source code with the command
git clone --depth=1 https://github.com/echronos/echronos.git
To obtain the powerpc-linux-gnu
GNU toolchain for building the RTOS for PowerPC e500 on Ubuntu Linux systems, run:
sudo apt-get install gcc-powerpc-linux-gnu
To obtain the qemu-system-ppc
emulator for running the machine-qemu-ppce500
systems on Ubuntu Linux systems, run:
sudo apt-get install qemu-system-ppc
To obtain, build, and install powerpc-linux-gdb
for debugging PowerPC e500 systems, run:
wget https://ftp.gnu.org/gnu/gdb/gdb-7.12.tar.xz
tar xaf gdb-7.12.tar.xz
cd gdb-7.12
./configure --target=powerpc-linux --prefix=/usr/
make -s
sudo make -s install
Build and run an example system for the RTOS variant Kochab on QEMU-emulated PowerPC e500:
cd echronos
prj/app/prj.py build machine-qemu-ppce500.example.kochab-system
# Run the generated system in qemu (press `ctrl-a` then 'x' to close QEMU after you are finished)
qemu-system-ppc -M ppce500 -S -s -nographic -kernel out/machine-qemu-ppce500/example/kochab-system/system
# To connect and view debug output run gdb in another shell prompt
# Note: The Kochab example will end with task b cycling forever between "blocked" and "unblocked"
powerpc-linux-gdb -ex "target remote :1234" -ex "b debug_print" out/machine-qemu-ppce500/example/kochab-system/system
(gdb) c
...
Breakpoint 1, debug_print (msg=0x33b4 "tick")
(gdb) c
Breakpoint 1, debug_print (msg=0x33e8 "task b blocking")
(gdb) c
Breakpoint 1, debug_print (msg=0x33b4 "tick")
(gdb) c
Breakpoint 1, debug_print (msg=0x33f8 "task b unblocked")
(gdb) quit
Basic RTOS concepts and usage are documented in README file and the docs
directory.
More detailed documentation for the x.py
tool can be found inside x.py
itself.
More detailed documentation for the prj
tool can be found in prj/manual/prj-user-manual.md
.
Pregenerated RTOS API manuals can be found on the eChronos GitHub wiki or you can build them yourself.
The software model and structure of the RTOS is governed by two stages of customization.
In the first stage, features, in the form of components, are customized for and composed into variants of the RTOS such that each variant has a specific feature set.
This stage is supported by the x.py
tool.
In the second stage, the RTOS variant is customized to the specific properties and requirements of a specific application.
Although this customization is limited to the functionality provided by the given variant, it controls details such as the number of tasks required by the application.
This stage is supported by the prj
tool.
The two stages can optionally be separated by deploying a product release to an application project. The application project is then only exposed to the second stage and the variant and functionality of the RTOS they require.
The following sections cover these concepts in more detail.
The RTOS comes in a number of different variants, each offering a specific set of features for a particular platform.
For example:
-
The RTOS variant Rigel supports tasks, co-operative round-robin scheduling, mutexes, signals, and interrupt events which can trigger the sending of signals. It is available for QEMU-emulated ARMv7-M.
-
The RTOS variant Kochab supports tasks, preemptive priority scheduling, mutexes with priority inheritance, semaphores, signals, and interrupt events which can cause task preemption and trigger the sending of signals. It is available for the ARMv7-M STM32F4-Discovery board, and QEMU-emulated PowerPC e500.
Features are implemented as individual components in individual C files.
Unlike typical C-based software, the RTOS does not compile components individually and later link them into a single binary.
Instead, the RTOS x.py
tool merges the component files of a variant into a single C file called the RTOS module.
The feature set of each RTOS variant is specified within the x.py
tool itself.
This allows for better compile-time optimizations.
The x.py
tool also supports building product releases of the RTOS.
A product release bundles a set of variants and target platforms tailored for a certain application product that builds on the RTOS.
Thus, the application product sees only what it needs without being needlessly exposed to all features and platforms supported by the RTOS.
The variants and platforms contained in a release are defined by release_cfg.py
.
An RTOS system encompasses the entirety of the OS and an application on top of it. It consists in particular of:
- the OS in the form of a variant of the RTOS with a feature set suitable for the application (e.g., which form of scheduling is supported)
- a system configuration that tailors the variant to the specific application instance (e.g., how many task or mutexes the application requires)
- the application code itself
Systems are built via the prj
tool which implements the RTOS build system.
At the build system level, systems are composed of modules (such as the RTOS module), so modules provide the unit of composition and reusability.
In its simplest form, a module is a C file and that is usually all that applications need to know about modules. However, modules can consist of the following elements:
- Entity definition file named
entity.py
or<module_name>.py
, specifying the module contents and customization options by providing amodule
Python object. - C and header file named
<module_name>.c/h
, providing the public interface and its implementation of the module in C - Assembly file named
<module_name>.s
, providing the module functionality as assembly code - Linker script named
<module_name>.ld
, specifying linker commands for linking the system (not just the module) - XML Configuration schema (as a standalone file or integrated into the entity definition script or source code files), specifying the configuration parameters supported by the module
- Builder module script
<module_name>.py
, defining asystem_build()
function to be executed in order for the system to be built. The presence of this function distinguishes the module as a Builder module.
A system is statically defined and configured in its system configuration in a .prx
file.
This is an XML file that lists the modules that make up a system and provides configuration parameters for each of the modules.
The .prx
file includes a static declaration of all the RTOS resources used by the system, including all tasks, mutexes, semaphores, interrupt handlers, etc.
The prj
tool reads .prx
files and composes, compiles, and links all the code to produce a system binary.
A complete system typically consists of
- an RTOS module
- modules dictating the build process for the target platform
- one or more modules containing platform-specific assembly code needed by the RTOS variant to implement low-level OS functionality
- one or more modules containing user-provided code that implement the application functionality
For example, the Kochab RTOS example system for QEMU-emulated PowerPC e500 (defined in packages/machine-qemu-ppce500/example/kochab-system.prx
) contains the following modules:
ppce500.rtos-kochab
, the Kochab variant of the RTOS for ppce500.ppce500.build
andppce500.default-linker
, which define building and linking for ppce500.ppce500.interrupts-util
andppce500.vectable
, which provide assembly-level RTOS code for ppce500.ppce500.debug
andgeneric.debug
, which define stubs for debug printing.machine-qemu-ppce500.example.kochab-test
, the user-provided test program for Kochab QEMU-emulated PowerPC e500.
On the file system, modules are grouped into packages, allowing modules to be organised based on common characteristics (such as platform or intended usage).
For example, the PowerPC e500 RTOS variant modules are grouped together with the build
, default-linker
, interrupts-util
and vectable
modules in the ppce500
package.
As another example, platform-agnostic RTOS example code such as the kochab-mutex-test
and timer-test
modules are grouped together in the rtos-example
package.
As described above, x.py
provides the means to generate all the different RTOS variants and prj
provides the means to combine an RTOS module with other modules to produce a system binary.
There are two main steps in building an RTOS-based system from the RTOS repository.
Step 1: Generate the RTOS variants.
./x.py build packages
This generates all the RTOS variants.
For each variant specified in x.py
it finds the appropriate components and combines them into the RTOS variant.
The resulting variant can be found in packages/<platform>/rtos-<variant>
.
Please look at the documentation inside x.py
for more information on the tool.
Step 2: Build a system.
prj/app/prj.py build <system name>
This finds the appropriate .prx
file, combines the required modules and generates a system binary.
prj
can be further configured using the top level project.prj
file, which specifies tasks that are automatically performed when prj
is run as well as the locations to look for modules and .prx
configuration files.
As a convenience prj
can be configured to automatically regenerate RTOS modules whenever it is run.
This is done by including the following line in the project.prj
file:
<startup-script>./x.py build partials --allow-unknown-filetypes</startup-script>
Please see prj/manuals/prj-user-manual
for more information on the prj
tool.
To generate the code for all available RTOS variants:
./x.py build packages
The resulting RTOS code is placed into packages/<platform>/rtos-<variant>/
.
The RTOS variants themselves are specified as lists of components within x.py
itself.
Adding a new RTOS variant means adding the appropriate list entries to x.py
, and adding new component code in the components
directory structure if necessary.
Please see the existing component code under components
for examples on how to develop RTOS components.
Manuals for the main RTOS variants are built with the command ./x.py build docs
.
On Linux, this depends on the tools wkhtmltopdf and pandoc to be installed.
The manuals are created in packages/<platform>/rtos-<variant>/documentation.*
in HTML, Markdown, and PDF.
Like the RTOS component code, the user manual content is componentized so that only the user documentation for components present in a particular RTOS variant appears in the user manual for that variant.
See components/*/docs.md
for examples of componentized user documentation.
To build just the prj
tool binary for stand-alone use:
# The `prj` binary is output to `prj_build_x86_64-unknown-linux-gnu/prj`
./x.py build prj
To build a system, use the prj build
sub-tool of prj
, supplying the system name as argument.
The name of the system is the basename of its .prx
file, minus its .prx
extension, appended to a dot-separated string indicating the sub-package location in which it can be found.
For example, to build the system whose .prx
file is located at packages/machine-qemu-ppce500/example/kochab-timer-demo.prx
, from the top level of the repository:
prj/app/prj.py build machine-qemu-ppce500.example.kochab-timer-demo
Alternatively, assuming the prj
tool binary is on the PATH:
prj build machine-qemu-ppce500.example.kochab-timer-demo
To build all product releases, run:
./x.py build prj
./x.py build docs
./x.py build partials
./x.py build release
The releases are defined in the top-level release_cfg.py
script and are generated by x.py
into release/*.tar.gz
.
Each product release contains:
- a
LICENSE
file - a
README.md
file containing a brief introduction to the release - a
build_info
file containing the commit hash of the RTOS repository from which the release was built - a
share
directory, containing the package directory structure containing all the released packages - a pre-built
prj
binary for the host platform targeted by the release
Note that any user manuals available for each RTOS variant in the release can be found at share/packages/<platform>/rtos-<variant>/documentation.*
in HTML, Markdown and PDF.
After unpacking the product release, create a project.prj
file for your software project in order to be able to use the prj
tool that comes packaged with the release.
A minimal project.prj
contains, at the very least, a search-path
entry to give prj
a hint as to where to find modules.
Here is a example that points to its relative location from the root of the release:
<?xml version="1.0" encoding="UTF-8" ?>
<project>
<search-path>share/packages</search-path>
</project>
After this, the prj
binary packaged with the release can be used in the same way as in the RTOS repository.
Additional application code can be placed in other directories as long as they are included as search paths in the project.prj file.
For example, to build the Kochab timer demo system provided with the PowerPC e500 release of the RTOS for Linux, run:
x86_64-unknown-linux-gnu/bin/prj build machine-qemu-ppce500.example.kochab-timer-demo
Note that the PATH environment variable needs to be set up manually so that it includes the tools invoked by prj
.
The components
directory contains the RTOS component source and documentation.
The packages
directory provides various platform-specific RTOS modules and other source modules.
RTOS modules generated by x.py
are output to locations within the packages
directory structure.
The x.py
tool provides an interface for:
- RTOS module and product release generation (
x.py build
), - task management (
x.py task
), and - testing (
x.py test
).
Much of x.py
's underlying implementation resides in the pylib
directory, and its self-test suite (x.py test x
) is implemented in x_test.py
.
The prj
directory contains everything related to the prj
tool, which provides an interface for configuring and building systems as compositions of source modules.
The release
directory is created by x.py
, and contains all releasable artifacts such as product releases.
The external_tools
and tools
directories contain external tools committed to the repository in order for the repository to be self-contained in its ability to provide everything that is needed for development.
See external_tools/LICENSE.md
and tools/LICENSE.md
for license information regarding the contents of these directories.
The pm
directory contains project management related meta-data.
This meta-data is crucial for documenting that our internal development processes are correctly followed, as well as providing a record for external audit.
The feature documentation for development tasks resides in pm/tasks
, and their reviews can be found under pm/reviews
.
The docs
directory contains various release documentation-related content, including templates for auto-generated manuals and top-level README files for product releases.
The unit_tests
directory contains unit-test code, including a model of the RTOS schedulers implemented in Python.
This document explains the work flows of creating, reviewing, integrating, and abandoning tasks. A task encapsulates the development and adoption of a feature into the project.
The lifecycle of a task knows the following actions and states, which are described in more detail below.
- create: create a task branch for working on a specific change or improvement without impacting the mainline branch
- update: while work is ongoing in a task, keep it up-to-date with any changes in the mainline branch
- review: signal that work in a task is complete and ready for review and integration
- integrate: once reviewed and accepted, integrate a task into the mainline branch
- abandon: when work on a task ceases without it having been integrated, abandon it to keep it accessible but not clutter the list of active tasks
A task tracks a development effort for a certain feature or improvement. Every change to the repository needs to go through a task.
A task also tracks the corresponding set of repository changes as a git branch. Typically, the terms task, branch, and task branch are used interchangeably. Therefore, managing a task effectively means managing a branch.
The command task.py create TASKNAME
creates a new task with the given name.
This also creates the corresponding branch.
The name is a brief summary of the purpose or goal of the branch.
By convention, the name must only contain characters, digits, hyphens, and underscores.
Also, task names need to be unique.
task.py
checks for this and fails if the task to create has the same name as another task.
From that point on, the developer of a task commits their changes to that branch.
While a developer works on a task, the mainline branch may integrate other tasks in the meantime. It is best practice to frequently bring a task up-to-date with changes on the mainline branch.
As long as a task has not been put up for review, it is ok to rewrite its git history and to rebase it onto the mainline branch.
After a task has been put up for review, it is important to maintain its git history to maintain an audit trail.
Therefore, the mainline branch needs to be merged into the task branch.
The command task.py update
takes care of this.
This section describes the review process first from the perspective of a developer and then from the perspective of a reviewer. Note that once the first round of reviews has started, the git history must no longer be modified under any circumstances. This is necessary to keep the audit trail intact that the review process establishes.
- When a developer considers a task ready for integration, they run
task.py request_reviews
. Make sure that all regression tests pass and notify reviewers. - Reviewers provide their feedback as described under Provide Reviews below, resulting in the review conclusion accepted or rework.
- When a reviewer asks for a task to be reworked, the developer needs to resolve all concerns raised in the review until that same reviewer provides another review in which they accept the updates and the task branch as a whole.
Steps 2 and 3 of the review process continue until there are at least two accepted conclusions and no more rework conclusions.
A reviewer starts a review by running the command task.py review
.
This creates the file pm/reviews/TASKNAME/_REVIEWERNAME_._INDEX_.md
where the review feedback goes.
Changes are expected to meet very high quality standards. In a review, polite and constructive thoroughness is expected and encouraged. A review consists of:
- reviewing the task description in
pm/tasks/TASKNAME
and checking whether the task motivation, goal, and test plan are necessary and sound - reviewing the regression test results and checking whether they pass
- reviewing the differences to the branch point from the mainline branch and whether they:
- fulfill the requirements of the task description
- are correct, complete, minimal, efficient, clean, elegant, and meet the conventions documented in internal-docs/conventions.
- are covered adequately by tests (if applicable)
- pass the test plan documented in the task description
If the reviewer's conclusion is accepted, that review is complete and neither the developer nor the reviewer would typically need to take any further action.
If the reviewer concludes a review with a rework verdict, the developer and reviewer need to resolve the reviewer's concerns. In some cases a reviewer's feedback can be accidentally incomplete, off topic, out of scope, or result from misunderstandings. Reviewers and developers are expected to resolve such cases constructively and in good faith.
Developers have two main avenues to resolve a rework verdict:
- They make the suggested changes and commit them to the task branch like any other change.
- They reply to the reviewer by leaving additional comments in the review file, explaining why they think that no changes are necessary.
Again, they commit these changes to the review file on the task branch like any other change.
The reviewer then replies by creating another review via the
task.py review
command.
Before a task may be integrated into the mainline branch, it needs to meet the following criteria:
- it has passed the review process as described above
- it is up-to-date with the latest revision of the mainline branch
- if merging the mainline branch into the task branch is not trivial and requires (or leads to) functional changes, it is recommended to re-open the review process to receive feedback on the result of the merge
- it passes the regression tests and the task's test plan
When these conditions are met, the command task.py integrate
merges the task into the mainline branch.
To abandon a task that is no longer being developed, manually add the prefix abandoned/
to its name and delete the original branch in the remote repository.
This can be accomplished with a command similar to git push origin TASKNAME:abandoned/TASKNAME :TASKNAME
There are several ways to check whether a task has been integrated into the mainline branch.
- Run the command
git merge-base --is-ancestor TASKNAME master
. If the task branchTASKNAME
has been integrated into the mainline branch, this command exits with exit code 0. If the task branch is not yet integrated, the command exits with a non-zero exit code. - Run the command
git log --grep TASKNAME master
. If the task is integrated, this command typically lists a few changes. If the task branch is not yet integrated, the command usually lists no changes. This approach depends on the task name being very different from any other part of unrelated commit messages.