Background
I am now at the beginning of converting my
Exploratory Project to Linux. I've
installed Fedora in an external drive partition and installed GNAT gcc for
Linux and began working with it.
First problem, of course, was with file naming
since I had used leading upper case on my Windows file names and the Linux
version of GNAT said all the files were misnamed. So I had to find out how to make the names all lower case without
needing to do it all brute force. (Of
course, Linux is case sensitive but it would seem that GNAT could have tried at
least obvious variations of the name before giving up.)
Until beginning the conversion to Linux I
thought that I had separated operating system depended code into a mC-Exec
package and, after I had found the need to have some such routines callable
from outside the mC framework, a minimal Exec package. This was dispelled quickly when I found
references to Win32Ada packages in a number of my project packages.
Therefore, I have started an Exec_Itf package
to be able to gather together the needed Win32 system calls of the project in a
way that there can be a set of common types and function / procedure
declarations in the Exec_Itf Ada specification and the body can be replaced
with an operating system dependent version; one for Win32 and another for
Linux.
It soon became obvious that the needed
Win32Ada types and procedures were not going to be directly compatible with
Linux so the new Exec_Itf interface package would need to declare its own and
that the package body would then need to make the necessary translations to use
the available routines of the particular operating system. This will require some changes in
non-operating system code to use the new types and function/procedure
declarations but seems like the better approach for the long run.
In using the newer versions of GNU GNAT that I
now have available for Windows and Linux it has also became apparent that the
Linux code can reference GNAT supplied packages directly in many cases where I
previously used Win32Ada calls. I
assume that this will also be the case when I finish and return to the Windows
version of the project. So this is
another reason to attempt to define new Exec_Itf types and procedures rather
than attempt to mimic those of Win32Ada.
This has resulted in my starting a new Linux
GNAT Test project in which I will attempt to create an interface for the Exec
and mC-Exec to reference removing all references to Win32Ada packages while
proceeding step-by-step.
So far I have provided what was needed to
allow the Exec-Text_Log child package of Exec to create and write to a Linux
log file rather than a Windows file and for the test Ada main to create and
execute what will become framework component threads in the exploratory
project.
This led me to want to be able to have the
components block and have the main process thread send wakeup events to the
components. And then the gotcha
occurred.
Linux Events
The Windows version of the project uses
mC-Exec Event_Create to call the Win32Ada
Wn32.Winbase.CreateEvent procedure. Looking for something to use with Linux I
failed to come up with anything. Google
searches told me that Linux didn't have such a thing.
However, in response to another's question one
or two responders mentioned using Linux pipes to implement the concept. Thinking about this, it seemed workable
enough. That is, a thread waiting to
read from a pipe would block until there was something to read. Therefore, write a byte to the pipe to send
an event and do a read from the pipe to wait for an event. Same functionality.
Therefore I put the code into my test main to
create the pipe – really a pair of pipes in Linux; a read pipe and a
corresponding write pipe – and to the read and write to try it out. Seemed to work well enough so I went ahead
and began the test version of mC-Exec – in this case exec_mc since mC-Exec is
only callable from within the framework and the GNAT test project has no
framework.
This I was able to accomplish without the need
to modify the interface so a good start – the exploratory project will be able
to use "events" as it did with Windows. (There no longer was the need to supply an event name since I am
using unnamed pipes but the calling routine can, of course, supply a name that
is just ignored.)
exec_mc Event 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.
package
Exec_mC is -- test version of mC-Exec
--|
OS/Executive Specific Interface-- ++
--| Overview:
--| This package, along with Exec_Itf, declares the types and methods to
--| interface between mC and the particular operating system / executive
--| of the target.
--| Notes:
--| This package is private so that its procedures can only be called
--| from child packages of mC and not from packages of components. There
--| is another Exec package for OS/executive specific procedures that can
--| be called by non-mC packages.
-- --
The above note doesn't, of course, apply to
this test package.
type Interface_Integer
is range -(2 ** 31) .. (2 ** 31) - 1;
Max_Name_Length
: constant := 50;
type Status_Type
--| Status returned by system specific Exec
proceduresis ( No_Error,
No_Action,
Not_Available,
Invalid_Param,
Invalid_Config,
Invalid_Mode,
Timed_Out
);
type Time_Interval_Type
-- 32-bit signed integer with 1 millisecond
LSBis new Interface_Integer;
for Time_Interval_Type'Alignment use 8;
Infinite_Time
--| Time Interval to indicate no timeout or no periodic time interval
--| for a thread
: constant Time_Interval_Type := -1;
procedure Initialize;
--| Initialize static data-------------------------------------------------------------------------
--| Notes: |
--| Event types, constants, and functions. |
--| |
--| Events with Linux will be implemented via pipes. |
-------------------------------------------------------------------------
Max_Number_Of_Events
: constant := 50;
type Event_Id_Type
--| Event Identifieris range 0..Max_Number_Of_Events;
type Event_Name_Type
--| Alphanumeric name of event--| Notes: This is the pipe path.
is new String( 1..Max_Name_Length );
Null_Event_Name
--| Null event name: constant Event_Name_Type
:= ( others => ' ' );
procedure Event_Create
( Event_Name : in Event_Name_Type;--| Event name (the name is not actually used for Linux pipes)
Event_Id : out Event_Id_Type;
--| Identifier of created event
Status : out Status_Type
--| Returned status
);
--| Create Event
-- ++
--| Overview:
--| This procedure creates a pair of Linux pipes to be used as an
--| event.
--| Notes:
--| The Event_Id references an internal table of event data. This
--| table, among other data, includes a pair of pipe handles where
--| the first handle of the pair is to wait on the pipe to read it
--| when a write occurs. The second pipe handle of the pair is to
--| write to the pipe to wake up the thread waiting on the read.
-- --
procedure Event_Reset
( Event_Id : in Event_Id_Type;--| Identifier of event to be cleared
Status : out Status_Type
--| Returned status
);
--| Clear Event
-- ++
--| Overview:
--| This procedure clears the read pipe of the event.
--| Notes:
--| For Linux this is a do nothing procedure since the Event_Wait
--| clears the pipe if specified.
-- --
procedure Event_Send
( Event_Id : in Event_Id_Type;--| Identifier of event to be sent
Status : out Status_Type
--| Returned status
);
--| Send Event
-- ++
--| Overview:
--| This procedure writes to the write pipe of the event.
-- --
procedure Event_Wait
( Event_Id : in Event_Id_Type;--| Identifier of event to wait upon
Clear : in Boolean := False;
--| True if to clear the pipe when wait satisfied
Time_Out : in Time_Interval_Type := Infinite_Time;
--| Wait time in which to receive event
Status : out Status_Type
--| Returned status
);
--| Wait for Event
-- ++
--| Overview:
--| This procedure initiates a read of the read pipe for the event.
--| It causes the calling thread to be blocked until the event is
--| received. When the pipe read is satisfied, the procedure will
--| return to allow the invoking thread to continue.
--| Notes:
--| The read is such that it will empty the pipe thus clearing the
--| wakeup event.
-- --
end
Exec_mC;
The above interface can be used with the
exploratory project without the need to modify the referencing code.
package
body Exec_mC is
--
++--| Notes:
--| This package body is for use with Linux.
-- --
--| Notes:
--|
Events
type Event_Pair_Type
--| Pipe pair for read and writeis ( Wait, -- read pipe
Send ); -- write pipe
type Event_Handle_Type
is array ( Event_Pair_Type'Range ) of
Exec_Itf.Pipe_Handle;
type Event_Item_Type
--| Created event infois record
Id : Event_Id_Type;
--| Event identifier
Name : Event_Name_Type;
--| Event name (unused with Linux)
Handle : Event_Handle_Type;
--| Pair of event pipe handles (i.e., file descriptors)
--| for read and write
end record;
The difference from the Windows version is the
declarations of the Event_Pair_Type and the Event_Handle_Type and its use to
provide two handles rather than one for the event.
type Event_Array_Type
--| Created eventsis array ( 1..Event_Id_Type'last ) of Event_Item_Type;
type Event_List_Type
--| Created events with count of those
createdis record
Count : Event_Id_Type;
--| Number of created events
List : Event_Array_Type;
--| List of created events
end record;
Events
--| Created events: Event_List_Type;
-------------------------------------------------------------------------
procedure Initialize is
begin -- Initialize
Events.Count := 0;
end Initialize;
-------------------------------------------------------------------------
--| Notes:--| System event interface procedures.
procedure Event_Create
( Event_Name : in Event_Name_Type;Event_Id : out Event_Id_Type;
Status : out Status_Type
) is
-- ++
--| Logic_Flow:
--| Check that available table positions exist, open a pair of unnamed
--| pipes, store the pipe file descriptors in the table, and return the
--| table index as the event identifier.
-- --
Result
--| Create result: Integer;
use type Interfaces.C.Unsigned_Long;
begin -- Event_Create
--| Logic_Step:
--|
Initialize.
Status
:= Not_Available;
Event_Id := 0;
if Events.Count < Event_Id_Type'last
then
Events.Count := Events.Count + 1;else
return;
end if;
--| Logic_Step:
--|
Create event.
Events.List(Events.Count).Id :=
Events.Count;
Events.List(Events.Count).Name :=
Event_Name;Result := Exec_Itf.Pipe( Events.List(Events.Count).Handle'address );
if Result < 0 then
Events.Count := Events.Count - 1;
Event_Id := 0;Status := Invalid_Param;
else
Event_Id := Events.Count;
Status
:= No_Error;
end if;
end Event_Create;
-------------------------------------------------------------------------
procedure Event_Reset
( Event_Id : in Event_Id_Type;Status : out Status_Type
) is
-- ++
--| Logic_Flow:
--| This procedure does nothing except verify the Event_Id for this
--| Linux implementation since the pipe clear is done upon wakeup from
--| the event wait.
-- --
begin -- Event_Reset
--| Logic_Step:
--|
Initialize.
Status := Not_Available;
--| Logic_Step:
--|
Reset event.
if Event_Id > 0 and then
Event_Id <= Events.Countthen
-- for Linux do nothing, pipe cleared upon wakeup from Wait
Status := No_Error;
end if;
end Event_Reset;
--------------------------------------------------------------------------
procedure Event_Send
( Event_Id : in Event_Id_Type;Status : out Status_Type
) is
-- ++
--| Logic_Flow:
--| Check if Event_Id is within range and, if so, write a byte to the
--| pipe with its handle at the table location of the Event_Id.
-- --
Message
--| One byte "message" to write
to pipe: constant Character := ASCII.NUL;
Written
--| Number of bytes written: Integer := 0;
function to_FH is new Unchecked_Conversion
( Source =>
Exec_Itf.Pipe_Handle,Target => Exec_Itf.File_Handle );
begin -- Event_Send
--| Logic_Step:
--|
Initialize.
Status := Not_Available;
--| Logic_Step:
--|
Set Event.
if Event_Id > 0 and then
Event_Id <= Events.Countthen
Written := Exec_Itf.Write_File -- use write pipe
(
to_FH(Events.List(Event_Id).Handle(Send)), Message'address, -- message to write
1 ); -- bytes to write
if Written = 1 then
Status := No_Error;end if;
end if;
end Event_Send;
--------------------------------------------------------------------------
procedure Event_Wait
( Event_Id : in Event_Id_Type;Status : out Status_Type
) is
-- ++
--| Logic_Flow:
--| Check if Event_Id is within range and, if so, read the pipe with
--| its handle at the table location of the Event_Id to empty it. When
--| there is something to read, the waiting thread will no longer be
--| blocked.
-- --
Bytes_Read
--| Number of bytes read from pipe: Integer := -1;
Bytes_to_Read
--| Depends upon whether to clear the pipe or not
: Integer;
Message
--| Buffer into which to read contents of
pipe: String(1..512); -- more then enough for a full pipe
-- T_Id
-- : Thread_Id_Type;
function to_FH is new Unchecked_Conversion
( Source =>
Exec_Itf.Pipe_Handle,Target => Exec_Itf.File_Handle );
begin -- Event_Wait
--| Logic_Step:
--|
Initialize.
-- My_Thread_Id( Thread_Id => T_Id,
-- Status => Temp_Status );-- if Temp_Status /= No_Error then
-- Status := Not_Available;
-- return;
-- end if;
These commented out references are to the
current thread and its status and control block. They will be made active again when the treatment of threads is
added back to this package.
Status := Not_Available;
-- TCB(T_Id).Waiting := On_Event;-- TCB(T_Id).Status.Thread_State := Waiting;
--| Logic_Step:
--|
Wait for Event.
if Event_Id > 0 and then
Event_Id <= Events.Countthen
Bytes_to_Read := 1; -- one byte written for each use of Send
if Clear then
Bytes_to_Read := 255; -- max possible in pipe to clear it
end if;
Bytes_Read := Exec_Itf.Read_File -- use read pipe
(
to_FH(Events.List(Event_Id).Handle(Wait)),Message'address, -- read buffer
Bytes_to_Read );
--| Logic_Step:
--|
Read from pipe has returned. The
wait is complete.
-- TCB(T_Id).Waiting := No_Condition;
-- TCB(T_Id).Status.Thread_State := Running;-- case Result is
-- when Exec_Itf.WAIT_TIMEOUT =>
-- Status := Timed_Out;
-- when Exec_Itf.WAIT_FAILED =>
-- null;
-- when Exec_Itf.WAIT_OBJECT_0 =>
-- Status := No_Error;
-- when others =>
-- null; --> will return Not_Available
-- end case;
if Bytes_Read > 0 then
Status := No_Error;end if;
end if;
end Event_Wait;
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. Some file interfaces are also shown since a
pipe can be read or written like a file in Linux. However, not all of these interfaces have been tried.
with
GNAT.OS_Lib;
with
System;with Unchecked_Conversion;
package
Exec_Itf is
-------------------------------------------------------------------------
--| Notes: |--| Miscellaneous types, constants, and functions. |
-------------------------------------------------------------------------
procedure Log_Error;
-------------------------------------------------------------------------
--| Notes: |--| File types, constants, and functions. |
-------------------------------------------------------------------------
type File_Handle is private;
--|
Corresponds to the file handle values used in the C routines
type Mode_Type
is ( Binary, Text );for Mode_Type'Size use Integer'Size;
for Mode_Type use ( Binary => 0, Text => 1 );
-- Used in all the Open and Create calls to specify if the file is to be
-- opened in binary mode or text mode. In systems like Unix, this has no
-- effect, but in systems capable of text mode translation, the use of
-- Text as the mode parameter causes the system to do CR/LF translation
-- and also to recognize the DOS end of file character on input. The use
-- of Text where appropriate allows programs to take a portable Unix
-- view of DOS-format files and process them appropriately.
Invalid_File_Handle
--|
File descriptor returned when error in opening/creating file;: constant File_Handle;
function Close_File
( Handle : File_Handle) return Boolean;
-- Close file referenced by Handle. Return False if the underlying
-- service failed. Reasons for failure include: disk full, disk quotas
-- exceeded and invalid file handle (the file may have been closed
-- twice).
function Create_File
( Name : String;Mode : Mode_Type := Text
) return File_Handle;
-- Creates new file with given name for writing, returning file
-- descriptor for subsequent use in Write calls. The file handle is
-- returned as Invalid_Handle if the file cannot be successfully created.
function Create_New_File
( Name : String;Mode : Mode_Type := Text
) return File_Handle;
-- Create new file with given name for writing, returning file descriptor
-- for subsequent use in Write calls. This differs from Create_File in
-- that it fails if the file already exists. File handle returned is
-- Invalid_Handle if the file exists or cannot be created.
function Open_Read
( Name : String;Mode : Mode_Type := Text
) return File_Handle;
-- Open file Name for reading, returning file handle. File handle is
-- returned as Invalid_Handle if file cannot be opened.
function Open_Read_Write
( Name : String;Mode : Mode_Type := Text
) return File_Handle;
-- Open file Name for both reading and writing, returning file handle.
-- File handle returned as Invalid_Handle if file cannot be opened.
function Read_File
( File : File_Handle;Addr : System.Address;
Num : Integer
) return Integer;
-- Read Num bytes to address Addr from file referenced by File.
-- Returned value is count of bytes actually read, which can be
-- less than Num at EOF.
function Write_File
--| Write Len bytes at Addr to File and
return number of bytes written.( File : File_Handle;
Addr : System.Address;
Len : Integer
) return Integer;
-------------------------------------------------------------------------
-- Pipes
function pipe
( Handle : System.Address) return Integer;
pragma Import( C, pipe, "pipe" );
type Pipe_Handle is private;
Invalid_Pipe_Handle
--|
File descriptor returned when error in opening/creating file;: constant Pipe_Handle;
private
type File_Handle is new Integer;
Invalid_File_Handle : constant File_Handle
:= -1;
type Pipe_Handle is new Integer;
Invalid_Pipe_Handle : constant Pipe_Handle
:= -1;
end
Exec_Itf;
with GNAT.OS_Lib;
with GNAT.Threads;
with Interfaces.C;
with Numeric_Conversion;
package
body Exec_Itf is
-------------------------------------------------------------------------
function Errno return Integer;
pragma Import (C, Errno,
"__get_errno");-- Return the task-safe last error number
type String255 is new String(1..255);
type StrPtr is access String255;-- error messages are no longer than 255 characters
function strerror
( Error_Number : Integer) return StrPtr;
pragma Import( C, strerror, "strerror" );
procedure Log_Error is
Error_Num
: Integer;
Error_Ptr
: Exec_Itf.strptr;
begin -- Log_Error
Error_Num := Errno; -- get last error
number
Error_Ptr := StrError( Error_Number =>
Error_Num );
declare
Length : Integer := 0;
function to_Addr is new
Unchecked_Conversion
( Source =>
StrPtr,Target => System.Address );
Error_Msg : String255; -- overlay Ada string on
for Error_Msg use at to_Addr(Error_Ptr);
-- error text
Err_No : Numeric_Conversion.String_Type;
Err : String(1..255) := ( others =>
ASCII.NUL );
Msg : String(1..10+255);
begin
-- Copy error text at StrPtr while
ignoring following text
for I in 1..255 loopexit when Error_Msg(I) = ASCII.NUL;
Err(I) := Error_Msg(I);
Length := I;
end loop;
Msg(1..6) := "Error ";
Err_No :=
Numeric_Conversion.Integer_to_ASCII(Error_Num,3);Msg(7..Err_No.Length+6) := Err_No.Value(1..Err_No.Length); -- ASCII
for I in Err_No.Length+7..10 loop -- insert following space(s)
Msg(I) := ' ';
end loop;
Msg(11..Length+10) := Err(1..Length); -- copy error text
Console.Write( Msg(1..Length+10) );
end;
end Log_Error;
-------------------------------------------------------------------------
-- Files
function File_Descriptor_to_Handle
is new Unchecked_Conversion( Source =>
GNAT.OS_Lib.File_Descriptor,Target => File_Handle );
function Handle_to_File_Descriptor
is new Unchecked_Conversion( Source =>
File_Handle,Target => GNAT.OS_Lib.File_Descriptor );
function to_Mode is new Unchecked_Conversion
( Source =>
Mode_Type,Target => GNAT.OS_Lib.Mode );
function Close_File
( Handle : File_Handle) return Boolean is
Status : Boolean;
begin -- Close_File
GNAT.OS_Lib.Close( FD => Handle_to_File_Descriptor(Handle),
Status => Status );
return Status;
end Close_File;
function Create_File
( Name : String;Mode : Mode_Type := Text
) return File_Handle is
-- Creates new file with given name for writing, returning the file
-- descriptor for subsequent use in Write calls. The file descriptor
-- returned is Invalid_File_Handle if file cannot be successfully created
FileDesc : GNAT.OS_Lib.File_Descriptor;
begin -- Create_File
FileDesc := GNAT.OS_Lib.Create_File(
Name => Name'address,
FMode
=> to_Mode(Mode) );
return File_Descriptor_to_Handle( FileDesc
);
end Create_File;
function Create_New_File
( Name : String;Mode : Mode_Type := Text
) return File_Handle is
-- Create new file with given name for writing, returning file descriptor
-- for subsequent use in Write calls. This differs from Create_File in
-- that it fails if the file already exists. File descriptor returned is
-- Invalid_FD if the file exists or cannot be created.
FileDesc : GNAT.OS_Lib.File_Descriptor;
begin -- Create_New_File
FileDesc := GNAT.OS_Lib.Create_New_File(
Name => Name,
FMode => to_Mode(Mode) );
return File_Descriptor_to_Handle( FileDesc
);
end Create_New_File;
function Open_Read
( Name : String;Mode : Mode_Type := Text
) return File_Handle is
-- Open file Name for reading, returning file descriptor File descriptor
-- returned is Invalid_FD if file cannot be opened.
Handle : GNAT.OS_Lib.File_Descriptor;
begin -- Open_Read
Handle := GNAT.OS_Lib.Open_Read( Name => Name,
FMode
=> to_Mode(Mode) );
return File_Descriptor_to_Handle( Handle
);
end Open_Read;
function Open_Read_Write
( Name : String;Mode : Mode_Type := Text
) return File_Handle is
-- Open file Name for both reading and writing, returning file
-- descriptor. File descriptor returned is Invalid_FD if file cannot be
-- opened.
Handle : GNAT.OS_Lib.File_Descriptor;
begin -- Open_Read_Write
Handle := GNAT.OS_Lib.Open_Read_Write( Name => Name,
FMode => to_Mode(Mode) );
return File_Descriptor_to_Handle( Handle
);
end Open_Read_Write;
function Read_File
( File : File_Handle;Addr : System.Address;
Num : Integer
) return Integer is
begin -- Read_File
return GNAT.OS_Lib.Read( FD =>
Handle_to_File_Descriptor(File),
A => Addr,N => Num );
end Read_File;
function Write_File
( File : File_Handle;Addr : System.Address;
Len : Integer
) return Integer is
begin -- Write_File
return GNAT.OS_Lib.Write( FD =>
Handle_to_File_Descriptor(File),
A => Addr,N => Len );
end Write_File;
end
Exec_Itf;