What you will learn in this file
Sometimes you want to attempt a semaphore operation but give up if it cannot complete within a certain time — avoiding indefinite blocking. Linux provides semtimedop() for exactly this purpose. This tutorial explains its behavior, the timespec structure, how it compares to the older setitimer() + semop() trick, and gives practical code examples.
Key Terms
#define _GNU_SOURCE /* Required — this is Linux-specific */
#include <sys/types.h> /* For portability */
#include <sys/sem.h>
int semtimedop(int semid, struct sembuf *sops, unsigned int nsops,
struct timespec *timeout);
/* Returns 0 on success, -1 on error */
| Parameter | Meaning |
|---|---|
semid |
Semaphore set ID (same as semop) |
sops |
Array of sembuf operations (same as semop) |
nsops |
Number of operations (same as semop) |
timeout |
Maximum time to wait. Pass NULL to behave identically to semop(). Pass a zero-valued timespec for immediate fail (like IPC_NOWAIT). |
The first three arguments are identical to semop(). The only difference is the fourth argument: a pointer to a maximum wait duration.
struct timespec {
time_t tv_sec; /* Seconds (whole seconds part) */
long tv_nsec; /* Nanoseconds (0 to 999,999,999) */
};
The timeout is a relative duration, not an absolute clock time. The kernel starts counting from the moment semtimedop() is called. If the semaphore becomes available within this duration, the call succeeds. If the duration expires first, the call fails with errno == EAGAIN.
| Desired timeout | tv_sec | tv_nsec |
|---|---|---|
| 5 seconds | 5 | 0 |
| 500 milliseconds | 0 | 500,000,000 |
| 100 microseconds | 0 | 100,000 |
| 1 nanosecond (minimum) | 0 | 1 |
| Immediate fail (no wait) | 0 | 0 |
| Block forever (like semop) | Pass NULL as timeout pointer | |
Note: The timeout value is a duration, not a deadline. Every call to semtimedop() gets a fresh countdown from the current time. If you want “retry until wallclock time X”, you must compute the remaining duration before each call.
Before semtimedop() existed, programmers implemented timeouts using setitimer() to deliver SIGALRM after a delay, which would interrupt semop() with EINTR. This approach has several problems:
| Issue | setitimer() + semop() | semtimedop() |
|---|---|---|
| Thread safety | SIGALRM is process-wide — affects all threads | Timeout is per-call, fully thread-safe |
| Complexity | Requires signal handler + careful handler code | Single function call |
| Interference | May interfere with other uses of SIGALRM | No signal interference |
| Performance | Signal delivery overhead on every timeout | Kernel handles entirely in-kernel (faster) |
| Portability | Available on all POSIX systems | Linux-only (not in SUSv3) |
The performance advantage matters particularly for database systems that perform many semaphore operations per second. Avoiding signal delivery on each timeout saves measurable CPU time at scale.
/* svsem_timed.c
* Demonstrates semtimedop() — try to acquire a semaphore
* but give up after 5 seconds if it remains locked.
* Compile: gcc -D_GNU_SOURCE svsem_timed.c -o svsem_timed
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/wait.h>
#include <time.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main(void)
{
int semid;
union semun arg;
semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); }
/* Initialize to 0 — semaphore is locked (no resource) */
arg.val = 0;
semctl(semid, 0, SETVAL, arg);
pid_t pid = fork();
if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); }
if (pid == 0) {
/* Child: holds the lock for 8 seconds, then releases */
printf("Lock-holder: sleeping for 8 seconds before release...\n");
sleep(8);
struct sembuf release = {0, 1, 0};
semop(semid, &release, 1);
printf("Lock-holder: released!\n");
exit(0);
}
/* Parent: try to acquire with 5-second timeout */
struct sembuf acquire = {0, -1, 0};
struct timespec timeout;
timeout.tv_sec = 5; /* Wait up to 5 seconds */
timeout.tv_nsec = 0;
printf("Waiter: trying to acquire (timeout = 5s)...\n");
int ret = semtimedop(semid, &acquire, 1, &timeout);
if (ret == 0) {
printf("Waiter: acquired successfully!\n");
/* Use the resource ... */
struct sembuf rel = {0, 1, 0};
semop(semid, &rel, 1);
} else {
if (errno == EAGAIN) {
printf("Waiter: TIMED OUT after 5 seconds — lock still held\n");
} else if (errno == EINTR) {
printf("Waiter: interrupted by signal\n");
} else {
perror("semtimedop");
}
}
wait(NULL);
semctl(semid, 0, IPC_RMID);
return 0;
}
/* Expected output:
* Lock-holder: sleeping for 8 seconds before release...
* Waiter: trying to acquire (timeout = 5s)...
* Waiter: TIMED OUT after 5 seconds — lock still held
* Lock-holder: released!
*/
Since semtimedop() takes a relative duration, you must recompute the remaining time before each retry if you want a fixed wall-clock deadline:
/* svsem_deadline.c
* Acquires semaphore with retry, giving up at an absolute deadline.
* Uses clock_gettime() to compute remaining time.
* Compile: gcc -D_GNU_SOURCE svsem_deadline.c -o svsem_deadline -lrt
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <time.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
/* Acquire semaphore within deadline_secs seconds from now.
* Returns 0 on success, -1 on timeout or error. */
int sem_acquire_deadline(int semid, int deadline_secs)
{
struct timespec deadline, now, remaining;
struct sembuf acquire = {0, -1, 0};
int ret;
/* Compute absolute deadline */
clock_gettime(CLOCK_REALTIME, &deadline);
deadline.tv_sec += deadline_secs;
while (1) {
/* Compute remaining time */
clock_gettime(CLOCK_REALTIME, &now);
remaining.tv_sec = deadline.tv_sec - now.tv_sec;
remaining.tv_nsec = deadline.tv_nsec - now.tv_nsec;
if (remaining.tv_nsec < 0) {
remaining.tv_sec--;
remaining.tv_nsec += 1000000000L;
}
/* Deadline already passed */
if (remaining.tv_sec < 0)
return -1;
ret = semtimedop(semid, &acquire, 1, &remaining);
if (ret == 0)
return 0; /* Success */
if (errno == EAGAIN)
return -1; /* Timed out */
if (errno == EINTR)
continue; /* Signal interrupted — retry with updated remaining */
return -1; /* Real error */
}
}
int main(void)
{
int semid;
union semun arg;
semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); }
arg.val = 0; /* Start locked */
semctl(semid, 0, SETVAL, arg);
printf("Trying to acquire within 3-second deadline...\n");
/* Resource will remain locked — we expect timeout */
if (sem_acquire_deadline(semid, 3) == 0) {
printf("Acquired!\n");
struct sembuf rel = {0, 1, 0};
semop(semid, &rel, 1);
} else {
printf("Could not acquire within deadline — giving up\n");
}
semctl(semid, 0, IPC_RMID);
return 0;
}
For tighter timing (e.g., in database or real-time systems), use sub-second precision:
/* Helper: build a timespec from milliseconds */
#define _GNU_SOURCE
#include <sys/sem.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
struct timespec ms_to_timespec(long milliseconds)
{
struct timespec ts;
ts.tv_sec = milliseconds / 1000;
ts.tv_nsec = (milliseconds % 1000) * 1000000L;
return ts;
}
/* Try to acquire semaphore, waiting up to ms milliseconds */
int sem_try_acquire_ms(int semid, long ms)
{
struct sembuf acquire = {0, -1, 0};
struct timespec timeout = ms_to_timespec(ms);
int ret = semtimedop(semid, &acquire, 1, &timeout);
if (ret == 0) return 1; /* Got it */
if (errno == EAGAIN) return 0; /* Timed out */
return -1; /* Error */
}
int main(void)
{
union semun { int val; struct semid_ds *buf; unsigned short *array; } arg;
int semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); }
arg.val = 0;
semctl(semid, 0, SETVAL, arg);
/* Try with 100ms timeout */
printf("Trying 100ms timeout...\n");
int r = sem_try_acquire_ms(semid, 100);
if (r == 1) printf("Acquired!\n");
else if (r == 0) printf("Timed out after 100ms\n");
else perror("semtimedop error");
/* Try with 500ms timeout */
printf("Trying 500ms timeout...\n");
r = sem_try_acquire_ms(semid, 500);
if (r == 1) printf("Acquired!\n");
else if (r == 0) printf("Timed out after 500ms\n");
else perror("semtimedop error");
semctl(semid, 0, IPC_RMID);
return 0;
}
| Detail | Value |
|---|---|
| First appeared in | Linux 2.6 (also back-ported to Linux 2.4 starting with 2.4.22) |
| In SUSv3? | No — Linux-specific extension. Not available on macOS, AIX, or Solaris. |
| Compile flag required | -D_GNU_SOURCE (or #define _GNU_SOURCE before includes) |
| Check for availability | #ifdef __linux__ guard if writing portable code |
| Error if expired | errno == EAGAIN (same as IPC_NOWAIT) |
For embedded Linux work (Thundersoft, Qualcomm platforms): Since you are targeting modern Linux kernels, semtimedop() is always available. The _GNU_SOURCE define is typically already set in embedded Linux BSPs.
A common embedded pattern: a watchdog checks if a worker has completed within a deadline. The worker signals completion via semaphore; the watchdog uses semtimedop() with a timeout:
/* svsem_watchdog.c
* Watchdog process waits for a worker to signal done.
* If worker takes too long, watchdog logs a fault.
* Compile: gcc -D_GNU_SOURCE svsem_watchdog.c -o svsem_watchdog
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/wait.h>
#include <time.h>
union semun { int val; struct semid_ds *buf; unsigned short *array; };
int main(void)
{
int semid;
union semun arg;
semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); }
arg.val = 0; /* Worker has NOT completed yet */
semctl(semid, 0, SETVAL, arg);
pid_t worker_pid = fork();
if (worker_pid == 0) {
/* --- WORKER: simulate doing work (may take variable time) --- */
int work_time = 3; /* Change to 6 to simulate a late/stuck worker */
printf("Worker [%d]: starting work (will take %ds)\n",
getpid(), work_time);
sleep(work_time);
/* Signal completion */
struct sembuf done_sig = {0, 1, 0};
semop(semid, &done_sig, 1);
printf("Worker [%d]: signaled DONE\n", getpid());
exit(0);
}
/* --- WATCHDOG: wait up to 5 seconds for worker to finish --- */
struct sembuf wait_done = {0, -1, 0};
struct timespec timeout = {5, 0}; /* 5-second watchdog timeout */
printf("Watchdog: waiting for worker (max 5s)...\n");
int ret = semtimedop(semid, &wait_done, 1, &timeout);
if (ret == 0) {
printf("Watchdog: worker completed on time! System healthy.\n");
} else if (errno == EAGAIN) {
printf("Watchdog: FAULT! Worker did not complete within 5 seconds!\n");
printf("Watchdog: taking recovery action...\n");
/* In real system: kill worker, restart subsystem, log fault, etc. */
} else {
perror("semtimedop");
}
wait(NULL);
semctl(semid, 0, IPC_RMID);
return 0;
}
semtimedop() is a Linux-specific variant of semop() that accepts a fourth argument — a pointer to a struct timespec specifying a maximum wait duration. If the semaphore operation cannot complete within this duration, the call fails with errno == EAGAIN. When NULL is passed as the timeout, it behaves identically to semop().
EAGAIN — the same error returned when semop() fails due to IPC_NOWAIT. This makes it consistent: both “won’t wait at all” (IPC_NOWAIT) and “waited too long” (semtimedop timeout) report the same error, meaning “resource not available right now.”
Relative — it specifies a duration counted from when semtimedop() is called, not a wall-clock timestamp. If you need an absolute deadline, you must compute the remaining time using clock_gettime() before each call. This is different from, say, pthread_cond_timedwait() which takes an absolute time.
The setitimer() approach delivers SIGALRM which is process-wide — it affects all threads, can interfere with other signal handling, and requires a signal handler. semtimedop() is entirely in-kernel, per-call, and thread-safe. It is also more efficient because no signal needs to be delivered; the kernel simply wakes the blocking syscall after the timeout expires.
You must define _GNU_SOURCE before including any headers, either by adding #define _GNU_SOURCE at the top of your source file or by compiling with -D_GNU_SOURCE on the command line. Without this, the prototype for semtimedop() is not visible and the compiler produces a warning or error.
No. When semtimedop() returns EINTR, the timeout clock does not reset. If you want to retry after a signal, you must recompute the remaining time (by subtracting elapsed time from the original timeout) and call semtimedop() again with the reduced timeout. Otherwise, repeated signal delivery could cause you to wait much longer than intended.
tv_nsec must be in the range 0 to 999,999,999 (inclusive). If you set it to 1,000,000,000 or more, semtimedop() will fail with errno == EINVAL. Always compute nanoseconds carefully: 1 millisecond = 1,000,000 nanoseconds; 1 microsecond = 1,000 nanoseconds.
A zero-value timeout means “do not wait at all” — semantically equivalent to passing IPC_NOWAIT on all operations. If the semaphore operation cannot be performed immediately, semtimedop() returns -1 with errno == EAGAIN without blocking.
| Topic | Key Function | File |
|---|---|---|
| Safe init (race condition fix) | semget + semctl + semop(no-op) |
Part 1 |
| Semaphore operations | semop(semid, sops, nsops) |
Part 2 |
| Timed operations | semtimedop(semid, sops, nsops, timeout) |
Part 3 |
You now understand System V Semaphore initialization, operations, and timed operations.
