Monday, October 22, 2012



 

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;