• try to use 'bg' instead of 'kill -cont' in ash
  • if 'bg' doesn't work on non-child/non-job processes, 'trap' 'SIGSTOP' to run 'wait <wait_process>' and 'trap' 'SIGCONT' to 'kill -kill <wait_process>' in ash
  • test if we can run multiple processes and make them echo on the screen concurrently but in different region
  • Inter-process interactions: IPCs, shared resources
  • On machines with multiple processors/cores, several processes/threads may run concurrently!
  • Lock in the process being stopped/scheduled and unlock in the other processes? (using condition variables)
  • Send SIGSTOP to all other processes accessing the message queue instead of locking them to avoid deadlock issues? (use the 3rd field of /proc/<pid>/stat to make sure a process is really stopped)
  • Use message queue of size 1 byte to simulate inter-process condition variable is that possible?
  • Understand the meaning of 'export' (such as 'export PATH') shell built-in: may it be used to exchange data between processes instead of files?
#!/bin/ash

##  Inter-process interactions:
##      .    .
##      .    .
##      .    .

##  Subdivide each statement/function call until each division becomes an atomic
##  operation.

##  Adding Sleep() between each instruction can outstand race condtion issues?

##  !! There is interval between @a1@ and @a2@ such that other process would 
##  interfere the execution within the interval.
NewTmpDir()
{
    local Node

    while :; do
	Node=$RANDOM
	if [ ! -e /tmp/${Node} ]; then # @a1@
	    mkdir /tmp/${Node}  # @a2@
	    eval "${1}=/tmp/${Node}"
	    return
	fi
    done
}

##  s=a
##  s=\$$s
##  s=`eval "echo $s"`
##  s=\$$s
##  s=`eval "echo $s"`
##     .
##     .
##     .
DeRef()
{
    DeRefRes=\$$1
    DeRefRes=`eval "echo $DeRefRes"`
}

##  Atomically exchange contents of variables indicated by $1 and the spinlock.
SpinAtomicXchg()
{
    local Temp

    flock -x 8

    DeRef $1
    Temp="$DeRefRes"
    SpinLock_GetVar $1
    SpinLock_SetVar $Temp

    flock -u 8
}

##  Set the PID of the calling process to the variable named in the first argument.
GetPid()
{
    local Stat Path

    Path=`pwd`
    cd /proc/self
    Stat=`cat stat`
    cd "$Path"
    eval "${1}=${Stat%% *}"
}

ContinueNoLock()
{
    kill -CONT $1
}

StopNoLock()
{
    kill -STOP $1
}

Continue()
{
    SpinLock_Lock
    ContinueNoLock $1
    SpinLock_Unlock
}

Stop()
{
    SpinLock_Lock
    StopNoLock $1
    SpinLock_Unlock
}

