Wednesday, November 28, 2012

Linux Exploratory Project Apps Initiated from Fourth App


Linux Exploratory Project Apps Initiated from Fourth App

 

Overview:

 

The Display app of the Windows version of my Exploratory Project launches the three user / worker apps of the project.  With Windows the use of the System.Diagnostics.Process.Start function of the Visual C# class starts the executable that is passed to it and returns its process identifier.  Since user apps are DOS, that is non-windows, applications a new terminal / DOS window is opened for each process to run in.  Its console output is displayed in its own window and the operator can interact with it via Ada Text_IO input or terminate it via ctrl-C or terminate them via a Display app option (that is, to close the user apps via the display app when the display app is being terminated).

 

So far I have not found a way to do the latter (that is, separate terminal windows) while running the Linux version. 

 

While implementing the fourth Linux application to start the three user apps of the Exploratory Project (in preparation for creating a display app using Linux) I found what first appeared to be a better Linux command than that used in the "Linux Launch of Other Applications from an Application" post of last month.

 

Its use will be illustrated after describing a Segmentation Fault that occurred.

 

Segmentation Fault:

 

Whereas the three user apps of the Exploratory Project all ran successfully in individual terminal windows (see the "Exploratory Project in Linux" post of a week ago) they failed to run successfully when launched from another application as had been done for the test apps as described in the post of last month.  Instead Segmentation Faults occurred for each of the EP user apps at seemingly random times.

 

This was a puzzlement since they had run when launched from their own terminal windows.  At first I thought it must be because of all the stack space and other memory being used as the various threads of each application were created (many more threads and other memory than in the three test applications launched from the fourth application) and looked for remedies to provide more memory.  When I wasn't successful I looked for other ways to have the fourth application process start the three EP user applications.

 

I then found some ways that I had missed last month and tried them with the same result.  However, in doing so I found an internet site of YoLindux that had posted a "Tutorial for Fork, Exec and Process control" that gave information on the system command and on the popen command and I decided that process popen command was much more in line with the single Visual C# Process Start function of Windows.

 

But since its use hadn't alleviated the Segmentation Fault I dug deeper to find the reason.  It turned out that in each application's write to its log file that, since I was using Ada, I hadn't null terminated the text strings.  I don't know why the applications worked when they were run in their own terminal window and not when they were run from the new fourth application that was started from a single terminal window.  But null terminating the strings fixed the problem.

 

[Although, since I was trying other possible fixes as well, there is the possibility that something else that I did resulted in eliminating the segmentation faults.  But I imagine that it was in providing the extra nul character since that made the text strings one character longer and thus prevented the C write file function from attempting to access a byte in another memory segment.]

 

Linux popen command:

 

The YoLinux web site says about popen "The popen() call opens a process by creating a pipe, forking, and invoking the shell (bourne shell on Linux). The advantage to using popen() is that it will allow one to interrogate the results of the command issued."  This is why I chose to use it since it did the fork of the application process and invoked the Linux shell to run the named command.  As with the way I used last month with the test applications, all the terminal output was displayed to the one terminal window (or piped to a file included in the command that launched the fourth application) used to start the fourth application.

 

This web site "tutorial" also mentioned various exec functions that can be used to initiate programs from other programs such as execl() and execlp(), execv() and execvp(), as well as execve(). 

 

But the popen also resulted in the Segmentation Faults.  I finally determined that this happened when I passed the executable path directly to popen but not when I passed the script name for each user app.  In the latter case they ran without the fault.  These scripts are of the form:

 

#!/bin/bash

/home/clayton/Source/EP/Object1/app1.exe

 

where the second line of the script is the same path name that had been passed to popen.  Therefore the use of the shebang ("#!") line would seem to have been necessary to avoid the segmentation faults.

 

Since I use a configuration file to provide variable info to the Exploratory Project apps and this file provides the executable path names I didn't wish to change to providing a script name and require that there be such scripts.

 

GNAT Non_Blocking_Spawn:

 

Therefore, I returned to the use of the Non_Blocking_Spawn function contained in the GNAT OS_Lib package that I used in the test apps. 

 

So that the new app4 application that is being used as a substitute for the graphical display app won't need to directly reference an operating system dependent function, I added a new Execute_Program function to the Exec_Itf package.  Therefore, for either Linux or Windows the display app can invoke the Execute_Program function and have it launch the user app by its executable path and name.

 

The Execute_Program function code is:

 

  function Execute_Program

  ( Name : in String

    --| Program name to be executed

  ) return PId_t is

  -- ++

  --| Notes:

  --|   GNAT s-os_lib.ads for the Spawn procedure has the following:

  --  This procedure spawns a program with a given list of arguments. The

  --  first parameter of is the name of the executable. The second parameter

  --  contains the arguments to be passed to this program. Success is False

  --  if the named program could not be spawned or its execution completed

  --  unsuccessfully. Note that the caller will be blocked until the

  --  execution of the spawned program is complete. For maximum portability,

  --  use a full path name for the Program_Name argument. On some systems

  --  (notably Unix systems) a simple file name may also work (if the

  --  executable can be located in the path).

  --

  --  Spawning processes from tasking programs is not recommended. See

  --  "NOTE: Spawn in tasking programs" below.

  --

  --  Note: Arguments in Args that contain spaces and/or quotes such as

  --  "--GCC=gcc -v" or "--GCC=""gcc -v""" are not portable across all

  --  operating systems, and would not have the desired effect if they were

  --  passed directly to the operating system. To avoid this problem, Spawn

  --  makes an internal call to Normalize_Arguments, which ensures that such

  --  arguments are modified in a manner that ensures that the desired effect

  --  is obtained on all operating systems. The caller may call

  --  Normalize_Arguments explicitly before the call (e.g. to print out the

  --  exact form of arguments passed to the operating system). In this case

  --  the guarantee a second call to Normalize_Arguments has no effect

  --  ensures that the internal call will not affect the result. Note that

  --  the implicit call to Normalize_Arguments may free and reallocate some

  --  of the individual arguments.

  --

  --  This function will always set Success to False under VxWorks and other

  --  similar operating systems which have no notion of the concept of

  --  dynamically executable file. Otherwise Success is set True if the exit

  --  status of the spawned process is zero.

  --|

  --|   Argument_List is a subtype of String_List.  String_List is in

  --|   s-string.ads as   

  --|     type String_List is array (Positive range <>) of String_Access;

  --|   where String_Access is

  --|     type String_Access is access all String;

  -- --

 

    Arg  : GNAT.OS_Lib.Argument_List(1..1) := ( others => null );

    Arg1 : GNAT.OS_Lib.String_Access := new String(1..10);

    Id   : GNAT.OS_Lib.Process_Id;

 

    function to_PId is new Unchecked_Conversion

                           ( Source => GNAT.OS_Lib.Process_Id,

                             Target => PId_t );

 

  begin -- Execute_Program

 

    Arg1.all := ( others => ' ' ); -- empty argument

    Arg(1) := Arg1;

 

    Id := GNAT.OS_Lib.Non_Blocking_Spawn( Program_Name => Name,

                                          Args         => Arg );

    return to_PId(Id);

 

  end Execute_Program;

 

app4 code:

 

The code for app4 that calls Execute_Program to launch the three Exploratory Project user applications is as follows.

 

procedure App4 is

 

  type App_Configuration_Type

  is record

    Count  : Integer;

    --| Number of user apps in configuration

    Handle : Exec_Itf.File_Handle;

    --| Handle of opened configuration file

    Name   : String(1..58);

    --| Path and name of configuration file

  end record;

 

  App_Configuration

  : App_Configuration_Type

  := ( Count  => 0,

       --| Number of user apps in configuration

       Handle => Exec_Itf.Invalid_File_Handle,

       --| File handle

       Name   => "/home/clayton/Source/EP/Ada/A661UA/Apps-Configuration.dat "

       --| Configuration file path and name

     );

 

  subtype Remote_App_Range_Type

  --| Range of remote user application processes to be launched

  is Apps.User_Application_Id_Type range 1..5;

 

  type Launched_App_Type

  --| Data about a user application

  is record

    Running : Boolean;

    --| Whether user application is running

    Id      : Exec_Itf.PId_t;

    --| Process id of launched user application

    Name    : Apps.Configuration.V_String_Type;

    --| User application name

  end record;

 

  type Launched_App_Array_Type

  is array (Remote_App_Range_Type) of Launched_App_Type;

 

  Launched_App

  : Launched_App_Array_Type;

 

  Status

  : mC.Exec.Status_Type;

 

  function Addr_to_Int is new Unchecked_Conversion

                              ( Source => System.Address,

                                Target => Integer );

 

begin

 

  Console.Initialize( App_Id => '4' ); -- output to the terminal and the log file

  Exec.Text_Log.Initialize;

  Exec.Text_Log.Open( App => ( Name => "App4                    ",

                               Id   => 4 ) );

  Console.Write( "App4 started" );

 

  mC.Exec.Initialize( App_Id => 4 );

 

  declare

    Current_Time : Exec_Itf.OS_Time;

    File_Time    : Exec_Itf.OS_Time;

    Split_Time   : Exec_Itf.Split_Time_Type;

    App_Name : String(1..4) := "app4";

    Name     : String(1..60);

    function Time_to_Int is new Unchecked_Conversion

                                ( Source => Exec_Itf.OS_Time,

                                  Target => Integer );

  begin

    Current_Time := Exec_Itf.Current_Time;

    Console.Write( "Current_Time", Time_to_Int(Current_Time) );

    Name(1..24) := "/home/clayton/Source/EP/";

    Name(25..5+23) := String(App_Name(1..4));

    Name(4+25..4+28) := ".log";

    for I in 4+29..Name'length loop

      Name(I) := ASCII.NUL;

    end loop;

    File_Time := Exec_Itf.File_Time_Stamp( Name => Name );

    Console.Write( "File_Time", Time_to_Int(File_Time) );

 

    Split_Time := Exec_Itf.System_Time( Time => File_Time );

    Console.Write( "System Date", Integer(Split_Time.Year),

                                  Integer(Split_Time.Month),

                                  Integer(Split_Time.Day) );

    Console.Write( "System Time", Integer(Split_Time.Hour),

                                  Integer(Split_Time.Minute),

                                  Integer(Split_Time.Second) );

  end;

 

  -- Get thread id of this main thread and display.

  declare

    Main_Thread_Id : mC.Exec.Thread_Id_Ext_Type;

    Process_Id     : Exec_Itf.PId_t;

 

    function to_Int is new Unchecked_Conversion

                           ( Source => Exec_Itf.PId_t,

                             Target => Integer );

  begin

    mC.Exec.My_Thread_Id

    ( Thread_Id => Main_Thread_Id,

      Status    => Status );

 

    Process_Id := Exec_Itf.GetPId;

    Console.Write("Main process id",to_Int(Process_Id));

  end;

 

  --| Logic_Step:

  --|   Obtain user application paths and names from Configuration file.

 

  for I in Remote_App_Range_Type loop

    Launched_App(I).Running := False;

    Launched_App(I).Id := Exec_Itf.Invalid_PId;

    Launched_App(I).Name.Count := 0;

    Launched_App(I).Name.Data := ( others => ASCII.NUL );

  end loop;

 

  -- null terminate the path name for use by C.

  App_Configuration.Name(App_Configuration.Name'length) := ASCII.NUL;

 

  -- open the configuration file.

  App_Configuration.Handle := Exec_Itf.Open_Read

                              ( Name => App_Configuration.Name );

 

  declare

 

    App    : Remote_App_Range_Type := 1;

    Bytes  : Integer;

    Fields : Integer;

    Idx    : Integer;

    Index  : Integer;

    Line   : String(1..1000);

 

    use type Apps.Component_Selector_Type;

 

    function to_Int is new Unchecked_Conversion

                           ( Source => Exec_Itf.PId_t,

                             Target => Integer );

 

  begin

 

    --| Logic_Step:

    --|   Obtain number of user apps from configuration file.

 

    Bytes := Exec_Itf.Read_File     -- read first line with number of user apps

             ( File => App_Configuration.Handle,

               Addr => Line'address,

               Num  => 13 );

    if Bytes > 2 then

      -- Find first field separator

      for I in 1..Bytes loop

        if Line(I) = '|' then

          -- Convert first field to numeric

          App_Configuration.Count := Numeric_Conversion.ASCII_to_Integer

                                     ( Num_String => Line(1..I-1) );

          exit; -- loop

        end if;

      end loop;

    end if;

 

    --| Logic_Step:

    --|   Obtain user app path and executable name.

 

    Bytes := Exec_Itf.Read_File

             ( File => App_Configuration.Handle,

               Addr => Line'address,

               Num  => 1000 );

    Idx := 1; -- index into Line

    if Bytes > 1 then

 

      Fields := 0;

      Index := 0;

 

      Parse:

      while Idx <= Bytes loop

        -- skip past first 10 field separators of each "record"

        if Line(Idx) = '|' then

          Fields := Fields + 1;

          if Fields = 11 then -- separator following path name

            Launched_App(App).Name.Data(Index+1) := ASCII.NUL; -- trailing NUL

            Launched_App(App).Name.Count := Index;

            Fields := 0;

            Index := 0;

            if App < Remote_App_Range_Type'last then

              App := App + 1;

            else

              exit Parse; -- loop

            end if;

          end if; -- Fields = 11

        elsif Fields = 10 then -- not separator, capture path

          Index := Index + 1;

          Launched_App(App).Name.Data(Index) := Line(Idx);

        end if; -- separator character

        Idx := Idx + 1;

      end loop Parse;

 

    end if; -- Bytes > 1

 

  end;

 

  -- Spawn / launch the other three application processes via this one.

  declare

 

  begin

 

    for I in Remote_App_Range_Type loop

      if Launched_App(I).Name.Count > 0 then

        Launched_App(I).Id := Exec_Itf.Execute_Program

                              ( Name => Launched_App(I).Name.Data );

        delay 0.2; -- allow the app to start for doing next

      end if;

    end loop;

 

  end;

 

  --| Logic_Step:

  --|   Count number of launched applications.

 

  declare

 

    Count

    : Integer;

 

    Launched_Count

    : Integer;

 

  begin

 

    Count := 0;

    Launched_Count := 0;

    for I in Remote_App_Range_Type loop

      if Launched_App(I).Name.Count > 0 then

        Launched_App(I).Running := True; -- assume running

        Launched_Count := Launched_Count + 1;

      end if;

    end loop;

 

    delay 0.5; -- Allow launched applications to start

 

    --| Logic_Step:

    --|   Wait for all the launched applications to finish.

 

    Forever:

    loop

      -- Wait for each launched app to finish running.

      for I in Remote_App_Range_Type loop

        if Launched_App(I).Running then

          if not Exec_Itf.Is_Running( Path => Launched_App(I).Name.Data ) then

            Launched_App(I).Running := False;

            Count := Count + 1;

            exit Forever when Count = Launched_Count;

          end if;

        end if;

      end loop;

      delay 0.2; -- to allow other applications to run

    end loop Forever;

 

  end;

 

  --| Logic_Step:

  --|   Terminate.

 

  -- Kill the threads and the lock semaphore.

 -- Exec_mC.Destroy;

 

  Exec.Text_Log.Close;

 

  Exec_Itf.Exit_to_OS;

 

end App4;

 

Another problem:

 

Since app4 does not, as yet, communicate with the user apps as it must for the A661 display app I have the user apps exiting when a counter reaches a particular value.  When app4 is changed to the display app, closing the display app will cause it to close the user apps.  In the meantime, the Linux named pipes remain in existence and seemingly continue to contain any data written to them but not yet read by the receiving app as it is closed - such as when the app is closed via a ctrl-C.  Therefore, I changed the script to start app4 to delete the named pipes before running app4.  As follows:

 

#!/bin/bash

# delete the named pipes created by the three user apps

rm /home/clayton/Source/EP/app1to2

rm /home/clayton/Source/EP/app1to3

rm /home/clayton/Source/EP/app2to1

rm /home/clayton/Source/EP/app2to3

rm /home/clayton/Source/EP/app3to1

rm /home/clayton/Source/EP/app3to2

# run the app that runs the three user apps

/home/clayton/Source/EP/Object4/app4.exe

 
Now onto graphics and GUIs as the app4 replacement.

No comments: