This is the mail archive of the ecos-discuss@sources.redhat.com mailing list for the eCos project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: "Threads on ecos" confirmation needed...



Nick, Thanks for the extensive reply.

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.


I agree with this. However in ecos, the borderline between the "user" and "kernel"
context is a quite "fuzzy", not as sharp as on general purpose OS-es. Sometimes, this
creates confusion and misunderstanding.


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.


Yes, I realized that also from some previous posts. I obviously confused it with
the number of posix threads, or non-default scheduler...


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


I assume "outside the kernel" means at the level of cyg_thread_*() functions?

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.


Actually, we are speaking about the same things, for the most of the part. The
my_new_thread(...) is actually replacement for cyg_thread_create(), as I suggested.
I made a mistake saying "...Cyg_Thread::exit() should be also modified...", as it
is not necessary. All that is needed is modification to cyg_thread_exit().
So, I would slightly change Your implementation of my_new_thread():


Cyg_Thread thr_slots[MAX_THREADS];
void *thr_stacks[MAX_THREADS];   // CHANGED
cyg_uint32 thr_map = 0;
int thread;   // MOVED HERE, NOW GLOBAL

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

HAL_LSBIT_INDEX( thread, thr_map );

thr_map &= 1<<thread;

Cyg_Scheduler::unlock();

       thr_stacks[thread] = malloc(STACKSIZE);   // ADDED
       Cyg_Thread *t = new((void *)&thr_slots[thread]) Cyg_Thread
                                                         (... , thr_stacks[thread], ...);

       return t;
}


This being, of course, exemplary skeleton.
Besides, isn't this limited to 32 threads, the number of bits available in "cyg_uint32 thr_map",
so that the MAX_THREAD can be at most 32?



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.

That's exactly my goal: providing another context for freeing the stack but not
the other therads' context...


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.


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?


It would definitely be platform specific, but this suits my needs as I'm interested
only in SPARC, PPC and especially MIPS platform. However, I don't agree that this
would be a very fragile mechanism. Inline "asm" macros and "noreturn" attribute are *well*
supported by the gcc (and they will remain to be supported bu future versions).
For example, on MIPS, I implemented a mechanism that enables the remote-debugged application to
pass the control back to the redboot while redboots' stabs notify gdb about application exit
so the gdb reports "Program exited normally.". That required small amount of code in
generic-stub.c, adding one HAL virtual vector entry which is used by the application to tell
redboots' stabs that it wants to exit, followed by "asm volatile ("\tbreak 0x5\n");", which is the trigger.
After that, redboot prompt (console), or the same gdb can be *reused* to load the same or new
program (which avoids power-cycling) allowing comfortable work with GUI IDE's built
using Eclipse, etc.
The MIPS my_exit() can be implement like this (skeleton again):


int exit_stack[STACKSIZE/sizeof(int)]; // ADDED
void (*exit_fn)(void) __attribute__((noreturn)); // ADDED
__attribute__((noreturn)) void my_exit() // MODIFIED
{
Cyg_Scheduler::lock(); // REORDERED WITH THE FOLOWING LINE
thread = Cyg_Thread::self()-&thr_slots[0]; // REORDERED WITH THE PREVIOUS LINE


thr_map |= 1<<thread;

exit_fn = thread->exit;

// load pre-exit stack
asm volatile ("\tmove $29, %0\n": : "r"(exit_stack): "$29");
/* Now, we are running on alternate stack... */
// free the threads' stack
free(thr_stacks[thread]);


       (*exit_fn)();
}


Now, "thread" and exit method are in global variables, locking scheduler ensures that no
other exiting thread would get the chance to enter the "critical section" of setting the new stack
and messing it up. After switching to alternate stack, normal stack can be freed. Then the
"exit" method would do its job and the scheduler lock will be removed after calling
Cyg_Scheduler::reschedule(); at the end of Cyg_Thread::exit(). Maybe there are some
"twilight zone" elements, but the HAL_THREAD_SWITCH_CONTEXT() and its friend
HAL_THREAD_LOAD_CONTEXT() certainly qualify for the Twilight Zone on *any* platform,
anyway. :-)
But then again, maybe I have some serious flaw(s)?


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.

Thanks for this information, I was unaware of that requirement for the idle thread.


Regards, Ivan.




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


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]