IsStopped()
{
    local Stat

    Stat=`cat /proc/${1}/stat`
    Stat=${Stat#* * }
    Stat=${Stat%% *}
    test "$Stat" = 'T'
}

TempUnlockProc()
{
    while :; do
        if IsStopped $1; then
            break;
        fi
    done
    SpinLock_Unlock
}

GetMessage()
{
    SpinLock_Lock
    if MsgQu_IsEmpty ...; then
        TempUnlockProc $Pid &
        StopNoLock $Pid # Self stopping.
    fi
    
    ##  ...retrive one message and set to var indicated by $1...
    
}

##  $1:Pointer to MsgQu $2:Message $3:Target PID
SendMessage()
{
    local Wake

    SpinLock_Lock

    if MsgQu_IsFull "$1"; then
        TempUnlockProc ... &
        ##  Add self to wait queue?
        StopNoLock ... # Self stopping.
    fi

    MsgQu_IsEmpty "$1"
    Wake=$?
    echo "$2" > "${1}/`cat \"${1}/End\"`"
    echo $((`cat "${1}/End"` + 1)) > "${1}/End"
    [ $Wake -eq 0 ] && ContinueNoLock $3

    SpinLock_Unlock
}

##  {{ SpinLock
SpinLock_Init()
{
    if [ ! -e "$SpinLockVar" ]; then
        SpinLock_SetVar 0
    fi
    exec 8> "$GlobalLock"
}

SpinLock_SetVar()
{
    echo "$1" > "$SpinLockVar"
}

SpinLock_GetVar()
{
    eval $1=`cat "$SpinLockVar"`
}

##  We should shorten the lock time as we can, becasue processes waiting on a
##  spin lock are essentially doing busy waiting which consume CPU resource.
SpinLock_Lock()
{
    local Lock

    Lock=1
    while :; do
        SpinAtomicXchg Lock
        if [ $Lock = 0 ]; then
            break;
        fi
    done
}

SpinLock_Unlock()
{
    local Lock

    Lock=0

    ##  Atomic operation in case lock variable value alternation is not atomic. 
    ##  Unlike mutex, a spin lock may be locked in a process and unlocked in the
    ##  other process.
    SpinAtomicXchg Lock
}
##  }} SpinLock

##  {{ Proc
##  $1:'' $2:Pointer to sys $3:Varable to receive 'this' pointer
Proc_Init()
{
    local Pid

    GetPid Pid
    set -- "${2}/${Pid}" "$2" "$3" # Set positional parameters.
    mkdir "$1" # Self allocation.

    eval "${3}=${1}"

    mkdir "${1}/MsgQu"
    MsgQu_Init "${1}/MsgQu"
}

Proc_Xxx()
{
    local This MessageDir Message

    Proc_Init "$1" "$2" This
    set -- "$This" "$2" 

    ## ...

    while GetMessage Message; do
        ProcessMessage $Message
    done

    ## ...
}
##  }} Proc

##  {{ MsgQu
MsgQu_Init()
{
    echo 0 > "${1}/Begin"
    echo 0 > "${1}/End"
}

MsgQu_IsEmpty()
{
    [ "`cat \"${1}/Begin\"`" = "`cat \"${1}/End\"`" ]
}

MsgQu_IsFull()
{
    ...
}
##  }} MsgQu

##  {{ Sys
Sys_Init()
{
    Proc_Init ...
}
##  }} Sys

NewTmpDir Root
Sys_Init "$Root"



GlobalLock="${Root}/Global.lock"
SpinLockVar="${Root}/SpinLock.var" # Locked:1 unlocked:0
This="${Root}/${Pid}"
MessageQueue="${This}/MessageQueue"

SpinLock_Init
Proc_Init
MsgQu_Init

##  ...
#!/bin/ash

##  Inter-process interactions:

##  Subdivide each statement/function call until each division becomes an atomic operation

##  Adding Sleep() between each instruction can outstand race condtion issues?

##  s=a
##  s=\$$s
##  s=`eval "echo $s"`
##  s=\$$s
##  s=`eval "echo $s"`
##     .
##     .
##     .
DeRef()
{
    DeRefRes=\$$1
    DeRefRes=`eval "echo $DeRefRes"`
}

SpinSetLockVar()
{
    echo "$1" > "$SpinLockVar"
}

SpinGetLockVar()
{
    eval $1=`cat "$SpinLockVar"`
}

SpinInit()
{
    if [ ! -e "$SpinLockVar" ]; then
        SpinSetLockVar 0
    fi
    exec 8> "$GlobalLock"
}

##  Atomically exchange contents of variables indicated by $1 and the spinlock.
SpinAtomicXchg()
{
    local Temp

    flock -x 8

    DeRef $1
    Temp="$DeRefRes"
    SpinGetLockVar $1
    SpinSetLockVar $Temp

    flock -u 8
}

##  We should shorten the lock time as we can, becasue processes waiting on a spin lock are essentially doing busy waiting which consume CPU resource.
SpinLock()
{
    local Lock

    Lock=1
    while :; do
        SpinAtomicXchg Lock
        if [ $Lock = 0 ]; then
            break;
        fi
    done
}

SpinUnlock()
{
    local Lock

    Lock=0
    SpinAtomicXchg Lock # Atomic operation in case lock variable value alternation is not atomic. Unlike mutex, a spin lock may be locked in a process and unlocked in the other process.
}

##  Set the PID of the calling process to the variable named in the first argument.
GetPid()
{
    local Stat Path

    Path=`pwd`
    cd /proc/self
    Stat=`cat stat`
    cd "$Path"
    eval "$1=${Stat%% *}"
}

ContinueNoLock()
{
    kill -CONT $1
}

StopNoLock()
{
    kill -STOP $1
}

Continue()
{
    SpinLock
    ContinueNoLock $1
    SpinUnlock
}

Stop()
{
    SpinLock
    StopNoLock $1
    SpinUnlock
}

IsStopped()
{
    local Stat

    Stat=`cat /proc/${1}/stat`
    Stat=${Stat#* * }
    Stat=${Stat%% *}
    test "$Stat" = 'T'
}

TempUnlockProcess()
{
    while :; do
        if IsStopped $1; then
            break;
        fi
    done
    SpinUnlock
}

GetMessage()
{
    SpinLock
    if IsDirEmpty "$MessageDir"; then
        TempUnlockProcess $Pid &
        StopNoLock $Pid # Self stopping.
    fi
    ##  ...retrive one message and set to var indicated by $1...
}

##SendMessage()
##{
##}

TaskInit()
{
    local Pid

    GetPid Pid
    mkdir "$This" > /dev/null 2>&1
}

MessageQueueInit()
{
    mkdir "$MessageQueue" > /dev/null 2>&1
    echo 0 > "${MessageQueue}/Begin.var"
    echo 0 > "${MessageQueue}/End.var"
}

TaskXxx()
{
    local Pid MessageDir Message

    GetPid Pid
    MessageDir="${Root}/${Pid}"

    ## ...

    while GetMessage Message; do
        ProcessMessage $Message
    done

    ## ...
}

local Root GlobalLock SpinLockVar

Root=/tmp/mtbv
GlobalLock="${Root}/Global.lock"
SpinLockVar="${Root}/SpinLock.var" # Locked:1 unlocked:0
This="${Root}/${Pid}"
MessageQueue="${This}/MessageQueue"

SpinInit
TaskInit
MessageQueueInit

##  ...
#!/bin/ash
exec 8> /tmp/mtbv/aaa # In shell 1
flock -x 8 # In shell 1
(flock -x 8;) 8> /tmp/mtbv/aaa # In shell 2, and shell 2 is blocked
flock -u 8 # In shell 1, and shell 2 is unblocked
#!/bin/ash
exec 8> /tmp/mtbv/aaa # In shell 1
flock -x 8 # In shell 1
(flock -x 8;) 8> /tmp/mtbv/aaa # In shell 2, and shell 2 is blocked
exec 8> /dev/null # In shell 1, and shell 2 is unblocked
#!/bin/ash

##  Adding Sleep() between each instruction can outstand race condtion issues?

##  s=a
##  s=\$$s
##  s=`eval "echo $s"`
##  s=\$$s
##  s=`eval "echo $s"`
##     .
##     .
##     .
DeRef()
{
    DeRefRes=\$$1
    DeRefRes=`eval "echo $DeRefRes"`
}

InitAtomicXchg()
{
    exec 8> "$GlobalLock"
}

##  Atomically exchange contents of variables indicated by $1 and $2.
AtomicXchg()
{
    flock -x 8

    DeRef $1
    Temp="$DeRefRes"
    DeRef $2
    eval "$1=$DeRefRes"
    eval "$2=$Temp"

    flock -u 8
}

##  We should shorten the lock time as we can, becasue processes waiting on a spin lock are essentially doing busy waiting which consume CPU resource.
SpinLock()
{
    local Lock

    Lock=1
    while :; do
        AtomicXchg Lock $1
        if [ $Lock = 0 ]; then
            break;
        fi
    done
}

SpinUnlock()
{
    local Lock

    Lock=0
    AtomicXchg Lock $1 # Atomic operation in case lock variable value alternation is not atomic. Unlike mutex, a spin lock may be locked in a process and unlocked in the other process.
}

##  Set the PID of the calling process to the variable named in the first argument.
GetPid()
{
    local Stat Path

    Path=`pwd`
    cd /proc/self
    Stat=`cat stat`
    cd "$Path"
    eval "$1=${Stat%% *}"
}

ContinueNoLock()
{
    kill -CONT $1
}

StopNoLock()
{
    kill -STOP $1
}

Continue()
{
    SpinLock SpinLockVar
    ContinueNoLock $1
    SpinUnlock SpinLockVar
}

Stop()
{
    SpinLock SpinLockVar
    StopNoLock $1
    SpinUnlock SpinLockVar
}

IsStopped()
{
    local Stat

    Stat=`cat /proc/${1}/stat`
    Stat=${Stat#* * }
    Stat=${Stat%% *}
    test "$Stat" = 'T'
}

TempUnlockProcess()
{
    while :; do
        if IsStopped $1; then
            break;
        fi
    done
    SpinUnlock SpinLockVar
}

GetMessage()
{
    SpinLock SpinLockVar
    if IsDirEmpty "$MessageDir"; then
        TempUnlockProcess $Pid &
        StopNoLock $Pid # Self stopping.
    fi
    ##  ...retrive one message and set to var indicated by $1...
}

TaskXxx()
{
    local Pid MessageDir Message

    GetPid Pid
    MessageDir="${Root}/${Pid}"

    ## ...

    while GetMessage Message; do
        ProcessMessage $Message
    done

    ## ...
}

local Root GlobalLock SpinLockVar

Root=/tmp/mtbv
GlobalLock="${Root}/global.lock"
SpinLockVar=0 # Locked:1 unlocked:0

InitAtomicXchg

##  ...
#!/bin/ash


##  Adding Sleep() between each instruction can outstand race condtion issues?


##  Set the PID of the calling process to the variable named in the first argument.
GetPid()
{
    local Stat Path

    Path=`pwd`
    cd /proc/self
    Stat=`cat stat`
    cd "$Path"
    eval "$1=${Stat%% *}"
}

ContinueNoLock()
{
    kill -CONT $1
}

StopNoLock()
{
    kill -STOP $1
}

Continue()
{
    (
        flock -x 8
        ContinueNoLock $1
        ) 8> $GlobalLock
}

Stop()
{
    (
        flock -x 8
        StopNoLock $1
        ) 8> $GlobalLock
}

IsStopped()
{
    local Stat
    
    Stat=`cat /proc/${1}/stat`
    Stat=${Stat#* * }
    Stat=${Stat%% *}
    test "$Stat" = 'T'
}

PostUnlock()
{
    while :; do
	if IsStopped $1; then
	    break;
	fi
    done
    Unlock $GlobalLock
}

GetMessage()
{
    Lock $GlobalLock
    if IsDirEmpty "$MessageDir"; then
        PostUnlock $Pid &
        StopNoLock $Pid # Self stopping.
    fi
    ##  ...retrive one message and set to var indicated by $1...
}

TaskXxx()
{
    local Pid MessageDir Message

    GetPid Pid
    MessageDir="${Root}/${Pid}"

    ## ...

    while GetMessage Message; do
	ProcessMessage $Message
    done

    ## ...
}

local Root

Root=/tmp/mtbv
GlobalLock="${Root}/global.lock"
#!/bin/ash

##  Set the PID of the calling process to variable named in the first argument.
m()
{
    local s p
    p=`pwd`
    cd /proc/self
    s=`cat stat`
    cd "$p"
    eval "$1=${s%% *}"
}

##  Self stopping test function.
p()
{
    local i
    echo ...beg
    m i
    echo $i
    kill -stop $i
    echo ...end
}
#!/bin/bash

w()
{
    echo '...beg w...'
    while :; do sleep 60; done
    echo '...end w...'
}

c() 
{
    echo '...beg c...'
    echo '...end c...'
}

s()
{
    echo '...beg s...'
    wait $ttt # 'wait' will return (similar to WAIT(2) returns 'EINTR') when interrupted by signal like USR1 or c()?
    echo '...end s...'
}

p()
{
    echo '...beg p...'

    w &
    ttt=$!

    trap c USR1 # Default handler terminates the program, so we make a dummy handler (do nothing) to avoid the termination
    trap s USR2

    while :; do
	echo -n '^'
	sleep 0.05
    done

    kill $ttt

    echo '...end p...'
}

p & echo $!

##  Use 'kill -USR2 $!' to stop/suspend p() and use 'kill -USR1 $!' to continue/resume p()
#!/bin/ash

##  Make text positioning and text printing done by a single 'echo' call, which
##  should reduce/eliminate the race condition when this function is called
##  within multiple processes.
##
##  This function doesn't add newline at the end of the output.
##
##  Notice that 'echo' don't accept '-e' option in 'sh' and 'ash'.
echo_at()
{
    local str
    str="\033[${1}H"
    if [ "$2" -gt 1 ]; then
        str="${str}\033[$(($2 - 1))C${3}"
    else
        str="${str}${3}"
    fi
    echo -n "$str"
}
##  In one of terminal window
(flock -x 8; sleep 200;) 8> /tmp/kkk

##  In the other terminal window
(flock -x 7; echo ---why---;) 7> /tmp/kkk
#/bin/bash

g()
{
    while :; do
	echo -e "\033[${1}H"
	sleep `echo "0.$RANDOM"`
    done
}

echo_at()
{
    echo -e "\033[${1}H"
    sleep .1
    if [ $2 -gt 1 ]; then
        echo -e "\033[$(($2 - 1))C${3}"
    else
        echo -e "$3"
    fi
}

p()
{
    while :; do
	echo_at "$1" "$2" "    <$RANDOM>    "
	sleep `echo "0.$RANDOM"`
    done
}

p 23 60 & g 1 & g 2 & g 3 & g 4 & g 5 & g 6 & g 7 & g 8 & g 9 & g 10 & g 11 & g 12 &
#/bin/bash

echo_at()
{
    echo -e "\033[${1}H"
    if [ $2 -gt 1 ]; then
        echo -e "\033[$(($2 - 1))C${3}"
    else
        echo -e "$3"
    fi
}

p()
{
    while :; do
	echo_at "$1" "$2" "    <$RANDOM>    "
	sleep `echo "0.$RANDOM"`
    done
}

p()
{
    iii=1
    while :; do
	echo_at "$1" "$2" "    <$iii>    "
	iii=$(($iii + 1))
	sleep `echo "0.$RANDOM"`
    done
}

##  (progn
##    (setq i 1)
##    (while (<= i 21)
##      (insert (format "p %d 60 & " i))
##      (setq i (1+ i))
##      )
##    )

p 1 60 & p 2 60 & p 3 60 & p 4 60 & p 5 60 & p 6 60 & p 7 60 & p 8 60 & p 9 60 & p 10 60 & p 11 60 & p 12 60 & p 13 60 & p 14 60 & p 15 60 & p 16 60 & p 17 60 & p 18 60 & p 19 60 & p 20 60 & p 21 60 & 

##ttt=$(echo '$ttt $!')
#!/bin/ash

w()
{
    echo '...beg w...'
    while :; do sleep 60; done
    echo '...end w...'
}

c()
{
    echo '...beg c...'
    echo $ttt
    kill $ttt
    echo '...end c...'
}

s()
{
    echo '...beg s...'
    w &
    ttt=$!
    echo $ttt
    wait $ttt
    echo '...end s...'
}

p()
{
    echo '...beg p...'

    trap c USR1
    trap s USR2

    while :; do
	echo '^'
	sleep 1
    done

    echo '...end p...'
}

#####

w()
{
    echo '...beg w...'
    while :; do sleep 60; done
    echo '...end w...'
}

c()
{
    echo '...beg c...'
    echo $ttt
    kill $ttt
    echo '...end c...'
}

s()
{
    echo '...beg s...'
    w &
    ttt=$!
    echo $ttt
    wait $ttt
    echo '...end s...'
}

p()
{
    echo '...beg p...'

    trap c USR1
    trap s USR2

    w &
    while :; do
	wait $!
    done

    echo '...end p...'
}

#####

c()
{
    echo '..beg c...'
    echo $ttt
    echo '..end c...'
}

w()
{
    echo '..beg w...'
    while :; do sleep 60; done
    echo '..end w...'
}

p()
{
    echo '..beg p...'

    w &
    ttt=$!
    echo "ttt=$ttt"
    trap c CONT

    w &
    while :; do
	wait $!
    done

    echo '..end p...'
}

#####

e()
{
    echo '..beg e...'
    echo '...trap...'
    echo '..end e...'
}

w()
{
    echo '..beg w...'
    ping localhost > /dev/null
    echo '..end w...'
}

p()
{
    echo '..beg p...'
    trap e CONT
    w &
    while :; do
	wait $!
    done
    echo '..end p...'
}

#####

w()
{
    ping localhost > /dev/null
}

p()
{
    trap e CONT
    w
}

p()
{
    trap '-' CONT
    w
}

#####

w()
{
    while :; do
	sleep 1
    done
}

e()
{
    echo '...trap...'
}

p()
{
    trap e USR1
    w
}