[En-Nut-Discussion] Feature Request 1572837: mutex API nesting level info
Nathan Moore
nategoose at gmail.com
Mon Jul 21 17:48:49 CEST 2008
> This is completely similar: I/O resources are seen as memory for the MCU
> (I/O registers for instance, or a bit in a I/O register or in a
> particular MCU register). Once again, Buthenof remarks are about design,
> not low level details.
When I use memcpy(src, dest, len) on normal RAM from a thread on NutOs I
don't block the only thing that
can step on my operation is an ISR, but since I know that no ISRs are
supposed to be writing to src[] or dest[]
I don't need to protect them from ISRs with
NutEnterCritical/NutExitCritical. Since I know that while another
thread might want to write to src or read/write dest under Linux I would get
a read lock on src and a write lock on
dest, but on NutOs these aren't needed because there is no preemptive
scheduling of threads or multiprocessing.
Relative to anything that should be able to change src[] or dest[] this
operation is atomic, so I don't use any form
of mutual exclusion other than knowing that this is the only running thread
right now.
Even the implementations of semaphores and the recursive mutexes take
advantage of knowing that the memory
that makes up the mutex current owner and lock count and the semaphore count
are not going to be trampled on
by someone else while operating on them. They both contain a NutEvent too,
and that is protected with critical
sections because it uses the standard NutEvent operations which allow both
ISRs and application code to post
events.
>
> > In NutOs you protect memory resources with critical sections (turning off
> > interrupts) which
> > are the widest form of mutex (and sadly are recursive, which I will argue
> > against until I'm blue
> >
> Usually a CPU/MCU offers to turn on/off interrupts, without any nesting
> level. Nested levels may exist in interrupt processing, but a particular
> interrupt can only be turned off or on. You can't for instance, have the
> following code:
NutEnterCritical and NutExitCritical are designed to be able to be called
recurrsively by
pushing the current global interrupt enable flag onto the stack and then
disabling interrupts
to enter the critical and just pop it back off the stack for the exit.
I think the future implementation is to have a global counter that will keep
up with nesting levels
and enable interrupts when that counter is 0. This is because compilers
can't understand when
applications mess with the stack directly.
I've argued against critical sections being recurrsive in the past.
> In a preemptable OS, an ISR is used by the scheduler to eventually yield
> the current thread and select a new one. An ISR in NutOs may wake up a
> thread that was waiting for an I/O to complete (or any other event). So
> basically the fact the OS is preemtable or not has no relation with
> design guidelines linked to recursives mutexes,
Awaking a thread with an event post from an ISR in NutOs only marks that
thread
to be moved to the run queue at the next call to NutThreadYield. It does
not immediatly
wake up that thread, so it's not the same as preemptable multitasking. If
the current running
thread that the ISR momentarily interrupted is in the middle of:
some_flag = 1;
while (some_flag) { /* nothing */ }
and the only place where some_flag is cleared is in another thread (perhapse
the newly awaken one)
then the current thread will run until the processor is reset.
> as you show yourself:
>
> > Recursive mutexes are very very good for IO routines, which do have
> > NutThreadYields in the
> > middle of them.
> > Say I have the code:
> >
> > void print_record(FILE * f, struct record * r) {
> > NutMutexLock(&(f->o_lock));
> > fprintf(f, "%s : %i\t%f\n", r->name, r->count, r->percent);
> > NutMutexUnlock(&(f->o_lock));
> > }
> >
> > void print_status(FILE *f) {
> > struct record * r;
> > NutMutexLock(&(f->o_lock));
> > fprintf(f, "Current status:\n");
> > for (r = record_list; r; r = r->next) {
> > print_record(f, r);
> > }
> > NutMutexUnlock(&(f->o_lock));
> > }
> >
> > void print_record_named(char *n) {
> > struct record *r;
> > for (r = record_list, r, r = r->next) {
> > if (0 == strcmp(n, r->name) ) {
> > print_record(f, r);
> > }
> > }
> > }
> >
> > Without the recursive mutex (which isn't actually in nut's FILE struct)
> > running code that uses these
> > (or any fprintf(f, ...) for that matter) could not guarentee that all of
> > their output was contiguous.
> >
> That's exactly memory sharing as described by Buthenof. You would
> 'print' into a memory buffer or into a UART driven by I/O registed, the
> problem is exactly the same.
No matter how big you make your buffers I can come up with a reasonable
example that would need a bigger buffer to work correctly.
Also I might only print something that big very rarely, and my example
showed
that the size of what I'm printing is not deterministic. The list may be
empty or
the list itself may take up half of the 128K of ram I have attached to my
AVR
(less is actually directly addressable and for the stdio f()s it can't be in
paged
ram.
>
> > A higher priority thread could pop in there and write it's junk in the
> > middle of this output.
> > With the recursive mutex you can reuse component print functions and also
> > lock at the higher level
> > when you need to print other stuff together with those.
> >
>
> This is an excellent example of a design problem: you are using the same
> mutex for different needs.
>
> 1) You have a first level of locking which is 'lower' than the second.
> This first level is in print_record() and needs a mutex to protect the
> file descriptor being used to access the buffer that this file
> descriptor uses.
>
> 2) You have a second level of usage in print_status(), which is 'higher'
> than the first one, since you want to make sure that a bunch of callsto
> print_record() are atomic. But you are also making direct usage of a
> file descriptor in print_status (to print 'Current status:') which is
> still another need.
>
> All this mixed usages of a single mutex makes you think that recursive
> mutexes are the solution.
Well that's exactly what they are good for. You have not told me why they
are bad, only
that I'm doing stuff wrong. I don't see anything wrong with using a
recursive mutex.
>
>
> Here is another way:
>
> - have a 'lock_puts()' function:
>
> void lock_puts(FILE * f, char *s) {
> NutMutexLock(&(f->o_lock));
> fputs(s,f);
> NutMutexUnlock(&(f->o_lock));
> }
>
> - everything that needs to print prepares a buffer in memory and sends
> it to lock_puts().
>
> No more recursive mutex needs, shortest locking time, the mutex is
> clearly used only to protect the file descriptor and nothing else,
> someone coming later and reading lock_puts() description understands
> easily how to use it, and if ever you change of OS later, the same logic
> will apply to your code base.
Ok, implement a splice with your method.
I want to take stuff from Uart1 and output it to Uart2 up until an IO line
goes low.
When that happens I want Uart1 to be used by other threads to output any
status
messages that they may have until that IO line goes high again (which will
likely
cause an interrupt which will post an event).
Nathan
More information about the En-Nut-Discussion
mailing list