Skip to content

Instantly share code, notes, and snippets.

@gsauthof
Created October 25, 2019 17:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gsauthof/6eb6c648e483005191c37f86e759906e to your computer and use it in GitHub Desktop.
Save gsauthof/6eb6c648e483005191c37f86e759906e to your computer and use it in GitHub Desktop.
Fix deadlock, busy waiting and comments in futex example from http://man7.org/linux/man-pages/man2/futex.2.html
/* futex_demo.c
Usage: futex_demo [nloops]
(Default: 5)
Demonstrate the use of futexes in a program where parent and child
use a pair of futexes located inside a shared anonymous mapping to
synchronize access to a shared resource: the terminal. The two
processes each write 'num-loops' messages to the terminal and employ
a synchronization protocol that ensures that they alternate in
writing messages.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
} while (0)
static int *futex1, *futex2, *iaddr;
static int
futex(int *uaddr, int futex_op, int val,
const struct timespec *timeout, int *uaddr2, int val3)
{
return syscall(SYS_futex, uaddr, futex_op, val,
timeout, uaddr, val3);
}
/* Acquire the futex pointed to by 'futexp': wait for its value to
become 0, and then set the value to 1. */
static void
fwait(int *futexp)
{
int s;
/* atomic_compare_exchange_strong atomically performs
the equivalent of:
bool cmpexch(int *val, int *exp, int newval)
{
if (*val == *exp) {
*val = newval;
return true;
} else {
*exp = *val;
return false;
}
}
*/
while (1) {
/* Is the futex available? */
int expected = 0;
if (atomic_compare_exchange_strong(futexp, &expected, 1))
break;
/* Futex is not available; wait */
s = futex(futexp, FUTEX_WAIT, 1, NULL, NULL, 0);
if (s == -1 && errno != EAGAIN)
errExit("futex-FUTEX_WAIT");
}
}
/* Release the futex pointed to by 'futexp': if the futex currently
has the value 1, set its value to 0 and the wake any futex waiters,
so that if the peer is blocked in fpost(), it can proceed. */
static void
fpost(int *futexp)
{
int s;
/* atomic_compare_exchange_strong() was described in comments above */
int expected = 1;
if (atomic_compare_exchange_strong(futexp, &expected, 0)) {
s = futex(futexp, FUTEX_WAKE, 1, NULL, NULL, 0);
if (s == -1)
errExit("futex-FUTEX_WAKE");
}
}
int
main(int argc, char *argv[])
{
pid_t childPid;
int j, nloops;
setbuf(stdout, NULL);
nloops = (argc > 1) ? atoi(argv[1]) : 5;
/* Create a shared anonymous mapping that will hold the futexes.
Since the futexes are being shared between processes, we
subsequently use the "shared" futex operations (i.e., not the
ones suffixed "_PRIVATE") */
iaddr = mmap(NULL, sizeof(int) * 2, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (iaddr == MAP_FAILED)
errExit("mmap");
futex1 = &iaddr[0];
futex2 = &iaddr[1];
*futex1 = 1; /* State: unavailable */
*futex2 = 0; /* State: available */
/* Create a child process that inherits the shared anonymous
mapping */
childPid = fork();
if (childPid == -1)
errExit("fork");
if (childPid == 0) { /* Child */
for (j = 0; j < nloops; j++) {
fwait(futex1);
printf("Child (%ld) %d\n", (long) getpid(), j);
fpost(futex2);
}
exit(EXIT_SUCCESS);
}
/* Parent falls through to here */
for (j = 0; j < nloops; j++) {
fwait(futex2);
printf("Parent (%ld) %d\n", (long) getpid(), j);
fpost(futex1);
}
wait(NULL);
exit(EXIT_SUCCESS);
}
@gsauthof
Copy link
Author

See also the upstream git repository for the futex(2) man page:
https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/log/man2/futex.2

@gsauthof
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment