BX task switching, shared variables and atomic access

Started by arhodes19044 June 22, 2005
When a BX task switch occurs can a it interrupt a multibyte data write
in the middle of a write operation? I.E. Are BX variable read and
write operations atomic?

I ask this because, while the BX is "multitasking", in actuality only
one task is working at a time. So, if one task is going to write data,
and another is going to read the data, do I have to lock the task while
a multibyte write/read is active?

I never really understood the semaphore function. I always thought of
a semaphore as a flag, but maybe when I recently read the
documentation, I thought that maybe it forced a variable to have atomic
access.

As I write this I am not sure I am expressing my question clearly and
succinctly, but I will see in what way I have confused you guys with it.

-Tony


--- In basicx@basi..., "arhodes19044" <spamiam@c...> wrote:
> When a BX task switch occurs can a it interrupt a multibyte
> data write in the middle of a write operation? I.E. Are BX
> variable read and write operations atomic?

I surmise that atomicity with respect to multi-tasking is at the
pcode instruction level. That is, a task switch can occur (unless
otherwise prevented) after any pcode instruction has completed and
before the next begins. This generally means that a task switch
won't occur in the midst of writing one variable but may occur in
the midst of a read-modify-write sequence.

For example, if you look at the pcode that is generated by I = I + 2
you'll see that there are typically four pcode instructions
generated:

pushI t.i
pushB #2
addI
popI t.i

A task switch could occur after any one of these instructions.
Under some conditions, the compiler will generate a single
instruction for the read, update, write of a variable. For example,
if I is a module-level or local integer variable, the statement I =
I + 1 generates a single pcode instruction. On the other hand, if I
is a pass-by-reference parameter then 5 pcode instructions are
generated.

You can see, then, that the answer to your first question is ... it
depends.

> I ask this because, while the BX is "multitasking", in actuality
> only one task is working at a time. So, if one task is going to
> write data, and another is going to read the data, do I have to
> lock the task while a multibyte write/read is active?
>
> I never really understood the semaphore function. I always
> thought of a semaphore as a flag, but maybe when I recently
> read the documentation, I thought that maybe it forced a
> variable to have atomic access.

The Semaphore() call was added to provide an "atomic test and set"
operation on a boolean variable. What this mean is that no other
task can intervene between when it is discovered that the boolean
variable is not set (indicating that it is available) and when it is
set (indicating that it is no longer available).

Don



> ... the Semaphore() function.

I'll take a stab at it.

Semaphore() is a combination boolean test and boolean set function of a
special sort: if the boolean var argument value is False, Semaphore()
will make it True, and yield True; if the var argument value is already
True, Semaphore() will not change it, and will yield False. Because the
boolean var test and change cannot be interrupted, the function permits
guaranteeing exclusive rights to some resource (in essence,
cooperatively making the resource atomic).

An example:

I use FRAM SPI memory. I write collected multibyte sample data to the
FRAM in one interrupt-driven task, scan and scale the FRAM data in
another task, and display it from a third task. Since the SPI interface
and the FRAM require multibyte transfers of several sequential commands,
the interrupt-driven task cannot simply blast it's collected data to the
FRAM without risk of interfering with a process or display access of the
data; i.e. if the FRAM is busy, the write will have to wait. As soon as
the FRAM is available, the write can proceed. Since only one task may
handle the FRAM at any given moment, I use a byte (tfFRAMLock, which
must be initialized False) to indicate FRAM-busy and test for it like
this:

do while not Semaphore(tfFRAMLock) 'loop if FRAM-busy
call Sleep(0) 'give time to using process
loop

tfFRAMLock will be False when Semaphore() executes if the FRAM is
available; if that's so, tfFRAMLock will be set to True so the do/loop
will fail, allowing this task to use the FRAM. If the FRAM is in use by
another task, tfFRAMLock will already be True, the do/loop will continue
and this task will surrender time to the task using the FRAM,
effectively waiting for it to become available. When this task is
finished with the FRAM, it must set tfFRAMLock to False, making it
available to another task, like this:

