[ECOS] "Threads on ecos" confirmation needed...

Nick Garnett nickg@ecoscentric.com
Sat Jan 31 16:36:00 GMT 2004


Ivan Horvat <ihorvat@xylon.hr> writes:

[I've concatenated a number of messages here, some attributions may
have been lost.]

> 
> And, if I got it right, would it be possible for future releases of
> ecos to create threads where the kernel itself would
> allocate/deallocate space for thread object? Probably, this may have
> negative impact on RT performance, but may be quite useful feature
> in some cases/applications.

It is a basic design principle of eCos that the kernel should not get
involved in memory allocation issues. Thus all kernel functions need
the memory that they are going to use to be supplied by the
application, or some higher level library.

Different applications have very different needs as far as memory use
is concerned, it would be wrong for the kernel to pre-judge that. It
makes much more sense that memory usage decisions are made at a higher
level. This also means that there are a whole set of error conditions
and synchronization issues that the kernel does not have to deal with,
keeping it simple.


> >Im my opinion there is no need to alter eCos behaviour. eCos does *not* need
> >malloc/free so kernel should not use it. Threads' data can be allocated in
> >global storage and/or on stacks and *nobody* should free that.
> >
> >Obviously you can implement auto-free feature if you want. There is even no
> >need to modify existing eCos code!
> >
> 
> I didn't mean to use malloc/free. If I recall well, there is a hard
> limit on number of threads that can be created in ecos (correct me
> if I'm wrong).

There is no limit at all on the number of threads allowed in eCos, so
long as you have the memory for the thread objects and their
stacks. There is a configurable limit on the number of POSIX threads
that may be in existence at any one time, but you can increase this to
any value.

> So, one can declare an array of Cyg_Thread objects
> (firts thought, also:-):
> 
>       Cyg_Thread thr_slots[MAX_THREADS];
> 
> with number of elements that is equal to maximum number of
> threads. Then, changes are quite simple. For example,
> cyg_thread_create() could be modified first to find a free
> "slot". Then, instead of placing the object into user supplied space
> pointed by 'thread':
> 
>       Cyg_Thread *t = new((void *)thread) Cyg_Thread (...);
> 
> it should place it in one of the "empty slots":
> 
>       Cyg_Thread *t = new((void *)&thr_slots[empty]) Cyg_Thread (...);
> 
> Then, Cyg_Thread::exit() should be also modified to find the 'self'
> within the 'thr_slots' and mark it as unallocated, available for
> future use. This would enable elegant and automatic termination of
> "detached" threads (which I need in large quantities :-).
> 
> The stack could be freed/cleaned by the exiting thread before
> calling *exit() (I think) and the above solution would "free" the
> space for thread object.
> 
> Of course, this is just a preliminary idea...
>

You can easily do all of this outside the kernel. For example:

Cyg_Thread thr_slots[MAX_THREADS];
char thr_stacks[MAX_THREADS][STACK_SIZE];
cyg_uint32 thr_map = 0;

Cyg_Thread *my_new_thread(...)
{
        Cyg_Scheduler::lock();

        int thread;
        HAL_LSBIT_INDEX( thread, thr_map );

        thr_map &= 1<<thread;

        Cyg_Scheduler::unlock();


        Cyg_Thread *t = new((void *)&thr_slots[thread]) Cyg_Thread
                                                          (... , &stack[thread][0], ...);

        return t;
}

void my_exit()
{
        int thread = Cyg_Thread::self()-&thr_slots[0];

        Cyg_Scheduler::lock();

        thr_map |= 1<<thread;

        thread->exit();
}

The one thing that you cannot do within a thread is to have it call
free() to release its thread object or stack. You would then end up
with a thread running in unallocated memory, which might be
reallocated before it successfully exits. Also, the last thing that
exit() does is context switch to another thread, for which we have to
pass through the scheduler. Going through the scheduler with an
invalid current thread would require all sorts of nasty special cases
to be added. This is why we have a two-phase thread shutdown
mechanism: exit() puts the thread to sleep and detaches it from the
scheduler data structures; the final destruction of the thread and the
freeing of its resources must then be done from another thread after
the exit() has succeeded.

> 
> Yes, this is a solution, but it requires another "garbage collector"
> thread. I would like to have the similar functionality provided by
> e.g Linux or Solaris; one can create a detached thread, and don't
> have to worry about its completion, as kernel resources are
> automatically cleaned-up.
>

Linux and Solaris are full kernel/user context operating systems. But
they must still get out of the context of the exiting thread to clean
it up. To do this they go into kernel mode. We don't have this
kernel/user dichotomy, and the only other contexts we have available
are other threads.

> 
> Why this can't be done with stacks? The "exit" method is quite
> simple and can be modified not to use stack variables. Initial
> creation of stack-frame and saving the return address can be
> avoided, I think, using "__attribute__" (or similar) gcc
> directives. Or thread->exit() may be inline... And per-thread
> clean-up code is ok for me, only if it can be done either by the
> thread that is about to exit, or by the *exit() function.

What you are suggesting here would be very architecture-specific and
incredibly fragile -- any change in the compiler might wreck it. While
it might work on register-rich architectures like MIPS or PowerPC, it
would have difficulties on smaller machines like ARM or SH4, and would
be impossible on IA32.

> 
> >
> > IH> The stack could be freed/cleaned by the exiting thread before calling
> > IH> *exit() (I think) and
> > IH> the above solution would "free" the space for thread object.
> >
> >You're calling the thread->exit() so the return address is placed on the
> >stack.
> >
> 
> Of course. But I'm using MIPS, and it doesn't automatically place
> the return address on the stack.  The call to exit() could be
> performed from "asm" macro, without saving the return address.
> Thread never returns from thread->exit(), so return address is not
> needed any more.  However, this problem can also be solved by
> temporarily switching the stack of the thread (with some simple
> synchronization) to universal "pre-exit stack", which would resolve
> this and the above (cleaning stacks) issue as well. This would be,
> of course, platform dependent, but still simple.
>

Again, this seems like a very fragile mechanism. Another design
feature of eCos is that all code (except the initial HAL startup) runs
in the context of a valid thread. Executing code in this proposed
twilight zone would raise all sorts of problems throughout the rest of
the kernel and other subsystems.

In any case we already have other stacks available to us for this
purpose, they belong to other threads, so why not use them?


> >You can create a wrapper around the 'thread' object and modify "exit"
> >function.
> >
> 
> Yes, that's the other possibility.
> 
> >The "garbage collector" could be included inside the "idle" thread.
> >
> >
> 
> Well, this is nice one. Why "idle" should idle, when it can be useful? :-)

One reason for not doing anything in the idle thread is that the
scheduler relies on the idle thread to always be executable. If the
idle thread is doing anything that would require it to synchronize
with other threads, then it might have to sleep. This would seriously
complicate the scheduler.


-- 
Nick Garnett                    eCos Kernel Architect
http://www.ecoscentric.com      The eCos and RedBoot experts


-- 
Before posting, please read the FAQ: http://sources.redhat.com/fom/ecos
and search the list archive: http://sources.redhat.com/ml/ecos-discuss



More information about the Ecos-discuss mailing list