Table of Contents

Explanation of essential TNKernel problems as well as several examples of poor implementation.

Essential problems of TNKernel

The most essential problem is that TNKernel is a very hastily-written project. Several concepts are just poorly thought out, others are poorly implemented: there is a lot of code duplication and inconsistency;

It is untested: there are no unit tests for the kernel, this is not acceptable for the project like real-time kernel;

As a result of the two above, the kernel is buggy. And even more, the kernel is really hard to maintain because of inconsistency, so when we add new features or change something, we are likely to add new bugs as well.

It is unsupported. I've written to the Yuri Tiomkin about troubles with MAKE_ALIG() macro as well as about bugs in the kernel, my messages were just ignored;

People are unable to contribute to the kernel. There is even no some kind of main repository: there's just an archive with source code published on the website. When someone wants to contribute, he or she has no way to do this but to write to Yuri, but, as I've already mentioned, messages like this are ignored. So, eventually people end up maintaining their own modification of TNKernel. Rest assured this will never happen with TNeo: even if some day I completely stop maintaining the kernel, I'll transfer ownership of the repository to someone who is interested in maintaining it.

Documentation is far from perfect and it lives separately of the project itself: latest kernel version at the moment is 2.7 (published at 2013), but latest documentation is for 2.3 (published at 2006).

Examples of poor implementation

One entry point, one exit point

The most common example that happens across all TNKernel sources is code like the following:

I understand there are a lot of people that don't agree with me on this (mostly because they religiously believe that goto is unconditionally evil), but anyway I decided to explain it. And, let's go further:

While multiple goto-s to single label are better than multiple return statements, it becomes less useful as we get to something more complicated. Imagine we need to perform some checks before disabling interrupts, and perform some other checks after disabling them. Then, we have to create two labels, like that:

Then, for each new error handling, we should just add new else if block, and there's no need to care where to go if error happened. Let the compiler do the branching job for you. More, this code looks more compact.

Needless to say, I already found such bug in original TNKernel 2.7 code. The function tn_sys_tslice_ticks() looks as follows:

If you look closely, you can see that if wrong params were given, TERR_WRONG_PARAM is returned, and interrupts remain disabled. If we follow the one entry point, one exit point rule, this bug is much less likely to happen.

Don't repeat yourself

Original TNKernel 2.7 code has a lot of code duplication. So many similar things are done in several places just by copy-pasting the code.

Mutexes have complicated algorithms for task priorities. It is implemented in inconsistent, messy manner, which leads to bugs (refer to Bugs of TNKernel 2.7)

Transitions between task states are done, again, in inconsistent copy-pasting manner. When we need to move task from, say, RUNNABLE state to the WAIT state, it's not enough to just clear one flag and set another one: we also need to remove it from whatever run queue the task is contained, probably find next task to run, then set reason of waiting, probably add to wait queue, set up timeout if specified, etc. In original TNKernel 2.7, there's no general mechanism to do this.

Meanwhile, the correct way is to create three functions for each state:

to set the state;

to clear the state;

to test if the state active.

And then, when we need to move task from one state to another, we typically should just call two functions: one for clearing current state, and one for settine a new one. It is consistent, and of course this approach is used in TNeo.

As a result of the violation of the rule Don't repeat yourself, when we need to change something, we need to change it in several places. Needless to say, it is very error-prone practice, and of course there are bugs in original TNKernel because of that (refer to Bugs of TNKernel 2.7).

Macros that return from function

TNKernel uses architecture-depended macros like TN_CHECK_NON_INT_CONTEXT. This macro checks the current context (task or ISR), and if it is ISR, it returns TERR_WRONG_PARAM.

It isn't obvious to the reader of the code, but things like returning from function must be as obvious as possible.

It is better to invent some function that tests current context, and return the value explicitly:

Code for doubly-linked lists

TNKernel uses doubly-linked lists heavily, which is very good. I must admit that I really like the way data is organized in TNKernel. But, unfortunately, code that manages data is far from perfect, as I already mentioned.

So, let's get to the lists. I won't paste all the macros here, just make some overview. If we have a list, it's very common task to iterate through it. Typical snippet in TNKernel looks like this:

If task that waits for mutex is in WAIT+SUSPEND state, and mutex is deleted, TERR_NO_ERR is returned after returning from SUSPEND state, instead of TERR_DLT. The same for queue deletion, semaphore deletion, event deletion.

Probably not a "bug", but an issue in the data queue: actual capacity of the buffer is less by 1 than user has specified and allocated

Event: if TN_EVENT_ATTR_CLR flag is set, and the task that is waiting for event is suspended, this flag TN_EVENT_ATTR_CLR is ignored (pattern is not reset). I can't say this bug is "fixed" because TNeo has event groups instead of events, and there is no TN_EVENT_ATTR_CLR flag.

Bugs with mutexes are the direct result of the inconsistency and copy-pasting the code, as well as lack of unit tests.