Monday, September 3, 2012

Using Threads in Linux




Overview


In creating my Thread Create interface for use with Linux I found a GNAT Threads package that had such a procedure so I used it. Then, while preparing to implement the Exploratory Project's Thread Lock and Unlock procedures for use with Linux I had the need to implement its My_Thread_Id procedure to obtain the identifier of the currently running thread. For this I found a Linux C function call to pthread_self to which I gave the name, within the project, of GetCurrentThreadId.


Then arose the problem since the thread identifier returned by the GNAT Threads Create_Thread turned out to be different from that returned by pthread_self when the created thread was running. Therefore, I wouldn't be able to determine which thread was which via the tables maintained by the Exploratory Project.

After some further investigation I found a Linux C function that could be used to create the thread – pthread_create – that did return the same thread identifier as returned by pthread_self.

And additional investigation resulted in my finding a GNAT Threads procedure that can be used to convert a GNAT thread identifier into the corresponding Linux thread identifier. This procedure is GNAT Threads Get_Thread.


Therefore, when working with GNAT Ada for Linux be careful to avoid becoming confused between the GNAT thread id and the Linux thread id.


Solution


For my Exploratory Project and its modifications to run using the Linux operating system I have declared two different thread identifiers and used Ada's strong typing to force the use of the correct procedure.

In my Exec_Itf operating system interface package I have declared the following two types.


type Thread_Handle_Type is private;


type Thread_Id_Type is new Interfaces.C.unsigned_long;


where in the private part of the Exec_Itf package specification


type Thread_Handle_Type is new Interfaces.C.unsigned_long;


so both types are really unsigned long integers and match what the types ultimately are when the typing is followed back to the original types.


I have used the Thread_Handle_Type for references to GNAT supplied packages and the Thread_Id_Type when using Ada pragma Import for Linux C functions. This will prevent future confusion to avoid calling a Linux function with a GNAT thread identifier and vice versa.


exec_mc Thread Interface


This is the Ada package used by the framework (in the exploratory project) to supply an interface that can allow the framework code to be independent of the operating system being used. The portion of it dealing with Events was presented in the previous post. This post will present the portion dealing with process Threads that has currently been implemented.


package Exec_mC is -- test version of mC-Exec
--| OS/Executive Specific Interface
-- ++
--| Overview:


. . . etc as presented before


-------------------------------------------------------------------------
--|Notes: |
--| Thread types, constants, and functions. |
-------------------------------------------------------------------------


Max_Number_Of_Threads
: constant := 25;


subtype Thread_Address_Type
--|System address of a thread
is System.Address;


Null_Thread_Address
--|Null system address for thread
: constant Thread_Address_Type;


type Thread_Id_Ext_Type
--|Framework thread identification
is 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;


Null_Thread_Id
: constant Thread_Id_Type := Max_Number_Of_Threads;


type Thread_Name_Type
--|Alphanumeric name of process thread
is new String( 1..Max_Name_Length );


Min_Thread_Value
: constant := 0;


what values to use for these with Linux?


Max_Thread_Value
: constant := 15;


type Thread_Priority_Type
--|Priority of system thread
is new Interface_Integer range Min_Thread_Value .. Max_Thread_Value;
for Thread_Priority_Type'size use Integer'size;


type Deadline_Type
is ( Soft_Deadline,
     Hard_Deadline );


type Component_Category_Type
--| 0 = Time Critical, 1 = Executive, 2 = User
--|Notes:
--| See mC-Itf_Types Component_Category_Type. The Integer values MUST be
--| the same as mC-Itf_Types.Component_Category_Type'pos.
is new Integer range 0..2;


type Stack_Size_Type
is new Interfaces.C.Unsigned_Long;


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;


type Thread_State_Type
--|Thread run state
is ( Dormant,
     Ready,
     Running,
     Waiting );


. . .


procedure My_Thread_Id
( Thread_Id : out Thread_Id_Ext_Type;
  --|Identifier of current thread
  Status : out Status_Type
  --|Returned status
);
--|Return FW identifier of currently running thread
-- ++
--|Overview:
--| Return framework thread id of current thread if not main thread.
-- --


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 Exec_Itf.Void_Ptr;
  --|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.
-- --


procedure Thread_Destroy
( Thread_Id : Thread_Id_Type
  --|Identifier of thread to destroy
);
--|Destroy Thread
-- ++
--|Overview:
--| This procedure may be used to prematurely abort the created thread.
--| Thread_Id is the value that was return by Thread_Create.


. . .


-----------------------------------------------------------------------------


private


--type Thread_Address_Type
--|System address of a thread
-- is new System.Address;


