UNIX System V Message Queues

Threads and processes can utilise UNIX System V (SysV) message queues to send and receive data in an arbitrary order for in-process and inter-process communication. Message queues were later adopted by a POSIX standard.

C Function Description
msgget() Creates a new message queue or connects to an existing one.
msgsnd() Sends a message to the message queue.
msgrcv() Receives messages of all types or only messages of the given type from the message queue. The access is blocking by default, unless flag IPC_NOWAIT is set.
msgctl() Changes permissions or closes the message queue.
Table 1: SysV message queue functions in C

Only a few routine calls are necessary to interact with SysV message queues from Fortran (table 1). The Fortran module sysv in sysv.f90 contains the required interface bindings. A custom derived type holds the message text:

integer, parameter :: MESSAGE_LEN = 256

type, bind(c) :: message_type
    integer(kind=c_long)   :: type
    character(kind=c_char) :: text(MESSAGE_LEN)
end type message_type

Instead of the particular message type, any derived type is allowed. The maximum size of a message is limited, usually 4056 bytes.

Code Name Description
2 ENOENT No message queue exists for key and msgflg did not specify IPC_CREAT.
12 ENOMEM The system does not have enough memory for the new data structure.
13 EACCES The calling process does not have permission to access the queue.
17 EEXIST A message queue already exists for key.
28 ENOSPC The system limit for the maximum number of message queues (MSGMNI) is reached.
Table 2: Error codes written to errno by msgget()

For debugging, we can output the numeric error code that gives further information regarding the type of error (table 2). The C integer errno in errno.h is defined as a macro and cannot be accessed directly from Fortran. Therefore, we first have to write a wrapper function in C, like error_number() in errno.c, that simply returns errno. Then, we are able to implement a Fortran interface, as in module posix in posix.f90.

Compiling the C and Fortran sources with gcc and gfortran creates the object files sysv.o, posix.o, and errno.o for linking:

$ gcc10 -c errno.c
$ gfortran10 -c posix.f90
$ gfortran10 -c sysv.f90

Example

The example opens a new SysV message queue with c_msgget(), sends a message of type 1 with c_msgsnd(), and then waits until c_msgrcv() returns the received message. The message queue access has been abstracted with the Fortran function ipc_send() and ipc_receive(). The example combines sender and receiver in a single process. In general, a message queue would rather be used to interconnect separate threads or processes instead.

! example.f90
program main
    use, intrinsic :: iso_fortran_env, only: stderr => output_unit, stdin => input_unit
    use, intrinsic :: iso_c_binding,   only: c_char, c_loc, c_null_char, c_null_ptr
    use :: posix
    use :: sysv
    implicit none
    integer,         parameter :: IPC_PERM = 0666 ! Access permissions.
    integer(kind=8), parameter :: IPC_TYPE = 1    ! Message type.
    character(len=MESSAGE_LEN) :: msg_text        ! Received message.
    integer                    :: msqid           ! Message queue id.

    ! Create message queue.
    msqid = c_msgget(IPC_PRIVATE, ior(IPC_CREAT, IPC_PERM))

    if (msqid < 0) then
        write (stderr, '(a, i0)') 'msgget() failed: ', c_errno()
        stop
    end if

    print '(a, i0, /)', 'Message Queue ID: ', msqid

    ! Send message to message queue.
    print '(a)', 'Sending message ...'

    if (ipc_send(type=IPC_TYPE, text='Hello, World!', flag=IPC_NOWAIT) > -1) then
        print '(a)', 'Done.'
    else
        write (stderr, '(a, i0)') 'msgsnd() failed: ', c_errno()
    end if

    ! Receive message from message queue (blocking I/O). Set `flag` to
    ! `IPC_NOWAIT` for non-blocking I/O.
    print '(a)', 'Waiting for message ...'

    if (ipc_receive(type=IPC_TYPE, text=msg_text, flag=0) > 0) then
        print '(2a)', 'Received: ', trim(msg_text)
    else
        write (stderr, '(a, i0)') 'msgsnd() failed: ', c_errno()
    end if

    ! Wait for user input.
    print '(/, a)', 'Press Enter to quit.'
    read (*, '(a)')

    ! Remove message queue.
    print '(a)', 'Closing message queue ...'

    if (c_msgctl(msqid, IPC_RMID, c_null_ptr) > 0) then
        write (stderr, '(a, i0)') 'msgctl() failed: ', c_errno()
    end if
contains
    function ipc_receive(type, text, flag)
        !! Waits for message of given type and returns message text. Calling the
        !! function is blocking, unless `flag` is set to `IPC_NOWAIT`.
        integer(kind=8),            intent(in)  :: type
        character(len=MESSAGE_LEN), intent(out) :: text
        integer,                    intent(in)  :: flag
        integer(kind=8)                         :: ipc_receive
        type(message_type), target              :: message

        ipc_receive = c_msgrcv(msqid, c_loc(message), MESSAGE_LEN, type, flag)
        ! Convert C char array to Fortran string.
        call c_f_str_chars(message%text, text)
    end function ipc_receive

    function ipc_send(type, text, flag)
        !! Converts Fortran string to C char array, and then sends message of
        !! given type by calling `c_msgsnd()`.
        integer(kind=8),  intent(in) :: type
        character(len=*), intent(in) :: text
        integer,          intent(in) :: flag
        integer                      :: ipc_send
        type(message_type), target   :: message
        integer                      :: i

        message%type = type
        ! Convert Fortran string to C char array.
        call f_c_str_chars(text, message%text, MESSAGE_LEN)
        ipc_send = c_msgsnd(msqid, c_loc(message), MESSAGE_LEN, flag)
    end function ipc_send

    subroutine c_f_str_chars(c_str, f_str)
        !! Copies a C string, passed as a char array pointer, to a Fortran string.
        character(kind=c_char), intent(in)  :: c_str(*)
        character(len=*),       intent(out) :: f_str
        integer                             :: i

        i = 1

        do while (c_str(i) /= c_null_char .and. i <= len(f_str))
            f_str(i:i) = c_str(i)
            i = i + 1
        end do

        if (i < len(f_str)) f_str(i:) = ' '
    end subroutine c_f_str_chars

    subroutine f_c_str_chars(f_str, c_str, n)
        !! Copies a Fortran string to a C char array.
        character(len=*),       intent(in)  :: f_str
        integer,                intent(in)  :: n
        character(kind=c_char), intent(out) :: c_str(n)
        integer(kind=8)                     :: i

        i = 1

        do while (f_str(i:i) /= c_null_char .and. i <= size(c_str))
            c_str(i) = f_str(i:i)
            i = i + 1
        end do

        if (i < size(c_str)) c_str(i:) = c_null_char
    end subroutine f_c_str_chars
end program main

The example workspace directory should contain the following files:

We can then compile and run the message queue example with:

$ gfortran10 -o example src/example.f90 sysv.o posix.o errno.o
$ ./example
Message Queue ID: 393227

Sending message ...
Done.
Waiting for message ...
Received: Hello, World!

Press Enter to quit.

Another process can open the created message queue for inter-process communication by calling c_msgget(393227_8, 0666). It is crucial to always remove opened message queues, as no more queues can be created after the system limit is reached (usually 40). The command-line tool ipcs shows all open SysV message queues:

$ ipcs -q
Message Queues:
T           ID          KEY MODE        OWNER    GROUP
q       393227            0 --rw--wa-w- user     user

Zombie message queues can be removed manually with ipcrm:

$ ipcrm -q <msqid>