do while not Semaphore(tfFRAMLock) 'loop if FRAM-busy
call Sleep(0) 'give time to using process
loop
call iiReadFRAMBin 'get FRAM data
iBinL = iBin 'move FRAM data to local vars
iBinAttrL = iBinAttr
tfFRAMLock = False 'release FRAM

The shared resource, in this case the FRAM, can be anything. The same
program, for example, shares COM1 so that fragments of displayed line
data from one task are not intermixed with fragments of displayed line
data from another concurrent task. Instead, each task can guarantee
that a complete line of data and it's CRLF are sent intact. One task
simply avoids sending to COM1 until its semaphore (tfCOM1Busy) indicates
that it's available.

If you multitask, master Semaphore(); it will open doors. Tom
Tom Becker
--... ...--
GTBecker@GTBe... www.RighTime.com
The RighTime Clock Company, Inc., Cape Coral, Florida USA
+1239 540 5700


Don, Tom, Thanks. Now I get it. Your explanations of the pcodes
and semaphores clears things up.

Usually, I have been using flags to control certain functions.

Writing to an LCD by several tasks sounds like a perfect use of a
semaphore. I had given up on trying to control access. I had even
gone so far as to read the XY position, then restore the XY
position, but I still had intermittent messes. I finally set flags
and then had a foreground idle-state (waiting for input) function do
ALL the writing. That worked well.

-Tony

--- In basicx@basi..., "Tom Becker" <gtbecker@r...> wrote:
> > ... the Semaphore() function.
>
> I'll take a stab at it.
>
> Semaphore() is a combination boolean test and boolean set function
of a
> special sort: if the boolean var argument value is False, Semaphore
()
> will make it True, and yield True; if the var argument value is
already
> True, Semaphore() will not change it, and will yield False.
Because the
> boolean var test and change cannot be interrupted, the function
permits
> guaranteeing exclusive rights to some resource (in essence,
> cooperatively making the resource atomic).
>
> An example:
>
> I use FRAM SPI memory. I write collected multibyte sample data to
the
> FRAM in one interrupt-driven task, scan and scale the FRAM data in
> another task, and display it from a third task. Since the SPI
interface
> and the FRAM require multibyte transfers of several sequential
commands,
> the interrupt-driven task cannot simply blast it's collected data
to the
> FRAM without risk of interfering with a process or display access
of the
> data; i.e. if the FRAM is busy, the write will have to wait. As
soon as
> the FRAM is available, the write can proceed. Since only one task
may
> handle the FRAM at any given moment, I use a byte (tfFRAMLock,
which
> must be initialized False) to indicate FRAM-busy and test for it
like
> this:
>
> do while not Semaphore(tfFRAMLock) 'loop if FRAM-busy
> call Sleep(0) 'give time to using process
> loop
>
> tfFRAMLock will be False when Semaphore() executes if the FRAM is
> available; if that's so, tfFRAMLock will be set to True so the
do/loop
> will fail, allowing this task to use the FRAM. If the FRAM is in
use by
> another task, tfFRAMLock will already be True, the do/loop will
continue
> and this task will surrender time to the task using the FRAM,
> effectively waiting for it to become available. When this task is
> finished with the FRAM, it must set tfFRAMLock to False, making it
> available to another task, like this:
>
> do while not Semaphore(tfFRAMLock) 'loop if FRAM-busy
> call Sleep(0) 'give time to using process
> loop
> call iiReadFRAMBin 'get FRAM data
> iBinL = iBin 'move FRAM data to local vars
> iBinAttrL = iBinAttr
> tfFRAMLock = False 'release FRAM
>
> The shared resource, in this case the FRAM, can be anything. The
same
> program, for example, shares COM1 so that fragments of displayed
line
> data from one task are not intermixed with fragments of displayed
line
> data from another concurrent task. Instead, each task can
guarantee
> that a complete line of data and it's CRLF are sent intact. One
task
> simply avoids sending to COM1 until its semaphore (tfCOM1Busy)
indicates
> that it's available.
>
> If you multitask, master Semaphore(); it will open doors. > Tom >
> Tom Becker
> --... ...--
> GTBecker@R... www.RighTime.com
> The RighTime Clock Company, Inc., Cape Coral, Florida USA
> +1239 540 5700


