Hi Phil,
>>> I don't. (A state-machine design with one big overall loop doing
>>> housekeeping is the ticket.)
>>>
>>> When there's an operating system available, you use blocking calls with
>>> timeout parameters, e.g. select(). When there isn't, you poll once, set
>>> the relevant interrupt enable, and go back to the housekeeping routine.
>>
>> So, each timeout requires an ISR? Let an OS virtualize that for you.
>> HUNDREDS of timeouts from a single "service". "Tickle me in 23.5 ms"
>> "Unblock this function call if it hasn't completed in 30ms", etc.
>
> You just crossed the line from fandom to irrationality. Of course not,
> silly.
You said:
"you poll once, set the relevant interrupt enable, and go back
to the housekeeping routine"
This begged the question, "So what happens when some OTHER aspect of
your product requires a CONCURRENT timeout?" E.g., you're blinking
a light (1 sec on, 1 sec off), waiting for user input (with an "accept
default if not overriden in X seconds" and implementing a timeout on a
disk access. Do you "set the relevant interrupt enable" for each of
those three independent activities?
"Of course not, silly!"
Such an approach would lead to serious constraints on your design
*or* having to deal with unavailable resources ("Sorry, I can't blink
the light right now. Please try again, later.")
My question is based on actual experience. I've encountered systems
where a hardware timer *was* used for all timing related "events" -- but
not in the "typical" manner (of virtualizing "software timers").
Instead, the timer was programmed to expire at the nearest
dead-line/event and the timer ISR then reloaded the (hardware)
timer from a delta queue. Much *finer* control of time (though
often unnecessary) but at a higher cost.
>>> Or the main loop can consist of a function--basically a thread scheduler
>>> on a diet--that looks at the flags, manages the state transitions, and
>>> calls the relevant service functions. When you have to go that far, a
>>> lightweight RTOS starts looking pretty good, though I've never used one
>>> myself.
>>>
>>>> In the second case, you have a mechanism to cleanly release
>>>> the blocking state. As such, a CONTROL THREAD (thread1 in
>>>> my example) can just as easily release that block (in addition
>>>> to a timeout *or* in PLACE of a timeout).
>>>
>>> If there's a way of making the call return unsuccessfully, that's
>>> functionally the same. You have to have error recovery someplace, and
>>> that requires saving state.
>>
>> You not only need a mechanism for *reporting* the "error exit",
>> but you also need a mechanism for pulling attention (execution)
>> away from whatever statement(s) is doing the actual block
>> (whether it is waiting for an IRQ, an event flag, etc.)
>
> Only if you do it your way. The normal approach doesn't need that--you
> just poll and move on.
What makes "polling" the "normal approach"? Do you really think
your cell phone polls everything that "might be/not-be ready"?
>>>> This allows an *algorithm* to determine when a blocking thread
>>>> is released. Time, user killing task, phase of moon, etc.
>>>> And, *how* that algorithm goes about this is nicely documented
>>>> by the API. Not ad hoc measures ("Let's set a flag and have the
>>>> spinning task examine the flag to see if it should stop spinning"
>>>> "No, why don't we just ZERO the timer that the task is already
>>>> waiting on and get the 'release' FOR FREE?" "What if we just
>>>> disable the task and restart it?" etc.)
>>>
>>> Anyone who's worried about ad hoc stuff should swear off embedded
>>> programming. ;)
>>
>> That's the appeal of working in a structured environment.
>> There are "established ways" of doing these things. You
>> *don't* have to think about "tricks" to coax the implementation
>> to do something for which you recently discovered a need.
>> "Gee, the file copy hangs indefinitely. How do I *abort*
>> it?" or, "Crap! it's an endless STREAM that I've told it
>> to copy. I surely don't want to wait around for THAT!"
>
> But that's a manufactured problem. If you don't wait, you don't have to
> worry. Just keep going, and the next timer tick or UART interrupt will
> let you know what to do.
*You* have to implement policy and mechanism. You should only
have to worry about *policy*. If you are implementing mechanism
in each individual instance, you carry a higher cost for that.
Did you remember to check to see if power is failing so you
can abort the wait *early*? Where *else* do I need to inject
the code to do that?
>>>> OS's are all about moving common used mechanisms into a formal
>>>> structure that can be exploited by tasks. Forcing EACH task
>>>> to assume the details of these mechanisms themselves (e.g.,
>>>> tracking time, *implementing* timeouts, etc.) makes extra
>>>> work for them and represents opportunities for error to
>>>> creep in.
>>>
>>> Sure. At least above some threshold of complexity. For my stuff, the
>>> reason I'm using a MCU at all is that I need to interleave control and
>>> data acq, which means maintaining control of the interrupts to attain
>>> timing coherence. That's hard with a RTOS, no?
>>
>> Depends on what guarantees you want from the RTOS (note that
>> I've only been talking about MTOS's, so far). Instead of
>> picking hardware resources to drive your algorithms (ISR's,
>> etc.), you pick appropriate VIRTUAL resources for the same
>> thing. The RTOS gives you guarantees -- just like the
>> silicon gives you guarantees -- about how long you will have
>> to wait before your code is activated in a particular set of
>> conditions (e.g., the hardware says IRQ latency is X *once*
>> your IRQ is enabled and of sufficient priority and the executing
>> instruction has completed execution, etc.)
>
> AFAIK it only gives you _maximum_ wait times. For timing coherence, wait
> times that are too short are just as bad.
The minimum is ZERO from the time of the triggering event.
You happily live with that same zero when the triggering
event is a hardware interrupt. So, what's the difference
if that same zero comes from a VIRTUAL interrupt?
You wouldn't set up your interrupt to trigger some unpredictable
amount of time before the time at which you *wanted* to begin
your activity. So, don't have the virtual interrupt trigger
before that time and you don't have a problem!
The problem with the "virtual machine" that multitasking
environments provide is that the timescales are different.
You can (reasonably) guarantee that an ISR will be invoked
within microseconds of the actual "hardware event" that
tugged on the IRQ line. You can see the (small number) of
other such "activities" that might be competing for the
"foreground".
But, in the MTOS environment, those competing entities can
be many more and many more complex. Instead of a handful
of "interrupt users", you can have scores of "tasking
users". If you've not considered how those tasks co-operate,
it is hard to predict what might be competing for specific
resources (e.g., the CPU) at specific times.
This is akin to not keeping track of which IRQ's might be
active (and at which priorities) at any given time.
Work in an environment with scores of (active) IRQ's and
you face a similar problem.
[But that doesn't mean it can't be managed. That's why
interrupts AND tasks have "priorities"]
>>>>> Once you become sufficiently paranoid about deadlock and timing holes,
>>>>> the main remaining difficulty with multithread is debugging it. (I've
>>>>> been writing multithreaded apps since 1992, and got my first SMP
>>>>> machine
>>>>> in about 1996, an early IBM Intellistation with dual Pentium Pros.)
>>>>
>>>> You write *any* program with debugging in mind. E.g., to debug the
>>>> file copy example I posed, I would debug each thread independently
>>>> in a "friendly environment" (a PC, ICE, mainframe, etc.). The
>>>> read and write can be redirected to real files -- or the "console".
>>>> The role of the missing worker thread (consumer or producer)
>>>> can be faked -- with something that appears to remove or insert
>>>> data from/to the buffer. Etc.
>>>
>>> Good luck debugging clusterized simulators thread-by-thread. My parallel
>>> FDTD code would execute half a time step and then croak. If you have
>>> threads doing essentially unrelated things, so that you have N programs
>>> running concurrently, that can work fine.
>>
>> You have to design with decoupling in mind. I.e., the file copy
>> example will work regardless of which thread you choose to
>> "single step". It just works very SLOWLY! :> (like debugging
>> a processor design at DC).
>
> How would you do that in the simulator? It works only in a restricted
> problem domain.
File copy? (assuming no disk) Replace write() and read() with dummy
routines that move bytes to/from a large FIFO (or the console, etc.).
Watch the contents of that FIFO as the program executes. Or, break
execution and have a look at periodic/random intervals.
It depends on the nature of the dummy data that you feed in. E.g,
if your read() merely grabs the current time of day (to the second)
and returns that as the data read from the "disk", just look
through the FIFO and verify that the most recent "writes" resemble
the time displayed by the clock on your wall.
>> One of the projects I am working on currently has dozens of
>> processors coupled loosely or tightly. Before I started the
>> design (both the hardware and software ends of it), I thought
>> about how I would *economically* be able to debug algorithms
>> where one part of the algorithm was executing on one CPU
>> and another was executing on another CPU both under the control
>> of a *third* CPU, etc.
>>
>> (It is *dizzying* to sit between three workstations talking
>> to three different processes on three different nodes running
>> three different pieces of code ... and trying to keep track of
>> what's happening, where! But, the code's expectations of
>> timeliness have been crafted so that I can do this without
>> breaking it)
>
> Try a few hundred processors.
That depends on the type of application hosted on those processors.
E.g., you would debug a symmetric (or nearly symmetric) algorithm
different than one in which each processor was doing something
specific/different.
E.g., when the home automation system is complete, here, there
will be upwards of 70 processors running at any given time.
Most, small little "motes" (CPU's are cheap!). If I notice that
the system is failing to announce "arrivals" properly (i.e.,
someone just rang the front doorbell), I would *not* watch the
mote that monitors the doorbell "button" to notice the contact
closure. Nor would I watch that closure being debounced,
recognized by the daemon charged with watching it. Nor any of
the various mechanisms that would result in its transfer to
the "automation controller".
I wouldn't watch the daemons in the controller recognize that
incoming event. Nor trace the execution of the DBMS as it
determines how to respond/notify that event. Nor, the speech
synthesizer gluing together diphones to tell me what's just
happened.
I wouldn't look at the steps that the localizer undertakes to
figure out where I *am* (physically) in the house. Nor would
I watch the audio be packetized for transmission *to* "me".
[Nor any of the other little boxes that have to participate
in this process]
Instead, I'd monitor the log (black box) at the controller
while pressing the doorbell. "Ah! It recognized the incoming
event. So, I know the sensing mote, network fabric, etc.
on that side of the application appears to be functioning.
Now, check the black box for the localizer to see where it
*thinks* I'm located. Hmmm, that explains the problem; it
thinks I'm in the back yard and has routed the announcement
to the transmitting mote that services the back yard!
Obviously too far away from my current location for my
earpiece to pick it up! I guess I'll need to focus on
the localizer subsystem..."
When I designed the system, I considered how difficult it would
be to debug a "process" (in the abstract sense) that was
distributed over multiple, physically separate, nodes. I
know I (personally) can't juggle more than three "contexts"
effectively in my head. So, I discouraged functionality
from involving more than three devices AT A TIME.
>>>>> If you have a good debugger, one that can at least bring up a source
>>>>> window for each thread at each breakpoint, you can find stuff pretty
>>>>> readily. Otherwise it's just iterated code reading and printf(). I've
>>>>
>>>> Implement black boxes ("flight recorders") and push status into them
>>>> with an "#if DEBUG" enabled macro (this allows them to be removed
>>>> later, if you don't want to ship the product with that). Being
>>>> internal mechanisms (i.e., no costly I/O), they have minimal impact on
>>>> performance. And, they can easily be sized to cover more or less
>>>> event-time.
>>>
>>> For events that you're expecting. Hitting a memory corruption bug isn't
>>> that friendly, unless you have a mechanism like mudflap that maintains a
>>> copy of the call stack.
>>
>> Of course! But the black box gives you a cheap way of seeing how far
>> you got -- and logging values of interest -- without seriously impacting
>> performance (which could alter the correctness of an RT algorithm).
>>
>> It's the equivalent of attaching a logic analyzer to trace "whatever".
>> But, since you can execute *code* to decide what (and when!) to store,
>> you aren't as constrained as you are with a passive LA approach.
>
> Modern MCU development systems do that for you already. There are
> silicon resources in the ARM for all of that--no coding required.
Yes, this goes back to earlier x86 implementations, etc. But, using
those requires you to either run the application at reduced speed
*or* limit how much "history" you can track.
The former isn't always possible (without moving effort into a
"good simulation"). The latter requires you to be able to
identify a suitable trigger that will be proximate to the
activity of interest.
The black box approach lets you litter your code with "log writes",
decide how many resources you want to devote to the caching of
those writes, and then let the code "just run". You can push lots
of extra detail into the black box that you can later chose to ignore.
Or, selectively disable reports that are not of interest to remove
some of that clutter (and effectively increase the size of the
FIFO).
In resource starved environments, you can change the macros that
implement this so that they simply push bytes to a fixed address
(even if that address is already "allocated" to something)
and monitor that address (with a logic analyzer of an external
"WOM").
>> For (traditional) serial ports, I have a variety of strategies based
>> on the performance level required and resources available to me.
>>
>> Outgoing data is placed in a shared TxFIFO. That action implicitly (or
>> explicitly) notifies a housekeeping task that is enabled whenever the
>> transmitter is idle. This task primes the transmitter with the
>> "oldest" character enqueued (e.g., I/O need not be character at a
>> time) and enables the TxEmpty interrupt. Once completed, this task
>> dies (it will be reenabled when/if the transmit FIFO ever empties).
>>
>> Thereafter, each TxIRQ pulls a character from the TxFIFO and places it
>> in the transmitter. So, the transmitter runs at the full data rate.
>> When the TxFIFO empties (i.e., a TxIRQ finds nothing in the buffer),
>> the TxIRQ is disabled and the transmitter housekeeping task is
>> reenabled (so it can watch for an appropriate "write()")
>>
> Sure. But you can just as easily do that in a housekeeping loop, like
> everybody else does.
If you move it to a background activity, then you run the risk of
the UART exhausting its available data (buffer) when the comm rate
exceeds the rate at which your "loop" can get around to re-servicing
it. It also means you have to dick with the interrupt mask more
often (since it is the entity that enforces atomic access to the
shared resource, in that case)
>>>> In the late 70's, I designed using the "foreground background"
>>>> style. It was *always* hard adding new features (when you think
>>>> about user interfaces and multiple concurrent "machine/mechanism"
>>>> activities). Trying to get timely responses from multiple
>>>> *competing* activities/requirements leads you to more brittle
>>>> solutions. To "hard" RT instead of a more robust "soft" RT
>>>> approach. (i.e., "if I *don't* get this done in time, the product
>>>> is broken. I need a faster CPU. I need another ISR. etc.")
>>>
>>> For something like that, I'd probably want to use multiple processors.
>>> Fast unis are harder to manage and make the gizmo more brittle and
>>> harder to expand.
>>
>> <frown> I begged for a second processor on the first such machine.
>> There were subsystems that ran continuously eating up huge parts
>> of the CPU (data acquisition). They seemed ideal for "spinning off".
>> But, we had a philosophy of pushing the processor into overload
>> instead of adding hardware.
>
> I think that's a dumb philosophy. CPU power is dirt cheap, programming
> resources and (especially) field failures aren't.
When it's NOT your name on the building, you usually don't have
much choice! :> Even folks coming from engineering backgrounds
seem to *quickly* forget the sort of issues that affect design -- once
they move into managerial positions. This is especially true of
new technologies (putting processors *into* consumer products
was a relatively novel idea in the 70's)
>>>> [In the barcode example, a user could INTENTIONALLY run a
>>>> barcode label across the scanner at very high rates of speed
>>>> *continuously* -- like shaking a can of paint. The system
>>>> would grind to a halt as resources were shifted to recording
>>>> and processing those edges. When the user's arm got tired
>>>> (it takes very little time to tire when you are doing that sort
>>>> of thing), the system just picked up where it left off.]
>>>
>>> Okay, but that isn't an argument in favour of multiple threads, just
>>> lean ISRs.
>>
>> But you extend the same philosphy upwards into the higher levels
>> of the application. I.e., cut the application into little pieces
>> that are easy to "get right" instead of trying to push them all
>> together into a monolithic block of code.
>
> That just hides the interactions and makes them harder to figure out
How does it "hide" them? You know where a given task's inputs come
from (you know where the characters that you receive from your
interrupt driven UART come from!). Why would you suddenly become
"undisciplined" in tracking the relationships of tasks? Don't
you know what the various (physical) processors in a multiprocessing
application are doing? The relationships between their data?
> when they occur. A housekeeping loop that's too slow is blatantly
> obvious, but latent race conditions that only show up when the system is
> stressed are not obvious. Obvious is good. Obvious is worth a lot. (Did
> I mention that obvious problems are better than non-obvious ones?)
>>> Mine typically loop at
>>> kilohertz rates, sometimes faster. Of course I'm not using a 1702.
>>>
>>>> While you're scanning keys, you might as well do the display
>>>> multiplexing. And, of course, you have to periodically obtain
>>>> the current LORAN coordinates so you can figure out where you
>>>> *are* (usually done every 10 GRI's). Etc.
>>>>
>>>> [Recall, we're talking about a 2 or 3MHz CPU I.e., memory
>>>> cycle times of about a microsecond :> ]
>>>>
>>>> It just doesn't make sense to saddle yourself with imposing and
>>>> maintaining all of this "mechanism" at a time when resources are
>>>> so plentiful (in all but HUGE volume products -- mice, keyboards,
>>>> etc.)
>>>
>>> If I had all that foofaraw to worry about, I might well use an RTOS too.
>>> But that's not enough to justify your blanket trashing of all
>>> housekeeping loops.
>>
>> I'm not "trashing all housekeeping loops".
>
> Your fanboy style is unbalanced enough that you could have fooled me.
No. Use what you *need* to get the job done. But, don't use
less than you *can* thinking you're saving something!
You wouldn't use small signal transistors when you could use
an op amp to solve the same problem (unless you could reduce
the problem to a few discretes *and* had significant cost pressure
on you to do so).
You wouldn't use fixed BINARY point (i.e., not just natural integers)
math when you could use floating point (unless you could reduce
the problem to whole integers and had significant resource pressure
to do so).
People seem to shy away from OS's in embedded applications more
often than they should. I haven't been able to figure out if
this is due to a lack of experience, being at the mercy of an
OS vendor or just "not wanting to move out of their comfort zone".
I've had to "fix" too many products over the years that someone
"threw together" with a pile of spaghetti code absent any real
structure that a more formal environment could have provided.
Almost always the foreground-background split. It works fine -- until
it doesn't. Until the customer wants a feature that it didn't
anticipate. Until the load on the processor is increased and
the solution fails to scale.
Many years ago, I had to recover some sources for a product that
the owner had "misplaced". So, I was faced with a reverse engineering
task *and* a "product enhancement" task.
The enhancement was trivial, conceptually. Of course, clients
see *everything* as trivial and expect that to be reflected in
what they will have to *pay*! :>
The product itself wasn't complex. Something that I could have
knocked off "in no time".
I bid a comfortable number for the documentation recovery, and the
"enhancements". I was spot on regarding how much effort it would
take to reverse engineer the code. And, the effort it would
require for me to document that code (remember, there are no
names for variables, functions, entry points, etc.).
But, the code that was revealed was *so* poorly written that the
enhancements proved to be painful to implement. Since the
product was already designed and deployed, I couldn't change the
resources available to the application (damn near every *byte*
of ROM and RAM was spoken for. And changing the processor
would have been pure fantasy!)
So, I was stuck with a bad implementation that I now had to
"fit in". What had looked like a walk in the park turned into
a project from hell.
"*Will* I be able to make this work in this framework??"
(thankfully, it was only a few months)
> > Rather, I'm praising the
>> benefits of using more structured programming environments. I could
>> grow my own vegetables and raise my own beef. Or, I could go to the
>> grocery and *buy* those things. What do I want to spend my (limited)
>> time doing? How much risk do I want to take on (what happens if the
>> few cattle get sick and die? do I go hungry??)
>>
>> The executive I outlined in that PDF was used for 100KB+ binaries.
>> WRITTEN IN ASM! Granted, it was more than 25 years ago (the first
>> pass of the article bears a 1987 date and it *followed* the actual
>> use of the technique) but that doesn't mean there weren't better
>> ways of doing things at that time. Folks would argue about whether
>> to use 0315 or 0xCD -- I would ask, "What's wrong with 'Call'?"
>>
>> (Amusing that one of our products hosted a BASIC interpreter...
>> *under* that crippled multitasking framework! Talk about making
>> life hard for yourself... :< )
>
> Right, but again, this is 2012.
You glossed over the example. Imagine how *you* would add a
"scripting language" to the "one big loop" design approach.
Of course, you *can* do it -- as evidenced by the fact that we
ran a BASIC interpreter on this bizarre platform *alongside*
(concurrent with) the other tasks that ran the instrument.
But, the design of that interpreter was needlessly (?)
complicated by the crippled environment. I.e., it had
to EXPLICITLY relinquish control of the processor (preserving
NOTHING on the stack -- even a record of "what it was
doing at the time") routinely to ensure the other tasks
weren't delayed in their processing.
Do you interpret a single scripted statement? How long
will that take? Do you constrain the user so that he
can;t write "expensive" statements? No transcendentals
in expressions, etc.? Do you interpret *part* of a statement
and (manually) keep track of where you were in that process
("OK, I have computed the cosine of the angle. Next time I
get a chance, I'll compute 2*a*b. Then, the time after that,
I'll work on squaring a. And squaring b, the time after
that! Then, put it all together and tackle the square root.
*Then* i can advance to the next statement in the script...")
When you have a "virtual machine" (afforded by a multitasking
environment), you concentrate on the problem you are solving
(interpreting the script) and NOT the mechanism of "sharing the
processor".
When I'm driving *nails*, I surely don't want to heft a LARGE ROCK
if I've got a hammer nearby! ;)
Let's just agree to disagree. If your solution works for you, great.
Mine works for me. Let's just each avoid *maintaining* the other's
projects! :>