40+
4 Areas
Click to Reveal
Intermediate
How to Use This Page
All interview questions from this chapter are collected here with model answers. Click any question to reveal the answer. Use this page for quick revision before interviews. Questions cover four topic areas: Architecture, Concurrent Server, Server Request Handling, and Client Design.
| API / Concept | What It Does | Key Point |
|---|---|---|
msgget(SERVER_KEY, ...) |
Get existing server queue by fixed key | Key must match on both sides |
msgget(IPC_PRIVATE, ...) |
Create brand new unnamed queue | Must pass ID out-of-band |
msgsnd(qid, &msg, size, flags) |
Send message to queue | size excludes mtype |
msgrcv(qid, &msg, maxsize, type, flags) |
Receive message from queue | type=0 means any type |
msgctl(qid, IPC_RMID, NULL) |
Delete a message queue | Required — queues persist until deleted |
atexit(removeQueue) |
Register cleanup on exit | Does NOT run on SIGKILL |
waitpid(-1, NULL, WNOHANG) |
Reap any finished child, non-blocking | Must loop — one call per child |
SA_RESTART |
Auto-restart slow syscalls after signal | Not all syscalls honor it |
EINTR |
System call interrupted by signal | Retry the call |
_exit() |
Exit without flushing stdio buffers | Use in forked child |
RESP_MT_FAILURE |
Server could not open file | First response to check |
RESP_MT_DATA |
A chunk of file data | May arrive multiple times |
RESP_MT_END |
End of transfer | Zero-length mtext |
7 questions on application design, message types, and IPC_PRIVATE.
IPC_PRIVATE creates a unique, unnamed queue that only the creating process knows about. If all clients used a fixed key, they would all get the same queue and would steal each other’s response messages. With IPC_PRIVATE, each client gets a unique queue ID which it passes to the server inside the request — guaranteeing that only that client receives the responses.RESP_MT_DATA: Sent for each chunk of file data. May be sent multiple times for large files.
RESP_MT_END: Sent with zero data length after all data has been sent. Signals to the client that the transfer is complete.
IPC_PRIVATE — no fixed address — so the ID must be communicated dynamically. Embedding clientId in the request is the standard request-reply pattern in SysV MQ applications.SERVER_KEY is a fixed integer key (like a port number) that any process can use to locate the server’s queue with msgget(SERVER_KEY, ...). IPC_PRIVATE is a special value (0) that tells the kernel to create a brand-new queue with no key — only the creating process gets the returned ID. There is no way to look up an IPC_PRIVATE queue by name.msgctl(IPC_RMID) or until the system reboots. Unlike file descriptors, there is no reference counting — closing a process does not automatically remove the queue. If not deleted, the queue occupies a slot in the system’s message queue table (limited by MSGMNI) indefinitely.msgrcv(..., 0, 0)). Each request contains the client’s unique queue ID. After forking, each server child sends responses to its specific client’s queue using req->clientId. The clients are separated by having different reply queue IDs, not by message type.mtype to separate them, two clients could pick the same type value, causing races. The IPC_PRIVATE per-client queue design cleanly avoids this.11 questions on fork, SIGCHLD, zombies, SA_RESTART, and EINTR.
wait()/waitpid() to collect its exit status. It holds no resources except a PID slot in the process table. In a server that forks a child per request without reaping, thousands of zombies accumulate, eventually exhausting all available PIDs. fork() then fails with EAGAIN, crashing the server.waitpid() call would only reap one child, leaving the other two as zombies. The while loop continues until waitpid() returns 0 (no more children have exited), ensuring all are reaped.WNOHANG makes waitpid() return immediately (with 0) if no child has exited, instead of blocking. Without it, the signal handler would block waiting for a child — but signal handlers should complete quickly and never block. The loop exits cleanly when waitpid() returns 0.errno and the next instruction that checks it. waitpid() itself may set errno (e.g. to ECHILD). If we don’t save and restore errno, the interrupted code in the main loop might see a corrupted error value, causing incorrect behavior.SA_RESTART tells the kernel to automatically restart certain “slow” system calls (those that can block) after a signal handler returns, instead of failing with EINTR. However, not all system calls are restarted on all Linux versions or in all situations. msgrcv() in particular may or may not restart. The explicit EINTR check in the loop is belt-and-suspenders — it handles the cases where auto-restart doesn’t happen.fork(), the child has copies of the parent’s stdio buffers. exit() flushes all stdio buffers before terminating. Flushing the parent’s buffered output from the child would cause duplicate output. _exit() terminates immediately without touching stdio buffers, leaving the parent’s buffers intact.fork(). The req variable (holding the request message with the client’s pathname and queue ID) is on the parent’s stack. After forking, the child has its own copy of that stack and thus a copy of req, ready to use.wait() yet — it’s dead but still in the process table.An orphan is a child whose parent died while the child was still running. The kernel re-parents it to
init (PID 1), which periodically calls wait(), so orphans do not become permanent zombies.msgctl(IPC_RMID) on the server queue before all clients have sent their requests, those clients’ msgsnd() calls would fail with EINVAL.10 questions on file open, data chunking, message sizing, and error paths.
read() may return fewer bytes than RESP_MSG_SIZE — especially on the last chunk of a file or when reading special files. Using numRead ensures only the actual data bytes are sent. Using RESP_MSG_SIZE would send uninitialized garbage bytes from the buffer after the actual data.RESP_MT_END message — a message with an mtype of RESP_MT_END and zero bytes of data. This is the sentinel that tells the client “all file data has been sent, you can stop receiving.” The zero length is valid in SysV MQ; the message is received successfully but msgrcv() returns 0.mtype field is part of the kernel’s message metadata — it is used for routing (selective receive by type). The size argument only describes the user data payload (everything after mtype). This is a fundamental SysV MQ API convention. Macros like REQ_MSG_SIZE = sizeof(struct requestMsg) - sizeof(long) encode this correctly.msgsnd() fails, that channel is broken — there is no other way to notify the client. The server simply stops sending. The consequence is that the client never receives RESP_MT_END and blocks forever in msgrcv(). This is a known limitation. Fix: add a timeout on the client, or include an error-reporting queue in the protocol.MSGMAX, typically 8192 bytes on Linux). Sending a message larger than this causes msgsnd() to fail with EINVAL. This is exactly why the server reads and sends in chunks of at most RESP_MSG_SIZE.flags = 0 in msgsnd()), msgsnd() blocks until there is space in the queue. The server child waits. In this application, the client is also reading from the queue simultaneously, so it frees up space. If you passed IPC_NOWAIT, msgsnd() would fail immediately with EAGAIN.exit(EXIT_FAILURE) on the error path. Since the child hasn’t done any stdio buffering work (it just opened a file and called snprintf), there is nothing harmful about calling exit() here. The _exit() is specifically important for the normal work-done path to prevent double-flushing of parent stdio buffers. On the error path the child is done anyway.MSGMNB: maximum total bytes in a single queue (default 16384 bytes).
MSGMNI: maximum number of message queues system-wide.
These can be read/changed via
/proc/sys/kernel/msgmax etc.open() will succeed. The server will read and send the file contents even though normal users cannot read it directly. This is a security concern in real servers — the server should check the requesting user’s permissions, not just try to open the file with its own (root) privileges.12 questions on IPC_PRIVATE, atexit, request composition, and response handling.
atexit() handlers do NOT run when: the process receives SIGKILL or SIGSTOP (which cannot be caught or ignored), the process calls _exit() directly, or the process is terminated by a hardware fault (e.g. SIGSEGV by default). For SIGTERM and SIGINT, if the process doesn’t install a handler that calls exit(), the queue will also leak.removeQueue() function registered with atexit() needs to access clientId. atexit() handlers take no arguments and have no return value. The only way to share state between main() and an atexit() handler is via a global (or static file-scope) variable.S_IWGRP grants write permission to processes in the same group as the client queue’s owner. This allows the server child (which may have a different UID) to call msgsnd() on the client’s queue to deliver responses.strncpy(dst, src, n) copies at most n bytes. If src is longer than n, it copies n bytes without a null terminator. The manual dst[n-1] = '\0' ensures the string is always null-terminated even when truncation occurs. Without it, the server could receive a non-null-terminated pathname and crash or open the wrong file.alarm(timeout_seconds) before the msgrcv() call and install a SIGALRM handler. When the alarm fires, msgrcv() returns -1 with errno == EINTR — the client can then treat this as a timeout. Another approach (Linux-specific): use IPC_NOWAIT with polling in a loop, sleeping between attempts.type argument of 0 in msgrcv() means “receive the first message in the queue regardless of its type.” The client does not filter by type here — it receives whatever arrives first (which could be RESP_MT_FAILURE, RESP_MT_DATA, or RESP_MT_END) and checks resp.mtype after receiving.IPC_PRIVATE queue with a unique ID. Each server child sends responses to its respective client’s queue. The two transfers are completely independent and do not interfere with each other.msgrcv() waiting for a response that will never come. The simple client has no timeout mechanism to detect this. The server queue is deleted when the server exits (if it calls msgctl(IPC_RMID)), but this does not unblock the client’s msgrcv() on its private queue.RESP_MT_FAILURE only if it cannot open the file — and if it can’t open the file, it exits immediately after sending the failure message. So the failure message is always the first and only message. Once the client receives the first message and it’s not RESP_MT_FAILURE, the file is being served successfully and subsequent messages will only be DATA or END.ipcs -qDelete a specific queue:
ipcrm -q <msqid>Delete by key:
ipcrm -Q <key>You can also read
/proc/sysvipc/msg for a machine-readable list.POSIX MQ: Newer API (
mq_open()); named like files (/myqueue); returns a file descriptor — can use with select()/epoll(); priority-ordered delivery; more portable.Choose SysV for legacy compatibility. Choose POSIX for new code — especially if you need to integrate with select/poll event loops.
IPC_EXCL combined with IPC_CREAT means “create only if it doesn’t already exist — fail with EEXIST if it does.” The server uses IPC_CREAT | IPC_EXCL to create its queue. This prevents accidentally connecting to a stale queue from a previous server run. If the old queue still exists (e.g. server crashed without cleanup), the new server startup will fail, alerting the admin.Find what is wrong in each snippet:
Bug 1:
/* SIGCHLD handler */
static void grimReaper(int sig) {
waitpid(-1, NULL, WNOHANG); /* reap one child */
}
waitpid() once. If multiple children exit simultaneously, only one is reaped. The others become permanent zombies. Fix: Put it in a while loop.Bug 2:
/* Server SIGCHLD handler */
static void grimReaper(int sig) {
while (waitpid(-1, NULL, WNOHANG) > 0)
continue;
/* errno not saved/restored */
}
savedErrno = errno before the loop and errno = savedErrno after. waitpid() modifies errno — the interrupted main-loop code may see wrong error values.Bug 3:
/* Client sending request */
strncpy(req.pathname, argv[1], sizeof(req.pathname));
msgsnd(serverId, &req, REQ_MSG_SIZE, 0);
strncpy copies up to n bytes. If argv[1] is exactly sizeof(pathname) characters long, no null terminator is written. Fix: copy sizeof - 1 bytes and manually set req.pathname[sizeof-1] = '\0'.Chapter 46 Tutorials
Part 1: Overview Part 2: Concurrent Server Part 3: serveRequest() Part 4: Client Design Home
function toggle(el) {
var answer = el.nextElementSibling;
var icon = el.querySelector(‘.ep-toggle-icon’);
if (answer.classList.contains(‘visible’)) {
answer.classList.remove(‘visible’);
icon.classList.remove(‘open’);
} else {
answer.classList.add(‘visible’);
icon.classList.add(‘open’);
}
}