--- In basicx@basi..., "arhodes19044" <spamiam@c...> wrote:
> Usually, I have been using flags to control certain functions.

That's exactly what a semaphore is for (hence the name). The
advantage is that the atomicity of the test and set operation of
Semaphore() guarantees that it works as you were hoping your flags
would. The use of ordinary variables as access control flags may
appear to resolve resource access problems but it is not guaranteed
to do so (again, because read-modify-write for ordinary variables is
not necessarily atomic).

For every resource you have that is being accessed by multiple tasks
always use Semaphore() to control the access.

Don't overlook the fact that sequences of code or entire subroutines
are also resources that you can control access to using Semaphore
(). This is often referred to as serialization when it applies to
code but it's really the same concept we've been discussing -
serialization of access to a resource.

Don


What exactly is the definition of "atomic" as it applies to microcontrollers?

Thad

Don Kinzer <dkinzer@dkin...> wrote:
... atomicity ... atomic ... ---------------------------------
Yahoo! Mail Mobile
Take Yahoo! Mail with you! Check email on your mobile phone.



--- In basicx@basi..., Thad Larson <highwayman_33402@y...>
wrote:
> What exactly is the definition of "atomic" as it applies to
microcontrollers?

In computer science the adjective "atomic" means indivisible in the
sense that, once begun, the operation is guaranteed to complete
without intervention.

http://en.wikipedia.org/wiki/Atomic_%28computer_science%29

With respect to task switching on the BX-24 I believe that
individual pcode instructions are atomic. This just means that a
task switch will only occur after one pcode instruction has finished
execution and just before the next begins. It is important to note
that this does not mean that a line of BasicX code is necessarily
atomic because some lines of BasicX code are compiled to multiple
pcode instructions. You can use Mike's disassembler to see what
pcode the compiler generates for your code (
http://home.austin.rr.com/perks/basicx/bxDism).

If the BX-24 virtual machine (the interpreter) is coded correctly a
pcode instruction that reads or writes multiple bytes should be
atomic with respect to interrupts. This can only be verified by
looking at the source or object code for the interpreter.


> ... What exactly is the definition of "atomic"...

The notion suggests exclusive execution or data access granularity.

If you liken a process to a molecule, the functions that comprise that
process might be atoms in that they cannot be further functionally
divided (at least to the lay person). A function that might be
interrupted by a timer, for instance, is not atomic since it's immediate
completion is not guaranteed; it might be postponed by a higher-priority
action.

As a simple example, consider an application that uses an interrupt
handler for some external asynchronous event. Since the main
application code can be interrupted it is not atomic; the interrupt
handler code, though, as long as interrupts remain disabled after it
receives control, is probably an atomic task since nothing will disturb
it through its completion.

The term atomic can be relative - a matter of perspective appearance -
applied at machine level (atomic processor operations, below) or higher
- like an interrupt handler or operating system housekeeping. The Linux
reference below describes cooperative multitasking, much like Basic-X,
which can make an entire task atomic for as long as necessary.

http://www.ibiblio.org/navigator-bin/navigator.cgi?Documentation/smp.tex
: "The Intel pentium processors have a wide variety of inbuilt
facilities for supporting multiprocessing, including [] a set of atomic
test and set, exchange and similar operations."

"Within the Linux kernel certain guarantees are made. No process running
[] will be pre-empted by another [] process unless it voluntarily
sleeps. This ensures that blocks of [] code are effectively atomic with
respect to other processes and greatly simplifies many operations."

Atomic can refer to data, too, if it is under exclusive control of some
task:
http://www.modula2.org/sb/websitearchive/env/index32.htm: "These
functions perform atomic operations in a multiprocessing environment.
This means that the processor executing these functions has exclusive
access to the data being operated on."

Semaphores can make data atomic. Tom
Tom Becker
--... ...--
GTBecker@GTBe... www.RighTime.com
The RighTime Clock Company, Inc., Cape Coral, Florida USA
+1239 540 5700