Message queues for Ada 95 on Linux

Download sv_m.tgz

The package has be 'beautified' 2002-07-15, and it's easier to understand now. Also no c-code is required anymore, all is in Ada code now. (Did some studying on 'Pragma Import')

This package provides a way of sending telegrams to other processes. It is based on the System V message queue functionality. If you do 'man msgget' and related man pages you'll get the idea, or read Richard Stevens' Network Programming part II IPC.

In short, this is how it works. Your goal is to send or receive messages from a message queue. There are a number of ways to set up this, eg several processes can use the same queue, or each processes can have a queue of their own. This package will work in the latter way.

In order to create a message queue you need to call msgget, which takes 2 parameters.

 int msgget(key_t key, int msgflg) 

The key can be found by using ftok (FileTOKey I guess)
 key_t ftok(char *pathname, int proj) 
(Yes proj is an int, not a char according to the headerfile)
So ftok takes a filename and computes a number, adding proj and computes again, and returns a number that the system hopes is unique, which it is in most cases. Weaknesses with this method are described in the man page of ftok.

This key_t number is passed to msgget and we get a queue identity. Depending on msgflg, we get different behaviour depending on the queue already exists or not. The package is set up to create the queue if it doesn't exist, with 'file-permissions' set to 664, and return the queue identity. If it already exists, and it is allowed to read from it, it will return the queue identity.

The identity is how we find were to receive from or send messages to.

Some rules apply to use the packages. Each process using it must have a unique process name. This is accomplished by using environment variables. In bash,

export PROCESS_NAME=the_name
Now, the process know what it is called. But ftok needs a file. There is a global constant in the sv_m body 'gcProcess_directory' that points out a directory. All processes using this package must have a file there (could be empty) that is called the same as 'the_name' exported above. Then ftok will be happy.

The nice thing with this package is that you send ordinary Ada records as messages. The packing and unpacking is provided in the package.

There is of course a 'send' and a 'receive' in the package. If the receive is supplied with a time out, then an exception is raised if no messages arrived in that time. Can be used for periodically actions. If no timeout is supplied, the process will sleep until a message arrives.

A tip is to have message definitions in a separate package, including message identities as constants. Then processes could be in a loop, receiving messages and unpack them with the correct datatype by looking at the message identity, perform some action based on the data in the message, and go back to sleep until the next message arrives. A good thing to have is a shutdown message too...

Both send and receive creates a message queue if there was no before the call. Receive creates a queue for the process calling receive, and send creates a queue for the process that will receive the message.

The send part is generic so instansiation must occure. Examples:

  1. A package containing record definitions (tst.ads)
  2. A program (p1) sending the test record to antoher program (p2)
  3. When p2 gets the message, it unpacks it, increments a counter, packs it and returns it
  4. When p1 gets the message back, it unpacks it, increments a counter, packs it and returns it until the counter reaches 100 000
The largest meassage that can be passed is 1024 bytes. This is a constant in sv_m.adb so if that is too small, then change it. The record package (tst.ads)
package tst is
  type bnl is record
    a : integer := 10;
    b : String(1..3) := "Hej";
    c : String(1..1017) := (others => '-');
  end record;
end tst;
p1 (p1.ads)
with text_io;
with sv_m;
with tst;

procedure p1 is

  package bnl_Package is new sv_m.Generic_io
          (Identity        => 3000,
           Data_Type       => tst.bnl);

  rec : tst.bnl;
  m: sv_m.message_type;
  f : integer := 1016;
begin
  begin
    loop    -- empty the queue if it already exists and contains messages.
      sv_m.receive(m,0.1);
    end loop;
  exception
    when sv_m.timeout => null;
  end;
  rec.a := 0;
  rec.c(f..f+1) := "BL";
 loop
    rec.a := rec.a + 1;
    bnl_Package.send(("p2             ",(others => ' ')),rec);
    sv_m.receive(m,10.0);
    rec := bnl_Package.unpack(m);
    exit when rec.a >= 100_000;
  end loop;
  text_io.put_line ("done rec.a " & rec.a'img);
  text_io.put_line("c(f..f+1): " & rec.c(f..f+1));
  text_io.put_line("f= " & f'img);
  text_io.put_line ("c: |" & rec.c & "|");
end p1;
p2 (p2.ads)
with text_io;
with sv_m;
with tst;
procedure p2 is

  package bnl2_Package is new sv_m.Generic_io
          (Identity        => 3000,
           Data_Type       => tst.bnl);

  rec : tst.bnl;
  m: sv_m.message_type;
begin
  loop
    sv_m.receive(m,30.0);
    rec := bnl2_Package.unpack(m);
--    text_io.put_line ("rec.a " & rec.a'img);
    rec.a :=  rec.a + 1 ;
    bnl2_Package.send(("p1             ",(others => ' ')),rec);
  end loop;
exception
  when sv_m.timeout =>
    text_io.put_line ("done rec.a " & rec.a'img);
end p2;

In order to try this don't forget to change the gcProcess_directory, and add a p1 and p2 file there (touch p1and p2 will do). Also, the sv_m body relies on a c-file called sv_m_c.c that must be compiled as well.

  1. Put all files in a directory (p1.adb, p2.adb, tst,ads, sv_m,ads, sv_m.adb
  2. Compile the ada files: gnatmake p1 and gnatmake p2
  3. Open 3 bash windows
  4. In the first do: export PROCESS_NAME=p1
  5. In the second do: export PROCESS_NAME=p2
  6. In the second type: p2
  7. In the first type: p1
  8. In the third, type ipcs -q many times, and you'll se the message fly between the queues
  9. When done, you can remove the queues with 'ipcrm'
This example takes 20 seconds to run on my PII 350 MHz.