Null_Thread_Address
--|Null system address for thread
: constant Thread_Address_Type := Thread_Address_Type(System.Null_Address);


end Exec_mC;


The package body:


with Ada.Task_Identification;
with Console;
with Interfaces.C;
with Numeric_Conversion;


package body Exec_mC is
-- ++
--| Notes:
--| This package body is for use with Linux.
-- --


. . .


---------------------------------------------------------------------------
--|Notes:
--| System thread static data and interface procedures.


type Thread_Status_Type
is record
  Attributes : Thread_Attributes_Type;
  Current_Priority : Thread_Priority_Type;
  Deadline_Time : Time_Interval_Type;
  Thread_State : Thread_State_Type;
end record;


type Waiting_Conditions_Type
--|Waiting condition type
is ( No_Condition,
     For_Resume,
     On_Delay,
     On_Period,
     On_Event,
     On_Semaphore,
     On_Buffer,
     On_Blackboard
   );


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
  Status : Thread_Status_Type;
  --|Thread status information
  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,
     Entry_Point => System.Null_Address,
     Stack_Size => 0,
     Base_Priority => Thread_Priority_Type'first,
     Period => Aperiodic,
     Deadline => Soft_Deadline );


Null_Thread_Status
--|Null thread status values
: constant Thread_Status_Type
:= ( Attributes => Null_Thread_Attributes,
     Current_Priority => Thread_Priority_Type'first,
     Deadline_Time => Infinite_Time,
     Thread_State => Dormant );


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,
     Status => Null_Thread_Status,
     Raised => False,
     Created => False,
     Started => False,
     Suspended => False,
     Waiting => No_Condition );


TCB
--|Thread Control Block
: TCB_Table_Type := ( others => Null_TCB );


type Thread_Names_First_Letters_Type
--|Table of first letter of each thread name
is array (Thread_Id_Type) of Character;


Thread_Names_First_Letters
--|First letters of the thread names
: Thread_Names_First_Letters_Type;


Main_Thread_Id
--|Special thread identifier for Main thread
: constant Thread_Id_Ext_Type := 0;


. . .


---------------------------------------------------------------------------


procedure My_Thread_Id
( Thread_Id : out Thread_Id_Ext_Type;
  Status : out Status_Type
) is
-- ++
--|Logic_Flow:
--| If running thread is main thread or error handler then set
--| return_code to Not_Available. Otherwise return the Thread Id
--| number of the calling thread.
-- --


Current_Thread_Id
--|Identifier Number of this thread
: Exec_Itf.Thread_Id_Type;


Found
--|True if current thread found in process control block
: Boolean := False;


Id
--|Framework thread id
: Thread_Id_Ext_Type;


use type Exec_Itf.Thread_Id_Type;


begin-- My_Thread_Id


--|Logic_Flow:
--| Obtain the Thread Id of the current thread. Then use the value
--| to obtain the index into the Thread Control Block table as the
--| thread id.


Id := 0;

Current_Thread_Id := Exec_Itf.GetCurrentThreadId; -- op sys thread id


for I in Thread_Id_Ext_Type'range loop

  if TCB(I).Thread_Id = Current_Thread_Id then
    Found := True;
    Id := I;
    exit;
  end if;
end loop;


--|Logic_Step:
--| Check if running process not found or out-of-range of
--| a user created process.


if not Found then

  raise Program_Error; -- Thread Id not found in Thread Control Block

elsif Thread_Id_Ext_Type'pos(Id) < Thread_Id_Type'first or else
  Thread_Id_Ext_Type'pos(Id) > Thread_Id_Type'last
then

  Thread_Id := 0;
  Status := Not_Available;

  --|Logic_Step:
  --| Return normal process identifier.

else

  Thread_Id := Id;
  Status := No_Error;

end if;


end My_Thread_Id;


---------------------------------------------------------------------------


This procedure shows two different ways to create the thread where the use of the Linux interface (vs that of GNAT) has been commented out.


procedure Thread_Create
( Thread_Name : in Thread_Name_Type;
  Start : in Thread_Address_Type;
  Parameter : in Exec_Itf.Void_Ptr;
  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 );


  function to_Int is new Unchecked_Conversion
  ( Source => Thread_Priority_Type,
    Target => Integer );


  use type Exec_Itf.Thread_Handle_Type;
  use type Exec_Itf.Thread_Id_Type;
  use type System.Address;


begin-- Thread_Create


  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


-- Result := Exec_Itf.pthread_attr_init( Attributes => Attributes_Ptr );
-- Result := Exec_Itf.Thread_Create
-- ( Thread_Id => TId'Address, -- location for thread id
-- Attributes => Attributes'Address, -- location of thread
-- -- attributes to use
-- Start => Start, -- pointer to thread start address
-- Arg => Arg'Address );


  Exec_Itf.Create_Thread( Start => Start,
                          Parameter => Parameter,
                          Stack_Size => Stack_Size,
                          Thread_Priority => to_Int(Thread_Priority),
                          Thread_Handle => Thread_Handle );


  Thread_Created := Thread_Handle /= Exec_Itf.Null_Thread_Handle;
  -- Thread_Created := Result = 0;


  if Thread_Created then

    Exec_Itf.Get_Thread -- get thread identifier from handle
    ( Thread_Handle => Thread_Handle,
      Thread_Id => TId );


    TCB(T_Id).FW_T_Id := T_Id;
    TCB(T_Id).Thread_Id := TId;
    TCB(T_Id).Thread_Handle := Thread_Handle;
--  TCB(T_Id).Status.Attributes := Attributes;
    TCB(T_Id).Status.Current_Priority := 0; -- need actual status here
    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;


end Thread_Create;


---------------------------------------------------------------------------


procedure Thread_Destroy
( Thread_Id : Thread_Id_Type
) is


begin-- Thread_Destroy


  Exec_Itf.Destroy_Thread( Thread_Handle => TCB(Thread_Id).Thread_Handle );

end Thread_Destroy;


. . .


---------------------------------------------------------------------------


procedure Initialize is


  function Id_to_Int is new Unchecked_Conversion
  ( Source => Exec_Itf.Thread_Id_Type,
    Target => Integer );


begin-- Initialize


  Events.Count := 0;

  for I in TCB'Range loop
    TCB(I) := Null_TCB;
  end loop;


  TCB(Main_Thread_Id).FW_T_Id := Main_Thread_Id;
  TCB(Main_Thread_Id).Thread_Id := Exec_Itf.GetCurrentThreadId;
  TCB(Main_Thread_Id).Thread_Handle := Exec_Itf.Null_Thread_Handle;

  TCB(Main_Thread_Id).Status.Thread_State := Ready;
  TCB(Main_Thread_Id).Status.Attributes.Base_Priority :=
    Max_Priority_Value;
  TCB(Main_Thread_Id).Status.Current_Priority := Max_Priority_Value;
  TCB(Main_Thread_Id).Created := True;
  TCB(Main_Thread_Id).Started := True;


  end Initialize;


end Exec_mC;



exec_itf Interface


The Exec_Itf Ada package is another layer that is the immediate interface to the compiler (GNAT) supplied packages and, for Windows, the Win32Ada code. The code used by the Exec_mC event procedures is shown below as well. Code provided previously is not shown.


with Ada.Task_Identification;
with GNAT.OS_Lib;
with Interfaces.C;
with System;
with System.OS_Constants;
with Unchecked_Conversion;


package Exec_Itf is


. . .


---------------------------------------------------------------------------
--|Notes: |
--| Thread types, constants, and functions. |
---------------------------------------------------------------------------


-- those needed to interface directly with Linux C functions.


type Void_Ptr is access all Integer;

type Thread_Handle_Type is private;

type Thread_Id_Type is new Interfaces.C.unsigned_long;


Null_Thread_Handle
--|Null thread handle value
: constant Thread_Handle_Type;


type pid_t is private;


type pthread_attr_t is limited private;


procedure Create_Thread
( Start : System.Address; --pointer to start address of thread
  Parameter : Void_Ptr; -- pointer to parameters
  Stack_Size : Natural; -- stack size in bytes
  Thread_Priority : Integer; -- priority for thread
  Thread_Handle : out Thread_Handle_Type
);
-- Creates a thread with the given (Size) stack size in bytes, and
-- the given (Prio) priority. The task will execute a call to the
-- procedure whose address is given by Code.


procedure Get_Thread
( Thread_Handle : in Thread_Handle_Type;
  --|GNAT thread id
  Thread_Id : out Thread_Id_Type
  --|Linux thread id
);
-- Convert GNAT thread identifier to that of Linux.


function To_Task_Id
( Thread_Handle : in Thread_Handle_Type
) return Ada.Task_Identification.Task_Id;
-- Given a low level Id, as returned by Create_Thread, return a Task_Id,
-- so that operations in Ada.Task_Identification can be used.


function pthread_attr_init
( Attributes : access pthread_attr_t
) return Interfaces.C.int;
-- Return attribute structure with default attribute values
pragma Import(C, pthread_attr_init, "pthread_attr_init");


function Thread_Create
( Thread_Id : in System.Address; --location for thread id
  Attributes : in System.Address; -- location of thread attributes to use
  Start : in System.Address; -- pointer to start address for thread
  Arg : in System.Address -- location of arguments for Start
) return Interfaces.C.int;
-- Create a thread and return its identifier at Thread_Id. The return
-- value is whether the create was successful (0) or an error number of
--  EAGAIN Insufficient resources to create another thread, or a
--   system-imposed limit on the number of threads was encountered.
--   The latter case may occur in two ways: the RLIMIT_NPROC soft
--   resource limit (set via setrlimit(2)), which limits the number
--   of process for a real user ID, was reached; or the kernel's
--   system-wide limit on the number of threads,
--   /proc/sys/kernel/threads-max, was reached.
--  EINVAL Invalid settings in attr.
--  EPERM No permission to set the scheduling policy and parameters
--   specified in attr.
pragma Import(C, Thread_Create, "pthread_create");


procedure Destroy_Thread
( Thread_Handle : Thread_Handle_Type
);
--This procedure may be used to prematurely abort the created thread.
--The Thread_Handle is the value that was returned by thread create.
--Notes:
-- There are two sets of procedures/functions. One using the GNAT
-- thread identifier that I refer to as the Thread Handle and one
-- using the Linux C functions that I refer to as the Thread Id.
-- Matching routines need to be used that use either the handle or
-- the identifier and not the wrong one for the routine.


function GetCurrentThreadId
return Thread_Id_Type;
-- This function gets the system identifier of the currently
--running thread.
pragma Import (C, GetCurrentThreadId, "pthread_self");


function GetPId return pid_t;
--This function gets system identifier of the current process
-- /application.
pragma Import (C, GetPId, "getpid");


. . .


-----------------------------------------------------------------------------


private


. . .


--The following type is similar to that in s-osinte.ads of GNAT rts native
--for a system thread id

type Thread_Handle_Type is new Interfaces.C.unsigned_long;


Null_Thread_Handle
: constant Thread_Handle_Type
:= 0;


type pid_t is new Integer;


subtype char_array is Interfaces.C.char_array;


type pthread_attr_t is record
  Data : char_array (1 .. System.OS_Constants.PTHREAD_ATTR_SIZE);
end record;
pragma Convention (C, pthread_attr_t);
for pthread_attr_t'Alignment use Interfaces.C.unsigned_long'Alignment;


end Exec_Itf;




with Console;
with GNAT.OS_Lib;
with GNAT.Threads;
with GNAT.Time_Stamp;
with Interfaces.C;
with Numeric_Conversion;
with System.Address_To_Access_Conversions;
with Text_IO;


package body Exec_Itf is


. . .


---------------------------------------------------------------------------
--Threads


procedure Create_Thread
( Start : System.Address; --pointer to start address of thread
  Parameter : Void_Ptr; -- pointer to parameters
  Stack_Size : Natural; -- stack size in bytes
  Thread_Priority : Integer; -- priority for thread
  Thread_Handle : out Thread_Handle_Type -- thread handle (GNAT Id)
) is
--|Notes:
--| This procedure does not return a Thread_Id that matches that of
--| GetCurrentThreadId.


  Thread_Ident : System.Address;


  function to_Id is new Unchecked_Conversion
  ( Source => System.Address,
    Target => Thread_Handle_Type );


begin-- Create_Thread


  Thread_Ident := GNAT.Threads.Create_Thread
                  ( Code => Start,
                    Parm => GNAT.Threads.Void_Ptr(Parameter),
                    Size => Stack_Size,
                    Prio => Thread_Priority );

  Thread_Handle := to_Id(Thread_Ident);


end Create_Thread;


procedure Destroy_Thread
( Thread_Handle : Thread_Handle_Type
) is


  function to_Addr is new Unchecked_Conversion
  ( Source => Thread_Handle_Type,
    Target => System.Address );


begin-- Destroy_Thread


  GNAT.Threads.Destroy_Thread( to_Addr(Thread_Handle) );


end Destroy_Thread;


procedure Get_Thread
( Thread_Handle : in Thread_Handle_Type;
  Thread_Id : out Thread_Id_Type
) is


  Ident : Thread_Id_Type;


  function TH_to_Addr is new Unchecked_Conversion
  ( Source => Thread_Handle_Type,
    Target => System.Address );


begin


  GNAT.Threads.Get_Thread( Id => TH_To_Addr(Thread_Handle),
                           Thread => Thread_Id'address );


end Get_Thread;


function To_Task_Id
( Thread_Handle : in Thread_Handle_Type
) return Ada.Task_Identification.Task_Id is


begin-- To_Task_Id


  return GNAT.Threads.To_Task_Id( Thread_Handle'address );


end To_Task_Id;


end Exec_Itf;

No comments: