Better IPC – sending a data item

This leads us to the next interesting fact: it is possible to send a data quantum—a piece  of data—via signals. To see how, let's revisit the powerful struct siginfo_t we studied earlier in this chapter. To have the signal handler receive the pointer to it, recall that we use the SA_SIGINFO flag when calling sigaction(2).

Recall the fact that, within struct siginfo_t, the first three members are simple integers, the fourth member is a union of structures, there are seven of them—only one of which will get instantiated at runtime; the one that does depends on which signal is being handled!

To help us recall, here's the initial portion of struct siginfo_t:

typedef struct {
int si_signo; /* Signal number. */
int si_code;
int si_errno; /* If non-zero, an errno value associated with
this signal, as defined in <errno.h>. */
union
{
int _pad[__SI_PAD_SIZE];
/* kill(). */
struct
{
__pid_t si_pid; /* Sending process ID. */
__uid_t si_uid; /* Real user ID of sending process. */
} _kill;

[...]

Within the union of structures, the structure of interest to us right now is the one that deals with real time signals—this one:

[...]
/* POSIX.1b signals. */
struct
{
__pid_t si_pid; /* Sending process ID. */
__uid_t si_uid; /* Real user ID of sending process. */
__sigval_t si_sigval; /* Signal value. */
} _rt;
[...]

So, it's quite straightforward: if we trap some real time signals and use SA_SIGINFO, we shall be able to retrieve the pointer to this structure; the first two members reveal the PID and RUID of the sending process. That itself is valuable information! 

The third member though, the sigval_t, is the key (in /usr/include/asm-generic/siginfo.h on Ubuntu and in /usr/include/bits/types/__sigval_t.h on Fedora):

union __sigval
{
int __sival_int;
void *__sival_ptr;
};
typedef union __sigval __sigval_t;

Note that the sigval_t is itself a union of two members: an integer and a pointer! We know that a union can only have one of its members instantiated at runtime; so the deal here is: the sender process populates one of the preceding members with data and then sends a real time signal to the receiver process. The receiver can extract the data quantum sent by appropriately de-referencing the preceding union. This way, one is able to send data across processes; the data is effectively piggy-backed on a real time signal! Quite cool.

But think: we can use only one of the members to piggy-back our data, either the integer int sival_int or the void * sival_ptr pointer. Which should one use? It's instructive to recall what we learned in Chapter 10Process Creation on process creation: every address within a process is a virtual address; that is, my  virtual address X is likely not pointing to the same physical memory as your virtual address X. In other words, attempting to communicate data via a pointer, which is after all nothing but a virtual address, might now work as well as expected. (If you are unsure about this, might we suggest rereading the malloc and The fork sections in Chapter 10, Process Creation.)

In conclusion, using an integer to hold and communicate data to our peer process would usually be a better idea. In fact, C programmers know how to extract, literally, every last bit from memory; you can always treat the integer as a bitmask and communicate even more information!

Additionally, the C library provides a helper routine to quite easily send a signal with data embedded within, the sigqueue(3) API. Its signature:

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);

The first two parameters are obvious: the process to send the signal sig to; the third parameter value is the union discussed.

Lets try this out; we write a small producer-consumer type of application. We run the consumer process in the background; it polls, waiting for the producer to send it some data. (As you might guess, polling is not ideal; in the multithreading topics, we shall cover superior methods; for now, we shall just simplistically poll.) When the receiver detects data has been sent to it, it displays all relevant details.

First, a sample run: to begin, we run the consumer (receiver) process (ch12/sigq_ipc/sigq_recv.c)  in the background:

$ ./sigq_recv &
[1] 13818
./sigq_recv: Hey, consumer here [13818]! Awaiting data from producer
(will poll every 3s ...)
$

Next, we run the producer (ch12/sigq_ipc/sigq_sender.c) , sending a data item to the consumer:

$ ./sigq_sender 
Usage: ./sigq_sender pid-to-send-to value-to-send[int]
$ ./sigq_sender $(pgrep sigq_recv) 42
Producer [13823]: sent signal 34 to PID 13818 with data item 42
$ nanosleep interrupted: rem time: 0000002.705461411

The consumer processes the signal, understands that data has arrived, and in the next polling cycle prints out the details:

Consumer [13818] received data @ Tue Jun 5 10:20:33 2018
:
signal # : 34
Producer: PID : 1000
UID : 1000 data item : 42

For readability, only key parts of the source code are displayed next; to view the complete source code, build it and run it, the entire tree is available for cloning from GitHub here: https://github.com/PacktPublishing/Hands-on-System-Programming-with-Linux.

Here's the receiver: ch12/sigq_ipc/sigq_recv.c: main() function:

#define SIG_COMM   SIGRTMIN
#define SLP_SEC 3

[...]
static volatile sig_atomic_t data_recvd=0;
[...]
int main(int argc, char **argv)
{
struct sigaction act;

act.sa_sigaction = read_msg;
sigfillset(&act.sa_mask); /* disallow all while handling */
act.sa_flags = SA_SIGINFO | SA_RESTART;
if (sigaction(SIG_COMM, &act, 0) == -1)
FATAL("sigaction failure");

printf("%s: Hey, consumer here [%d]! Awaiting data from producer "
"(will poll every %ds ...) ",
argv[0], getpid(), SLP_SEC);

/* Poll ... not the best way, but just for this demo... */
while(1) {
r_sleep(SLP_SEC);
if (data_recvd) {
display_recv_data();
data_recvd = 0;
}
}
exit(EXIT_SUCCESS);
}

We poll upon the arrival of the real time signal, sleeping in a loop for three seconds on each loop iteration; polling is really not the best way to code; for now, we just keep things simple and do so (in the Chapters 14, Multithreading with Pthreads Part I - Essentials and Chapter 15, Multithreading with Pthreads Part II - Synchronization, we shall cover other efficient means of synchronizing on a data value).

As explained in the section Sleeping correctly, we prefer to use our own wrapper over nanosleep(2), our r_sleep() function, keeping the sleep safe.

In the meantime, a part of the sender code: ch12/sigq_ipc/sigq_sender.csend_peer():

static int send_peer(pid_t target, int sig, int val)
{
union sigval sv;

if (kill(target, 0) < 0)
return -1;

sv.sival_int = val;
if (sigqueue(target, sig, sv) == -1)
return -2;
return 0;
}

This function performs the work of checking that the target process is indeed alive, and if so, sending it the real time signal via the useful sigqueue(3) library API. A key point: we wrap or embed the data to be sent inside the sigval union, as an integer value.

Back to the receiver: when it does receive the real time signal, its designated signal handler code, read_msg(), runs:

[...]
typedef struct {
time_t timestamp;
int signum;
pid_t sender_pid;
uid_t sender_uid;
int data;
} rcv_data_t;
static rcv_data_t recv_data;

[...]

/*
* read_msg
* Signal handler for SIG_COMM.
* The signal's receipt implies a producer has sent us data;
* read and place the details in the rcv_data_t structure.
* For reentrant-safety, all signals are masked while this handler runs.
*/
static void read_msg(int signum, siginfo_t *si, void *ctx)
{
time_t tm;

if (time(&tm) < 0)
WARN("time(2) failed ");

recv_data.timestamp = tm;
recv_data.signum = signum;
recv_data.sender_pid = si->si_pid;
recv_data.sender_uid = si->si_uid;
recv_data.data = si->si_value.sival_int;

data_recvd = 1;
}

We update a structure to hold the data (and metadata), allowing us to conveniently print it whenever required.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset