Title |
How do I use a non-blocking send? |
Ref. No. |
QNX.000009294 |
Category(ies) |
Development |
Issue |
I've noticed that the QNX Send() function is a blocking call. Is there some way to arrange for a non-blocking send?
|
Solution |
Yes, and I can think of two reasons why you might want to avoid blocking sends:
•x09to prevent a deadlock, where two processes issue a Send() request to each other at the same time
•x09to allow all critical processes to continue executing rather than becoming SEND-blocked on another process and therefore unable to do further processing
Some of the possible options include:
•x09using a supplementary API such as POSIX message queues, TCP/IP, or the queue library from /usr/free/qnx4/os/samples/misc/queue.tgz
•x09triggering a proxy, which is non-blocking. (In some cases, proxies may be used instead of messages.)
•x09re-architecting the program interface so that processes communicate on a client-server model instead of a peer-to-peer model
•x09setting up a "send client" to perform Send() requests
Supplementary APIs
Using one of the supplementary APIs can save time since most of the coding is already done for you. All you have to do is link to the library and run the appropriate manager.
On the down side, all three libraries (POSIX, TCP/IP, and the queue library) use a transparent Send() to implement the send function, so a SEND-blocked state isn't completely unavoidable. In all three cases, the APIs assume that the receiving program will be spending most of its time blocked on a Receive() call. If that's the case, the sending process won't be caught for long in a SEND-blocked state.
You also get the overhead of going through a general-purpose messaging process, though in the case of the queue library, you can customize or optimize that library and server for your application. There is one added drawback to using the TCP/IP library: you'll be bringing in a relatively large, separately licensed server to perform a fairly small task.
Triggering a Proxy
Proxies are definitely nonblocking, and are the fastest way to get a binary value (state) change to another process. However, proxies are unable to carry very much content since their primary purpose if for notification.
The Client-Server Model
Re-architecting the interface to a client-server model, if applicable, can be one of the cleanest solutions. The client-server model works well for a pair of processes exchanging data, or for a single process that exchanges data with other processes (assuming that only one of these processes can't afford to be SEND-blocked).
If the client wishes to send to the server, it simply calls the Send() function. If the server wishes to send to the client, a sequence like this will occur:
Server: trigger() client proxy.
Client: Receive() proxy. Send() "I got proxy" message to server [SEND-blocked].
Server: Receive() client "I got proxy" message. reply() with message server wanted to send.
Client: Return from Send() with message from server. Perform any processing necessary. Send() reply to server, if any needed for this message.
Server: Receive() reply from client.
Normally, the client and server will set up the proxy when they are initialized; the client calls the qnx_proxy_attach() function and send the resulting proxy to the server for the server's future use.
Setting Up a Send-Client
In setting up a send-client, a separate process is needed to issue Send() requests on the behalf of the process. The sequence would be something like this:
Client: Send() "I'm ready" message to main process [SEND-blocked].
Main process: Receive() "I'm ready" message and flag this. Perform whatever processing, Receive() loop, etc. is needed to send. Reply() to send client with message to be sent, including header. Using ID of processes (pid) that would otherwise be SEND-blocked, flag not ready to send.
Client: Return from Send(); send message wherever [SEND-blocked].
Main process: Continues processing.
Client: Return from Send(). Send() to main process with reply from previous Send(). Return "I'm ready" message to Send() again [SEND-blocked].
Main process: Receive() reply ("I'm ready") message and flag this. Perform whatever processing, Receive() loop, etc. is needed to send. Reply() to send-client with message to be sent, including header. Using ID of processes (pid) that would otherwise be SEND-blocked, flag not ready to send.
This sequence adds the overhead of a second process and extra copies of data for all sends. Using shared memory or running the send client as a thread in the main process could alleviate some of this overhead.
A minimal send client might look something like this.
#include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/kernel.h> #include <sys/sendmx.h>
#define BUF_SIZE 65535 #define READY_MSG 1
typedef struct { x09short type; x09unsigned len; x09char data[]; x09} msg_struct;
void main() { x09struct_mxfer_entry rmsg[2]; x09struct_mxfer_entry smsg[1]; x09pid_t pid, parent_pid; x09char *buf; x09msg_struct *msg;
x09parent_pid getppid() x09buf = (char *) malloc(BUF_SIZE); x09msg = (msg_struct *) buf;
x09_setmx( &(rmsg[0]), &pid, sizeof(pid)); x09_setmx( &(rmsg[1]), but, BUF_SIZE; x09msg->type = READY_MSG; x09msg->len = 0; x09_setmx( &(smsg[0]), msg, sizeof(msg->type) + sizeof(msg->len));
x09for (;;) x09{ x09x09Sendmx( parent_pid, 1, 2, smsg, rmsg ); x09x09Send( pid, msg, msg, msg->len+sizeof(msg->type), BUF_SIZE ); x09x09_setmx( &(smsg[0]), msg, sizeof(msg_struct) - sizeof(char) + msg->len ); x09} }
A small set of cover functions for this send-client might look like:
#include <stdlib.h> #include <unistd.h> #include <process.h> #include <sys/types.h> #include <sys/kernel.h> #include <sys/sendmx.h>
#define BUF_SIZE 65535 #define READY_MSG 1
typedef struct { x09short type; x09unsigned len; x09char data[]; x09} msg_struct;
static pid_t sc_pid; static int client_ready; static int rbuf_len;
int setup-send-client() { x09sc_pid = spawnl( P_NOWAIT, "/scpath/send-client", "send-client", NULL ); x09if (sc_pid)
x09{ x09x09int status; x09x09if ( Receive(sc_pid, &status, sizeof(status)) && x09x09(status = READY_MSG)) x09x09client_ready 1; x09x09else x09x09client_ready 0; x09} x09x09else x09x09client_ready = 0; x09x09return client_ready; }
int my_send( pid_t pid, void *sbuf, void *rbuf, unsigned slen, unsigned rlen)
{ x09struct _mxfer_entry rmsg[2]; x09rbuf = rbuf; /* or could just not pass as parameter */ x09rbuf_len = rlen;
x09if (! client_ready) x09return -1;
x09_setmx( &(rmsg[0]), &pid, sizeof(pid)) x09_setmx( &(rmsg[1]), sbuf, slen); x09client_ready = 0; x09return Replymx( pid, 2, rmsg ); }
int my_receive( pid_t pid, void *msg, unsigned nbytes) { x09int rpid;
x09rpid = Receive( pid, msg, nbytes ); x09if (rpid != sc_pid) x09return rpid; x09client_ready = 1; x09if (rhuf_len) /* previous send expected reply */ x09return 0; /* Receive will never return pid 0, use to flag Reply */ x09rpid = Receive( pid, msg, nbytes ); x09return rpid; }
In practice, some facility to handle and pass errors back to the parent is important.
|
|