Overview:
In the
Windows based version of my Exploratory Project (EP) I create the threads in
which each of the components is to execute and then start these threads after
all the threads have been created.
Linux seems
to start running a thread as it becomes created. Therefore, in order to be able to retain the wait until ready so
as to be able to keep the basic code (versus the executive interface)
unchanged, I needed a method to have the Linux created threads wait to execute
until the Thread_Start procedure was invoked.
(It may be that Windows
does the same since, most likely, I didn't check whether the Windows process waited
until the start was done.)
This post
will illustrate a way to accomplish this.
Design:
The
exploratory project contains an Exec Ada package hidden inside of the general
mC message controller framework package to contain special interface procedures
that can be dependent upon the operating system.
Two of this
packages procedures are Thread_Create and Thread_Start. These procedures are invoked from another
private mC package called Scheduler that determines when particular application
components are to run. One thing
Scheduler does is to create threads in which to run the components. At the end of application initialization, it
also invokes Thread_Start to cause initiate the execution of the code of the
various components in their threads.
Therefore,
it was a simple matter to create a new reentrant procedure within the Exec
package to be the initial code that Thread_Create specifies as the start
address of the thread passing it the Thread Control Block index of the thread
as a parameter. The entry code of this
new procedure then saves the passed index on the stack (so as to be reentrant)
and loops forever.
Within this
loop is a check as to whether Thread_Start has signaled that the actual code of
the thread is to be executed and, if the signal has not yet been set, a delay
to allow other threads to execute. Thus
each thread create causes this dummy thread procedure to execute within the
created thread and store the calling parameter value on a new stack frame
instance.
Thread_Start
can be called to start all the threads or a particular one. If a particular one, it sets a start boolean
in the Thread Control Block to indicate that the indicated thread is to be
started. If all the threads are to be
started, it sets a common static boolean.
Each time a
thread resumes after its delay has completed, it checks both its Thread Control
Block boolean and the common one to see if either indicate that the actual
component code to be executed within the thread is to be started. If so, then it exits the loop and invokes
the actual code of the thread at the address that the Thread_Create saved in
the Control Block. Since the thread is
now executing, it is expected that there will be no return to the instance of
the wait procedure.
Implementation:
The Thread Control Block (TCB) and the
Thread_Create procedure were modified to be able have an individual start
boolean and start address for each created thread and to invoke the Linux
version of the create thread code by passing a parameter containing the TCB
index and specifying the address of the new wait to start procedure.
This procedure (Thread_Wait_to_Start) was
added and Thread_Start was modified to set either the common
Thread_Start_Signaled boolean or the particular TDB Start_Signaled boolean.
The modified and added code is below. Some of the illustrated code is unchanged
but included since referenced by new or modified code.
exec_mc.ads:
. . .
type Thread_Id_Ext_Type
--| Framework thread identification
is new Integer range 0 ..
Max_Number_Of_Threads + 1;
subtype Thread_Id_Type
--| Framework thread identification type,
excluding Main and error handler
--| threads
is Thread_Id_Ext_Type range 1 ..
Max_Number_Of_Threads;
. . .
type Thread_Attributes_Type
--| Attributes that thread is to have
is record
Name : Thread_Name_Type;
--| Alphanumeric name of thread
-- Key : mC.Itf_Types.Participant_Key_Type;
--| Component key
Entry_Point : Thread_Address_Type;
--| Code location of beginning of thread
main procedure
Stack_Size : Stack_Size_Type;
--| Stack space to be allocated to process
Base_Priority : Thread_Priority_Type;
--| Normal priority for the process
Period : Time_Interval_Type;
--| Aperiodic or time interval for op
system type of Periodic thread
Deadline : Deadline_Type;
--| Op system type of deadline
end record;
. . .
procedure Thread_Create
( Thread_Name : in Thread_Name_Type;
--| Assigned thread name
Start : in Thread_Address_Type;
--| Pointer to start address of thread
Parameter : in System.Address;
--| Pointer to parameters
Stack_Size : in Natural;
--| Stack size in bytes
Thread_Priority : in Thread_Priority_Type;
--| Priority for thread
Thread_Id : out Thread_Id_Type;
--| Identifier of created thread
Status : out Status_Type
--| Returned status
);
--| Create Thread
-- ++
--| Overview:
--|
Create thread.
--| Notes:
--|
For Linux, the thread will start when created but in a
--|
temporary procedure. The use of
Thread_Start will cause
--|
the actual thread procedure to be started.
-- --
. . .
procedure Thread_Start
( Thread_Id : in Thread_Id_Ext_Type;
--| Identifier of thread to be started
Status
: out Status_Type
--| Returned status
);
--| Start Thread or Threads
-- ++
--| Overview:
--|
Indicate particular thread to be started or, if Thread_Id is 0,
--|
indicate that all threads to be started.
-- --
. . .
exec_mc.adb:
. . .
type Thread_Control_Block_Type
--| Thread Control Block
is record
FW_T_Id : Thread_Id_Ext_Type;
--| Framework thread identifier
Thread_Id : Exec_Itf.Thread_Id_Type;
--| System (i.e., Linux) thread identifier
of the thread
Thread_Handle : Exec_Itf.Thread_Handle_Type;
--| GNAT thread handle of the thread
Thread_Address : System.Address;
--| Thread entry point
Thread_Parameter : System.Address;
--| Pointer to pass to actual Linux thread
Status : Thread_Status_Type;
--| Thread status information
Start_Signaled : Boolean;
--| True indicates that Thread_Start
signaled for the thread
Raised : Boolean;
--| Flag indicates priority level has been
raised to System level
Created : Boolean;
--| Flag indicates that thread has been
created
Started : Boolean;
--| Flag indicates that thread has been
started
Suspended : Boolean;
--| Flag indicates that thread has been
suspended
Waiting : Waiting_Conditions_Type;
--| Thread waiting condition
end record;
type TCB_Table_Type
--| Thread control block table type
is array (Thread_Id_Ext_Type) of
Thread_Control_Block_Type;
Null_Thread_Name
--| Null thread name
: constant Thread_Name_Type
:= ( others => ' ' );
Null_Thread_Attributes
--| Null thread attributes values
: constant Thread_Attributes_Type
:= ( Name => Null_Thread_Name,
--
Key => ( others
=> 0 ),
Entry_Point => System.Null_Address,
Stack_Size => 0,
Base_Priority =>
Thread_Priority_Type'first,
Period => Aperiodic,
Deadline => Soft_Deadline );
Null_TCB
--| Null values for the thread control block
: constant Thread_Control_Block_Type
:= ( FW_T_Id => 0,
Thread_Id => 0,
Thread_Handle => Exec_Itf.Null_Thread_Handle,
Thread_Address => System.Null_Address,
Thread_Parameter =>
System.Null_Address,
Status => Null_Thread_Status,
Start_Signaled => False,
Raised => False,
Created => False,
Started => False,
Suspended => False,
Waiting => No_Condition );
TCB
--| Thread Control Block
: TCB_Table_Type := ( others => Null_TCB
);
type Thread_Index_Record_Type
--| Parameter to pass to dummy thread to
wait until thread start time
is record
Index : Thread_Id_Ext_Type;
--| Index into TCB of thread
end record;
type Thread_Index_Parameter_Ptr_Type
is access Thread_Index_Record_Type;
for
Thread_Index_Parameter_Ptr_Type'storage_size use 0;
procedure Thread_Wait_to_Start
( Parameters : in
Thread_Index_Parameter_Ptr_Type
);
-- ++
--| Overview:
--|
Loop until Thread_Start is invoked.
Then execute the code at
--|
Thread_Address.
--| Notes:
--|
This must be a re-entrant procedure since it will be invoked by
--|
Thread_Create for each thread that is created to await the common
--|
thread start. Therefore,
Thread_Create passes the TCB index as
--|
the parameter when creating the thread.
That will cause this
--|
procedure to start and it will save the index as its stack variable
--|
so each thread will be saving its particular value.
-- --
. . .
procedure Thread_Create
( Thread_Name : in Thread_Name_Type;
Start : in Thread_Address_Type;
Parameter : in System.Address;
Stack_Size : in Natural;
Thread_Priority : in Thread_Priority_Type;
Thread_Id : out Thread_Id_Type;
Status : out Status_Type
) is
Arg
--| Start arguments
: String(1..100) := ( others =>
ASCII.NUL );
Attributes
: Exec_Itf.pthread_attr_t;
type Attributes_Ptr_Type is access
Exec_Itf.pthread_attr_t;
function to_Attr_Ptr is new
Unchecked_Conversion
( Source =>
System.Address,
Target => Attributes_Ptr_Type );
Attributes_Ptr : Attributes_Ptr_Type :=
to_Attr_Ptr(Attributes'address);
Result
: Interfaces.C.int;
T_Id
--| Thread identifier
: Thread_Id_Ext_Type;
Thread_Created
--|
True if thread was created successfully
: Boolean;
Thread_Handle
--| Handle returned by CreateThread
: Exec_Itf.Thread_Handle_Type;
TId
--| Thread identifier
: Exec_Itf.Thread_Id_Type;
pragma Volatile( TId );
use type Exec_Itf.Thread_Handle_Type;
use type Exec_Itf.Thread_Id_Type;
use type Interfaces.C.int;
use type System.Address;
begin -- Thread_Create
--| Logic_Step:
--|
Lock the thread semaphore to prevent another thread from
--|
accessing the critical region.
--| Note:
--|
The created thread can starting running immediately suspending
--|
the thread that invoked this Create and preventing it from
--|
updating the TCB. Therefore, the
other thread can attempt other
--|
Exec-mC procedures that would access the incomplete TCB.
Thread_Lock;
T_Id := 0;
for I in Thread_Id_Type loop
if not TCB(I).Created or else
TCB(I).Status.Attributes.Name =
Thread_Name
then
T_Id := I;
exit;
end if;
end loop;
if T_Id = 0 or else
Thread_Name = Null_Thread_Name or else
Start = Null_Thread_Address
then
Status := Invalid_Config;
elsif TCB(T_Id).Created then
Status := No_Action;
else
--| Logic_Step:
--|
Buffer thread TCB index to be saved by Thread_Wait_for_Start that
--|
is used as the start address for the thread for it to save on its
--|
stack. Also save the actual
start address and the pointer to any
--|
parameters for the thread to be used by Thread_Wait_for_Start to
--|
invoke the actual thread code when Thread_Start is invoked.
TCB(T_Id).FW_T_Id := T_Id;
TCB(T_Id).Thread_Address := Start;
TCB(T_Id).Thread_Parameter := Parameter;
Result := Exec_Itf.pthread_attr_init(
Attributes => Attributes_Ptr );
if Stack_Size > 0 then
Result :=
Exec_Itf.pthread_attr_setstacksize( Attributes_Ptr,
Stack_Size );
Exec_Itf.Log_Error;
end if;
Result := Exec_Itf.Thread_Create
( Thread_Id => TId'Address, -- location for thread id
Attributes =>
Attributes'Address,
Start => Thread_Wait_to_Start'address,
Arg => TCB(T_Id).FW_T_Id'address ); --
parameters
Note:
Created thread could start running before the return to the next
statement. However, it will soon enter
a delay that will allow the process/thread that called this procedure to resume
and execute the next statement.
Thread_Created := Result = 0;
if Thread_Created then
TCB(T_Id).Thread_Id := TId;
TCB(T_Id).Thread_Handle := Thread_Handle;
TCB(T_Id).Status.Current_Priority :=
0;
TCB(T_Id).Status.Deadline_Time := Aperiodic;
TCB(T_Id).Status.Thread_State := Dormant;
TCB(T_Id).Created := True;
Thread_Names_First_Letters(T_Id) := Thread_Name(1);
Thread_Id := T_Id;
Status := No_Error;
else -- Exec_Itf error
TCB(T_Id).FW_T_Id := 0;
TCB(T_Id).Thread_Id := 0;
TCB(T_Id).Thread_Handle := Exec_Itf.Null_Thread_Handle;
TCB(T_Id).Created := False;
Thread_Id := 1;
Status := Invalid_Config;
end if;
end if;
--| Logic_Step:
--|
Unlock the thread semaphore to allow another thread to
--|
access the critical region.
Thread_Unlock;
end Thread_Create;
. . .
procedure Thread_Start
( Thread_Id : in Thread_Id_Ext_Type;
Status
: out Status_Type
) is
-- ++
--| Logic_Flow:
--|
Signal that Thread_Start has been invoked.
-- --
begin -- Thread_Start
if Thread_Id = 0 then
Thread_Start_Signaled := True;
else
TCB(Thread_Id).Start_Signaled := True;
end if;
end Thread_Start;
. . .
type Invoke_Thread
is access procedure
--| Method to invoke thread at its start
address
( Parameters : in System.Address
--| Pointer to parameters to pass to
thread's main procedure
);
procedure Thread_Wait_to_Start
( Parameters : in
Thread_Index_Parameter_Ptr_Type
) is
-- ++
--| Logic_Flow:
--| Save the thread TCB index passed via the parameter pointer and
then
--|
loop until Thread_Start is invoked.
Then execute the code at the
--|
Thread_Address stored in TCB entry as the index.
--| Notes:
--|
This must be a re-entrant procedure since it will be invoked by
--|
Thread_Create for each thread that is created to await the common
--|
thread start. Therefore,
Thread_Create stores the TCB index in a
--|
static location before creating the thread. That will cause this
--|
procedure to start and it will save the index as its stack variable
--|
so each thread will be saving its particular value.
-- --
Index
--| Index into TCB array for instance of
this re-entrant procedure
: Thread_Id_Ext_Type;
begin -- Thread_Wait_to_Start
--| Logic_Step:
--|
Save buffered Thread_Index on the stack as Linux starts the thread
--|
being created by Thread_Create.
Index := Parameters.Index;
--| Logic_Step:
--|
Loop until Thread_Start is invoked.
--| Notes:
--|
Each delay also allows other instances of this re-entrant procedure
--|
to run so that each can detect that Thread_Start has occurred.
Forever:
loop
exit Forever when
TCB(Index).Start_Signaled or else Thread_Start_Signaled;
delay 0.2;
end loop Forever;
--| Logic_Step:
--|
Invoke the actual procedure for the thread of the index.
--| Notes:
--|
The procedure should never return.
declare
function Addr_to_Access is new
Unchecked_Conversion
( Source
=> System.Address,
Target
=> Invoke_Thread );
Run_Thread
: Invoke_Thread
:= Addr_to_Access(
TCB(Index).Thread_Address );
begin
TCB(Index).Started := True;
Run_Thread( Parameters =>
TCB(Index).Thread_Parameter );
raise Program_Error;
end;
end Thread_Wait_to_Start;