7

I have different processes concurrently accessing a named pipe in Linux and I want to make this access mutually exclusive.

I know is possible to achieve that using a mutex placed in a shared memory area, but being this a sort of homework assignment I have some restrictions.

Thus, what I thought about is to use locking primitives on files to achieve mutual exclusion; I made some try but I can't make it work.

This is what i tried:

flock(lock_file, LOCK_EX)

// critic section

flock(lock_file, LOCK_UN)

Different projects will use different file descriptors but referring to the same file. Is it possible to achieve something like that? Can you provide some example.

Simone
  • 2,261
  • 2
  • 19
  • 27
  • I can't use a mutex! I don't want to complicate things, but this must be done this way – Simone Sep 06 '11 at 19:16
  • 3
    Maybe you should explain why you want to use a file instead of a real mutex... – Macmade Sep 06 '11 at 19:19
  • because different processes will access the same pipes using a static library, and the static library is a project requirement – Simone Sep 06 '11 at 19:24
  • I don't see how that stops you from linking something like pthreads and using its mutex implementation, though. – David Z Sep 06 '11 at 19:34
  • i don't know...i'm worried about the fact that this project will have to run on a teacher computer and it cannot ask for super user privileges – Simone Sep 06 '11 at 19:37
  • 1
    What sort of mutex are people proposing should be used? This is a multi-process system so pthread mutexes are not an option; they are for a single multi-threaded process. – Jonathan Leffler Sep 06 '11 at 20:09
  • @Jonathan I don't know about pthread or *nix systems, but on Windows the mutex is cross-process. There must be something similar on *nix systems. – David Heffernan Sep 06 '11 at 20:32
  • 1
    @Simone you don't need super user rights to use a mutex! – David Heffernan Sep 06 '11 at 20:32
  • but i need super user rights to link to a shared library right? and moreover, if i link a dynamic library with a static library i won't have a static but a dynamic library i think – Simone Sep 06 '11 at 20:34
  • you don't need super user rights to link to library – David Heffernan Sep 06 '11 at 21:24

3 Answers3

6

The standard lock-file technique uses options such as O_EXCL on the open() call to try and create the file. You store the PID of the process using the lock, so you can determine whether the process still exists (using kill() to test). You have to worry about concurrency - a lot.

Steps:

  • Determine name of lock file based on name of FIFO
  • Open lock file if it exists
  • Check whether process using it exists
    • If other process exists, it has control (exit with error, or wait for it to exit)
    • If other process is absent, remove lock file
  • At this point, lock file did not exist when last checked.
  • Try to create it with open() and O_EXCL amongst the other options.
  • If that works, your process created the file - you have permission to go ahead.
  • Write your PID to the file; close it.
  • Open the FIFO - use it.
  • When done (atexit()?) remove the lock file.

Worry about what happens if you open the lock file and read no PID...is it that another process just created it and hasn't yet written its PID into it, or did it die before doing so? Probably best to back off - close the file and try again (possibly after a randomized nanosleep()). If you get the empty file multiple times (say 3 in a row) assume that the process is dead and remove the lock file.

You could consider having the process that owns the file maintain an advisory lock on the file while it has the FIFO open. If the lock is absent, the process has died. There is still a TOCTOU (time of check, time of use) window of vulnerability between opening the file and applying the lock.

Take a good look at the open() man page on your system to see whether there are any other options to help you. Sometimes, processes use directories (mkdir()) instead of files because even root can't create a second instance of a given directory name, but then you have issues with how to know the PID of the process with the resource open, etc.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • another way? i also thought about the use of a third fifo, since a fifo block the readers when is empty..it's possible? – Simone Sep 06 '11 at 19:30
  • I wouldn't go so far as to say it is impossible, but it doesn't feel right using a FIFO to obtain exclusive access to another FIFO, and I'm not sure how it would work, anyway. But I've not spent long thinking about how it might work. – Jonathan Leffler Sep 06 '11 at 19:58
  • This method is bugprone: PIDs are not guaranteed to be unique and can be reused. I don't see why there would be a TUCTOU between opening the file and locking it. Why not just use `flock`, which is released automatically on program exit? – Vladimir Panteleev Dec 27 '13 at 00:27
  • 1
    @CyberShadow: I didn't claim it was free from all possible flaws; I just said it was the standard technique. Unix has run for years using minor variants of this technique, and mostly survived. There's a TOCTOU because there are multiple system calls; only atomic system calls for creating files don't have TOCTOU problems. You can use `flock()` if you wish; it isn't the standard technique, in part because it was a late arrival on the scene (somewhere in the 80s, I believe). _[...Continued...]_ – Jonathan Leffler Dec 27 '13 at 01:32
  • _[...Continuation...]_ A possible advantage of the lock file is that if it is present but the process isn't, you can assume it crashed and you can take extra precautions to ensure the protected file is sane. OTOH, it might well be better just to do that validation anyway. – Jonathan Leffler Dec 27 '13 at 01:33
  • Re TOCTOU: the lock acts as a mutex anyway, making it irrelevant how much time passes between opening the file and locking it. This is the case with `flock`. Criticism is not personal, but we are not in the 70's and there are / should be better methods now. – Vladimir Panteleev Dec 27 '13 at 02:03
6

I'd definitely recommend using an actual mutex (as has been suggested in the comments); for example, the pthread library provides an implementation. But if you want to do it yourself using a file for educational purposes, I'd suggest taking a look at this answer I posted a while ago which describes a method for doing so in Python. Translated to C, it should look something like this (Warning: untested code, use at your own risk; also my C is rusty):

// each instance of the process should have a different filename here
char* process_lockfile = "/path/to/hostname.pid.lock";
// all processes should have the same filename here
char* global_lockfile = "/path/to/lockfile";
// create the file if necessary (only once, at the beginning of each process)
FILE* f = fopen(process_lockfile, "w");
fprintf(f, "\n"); // or maybe write the hostname and pid
fclose(f);

// now, each time you have to lock the file:
int lock_acquired = 0;
while (!lock_acquired) {
    int r = link(process_lockfile, global_lockfile);
    if (r == 0) {
        lock_acquired = 1;
    }
    else {
        struct stat buf;
        stat(process_lockfile, &buf);
        lock_acquired = (buf.st_nlink == 2);
    }
}
// do your writing
unlink(global_lockfile);
lock_acquired = 0;
Community
  • 1
  • 1
David Z
  • 128,184
  • 27
  • 255
  • 279
  • Nice, but the busy wait should be avoided. On BSD/Darwin one can use `kqueue` for that, with `EVFILT_VNODE` and `EVFILT_PROC`. Some method probably exists on Linux as well. – Per Johansson Sep 06 '11 at 20:09
  • The main problem with the `link` method is that the lock will remain if the program terminates in an abnormal way (bug/crash/signal/system shutdown). `flock` does not have this problem. – Vladimir Panteleev Dec 27 '13 at 00:20
4

Your example is as good as you're going to get using flock (2) (which is after all, merely an "advisory" lock (which is to say not a lock at all, really)). The man page for it on my Mac OS X system has a couple of possibly important provisos:

Locks are on files, not file descriptors. That is, file descriptors duplicated through dup(2) or fork(2) do not result in multiple instances of a lock, but rather multiple references to a single lock. If a process holding a lock on a file forks and the child explicitly unlocks the file, the parent will lose its lock

and

Processes blocked awaiting a lock may be awakened by signals.

both of which suggest ways it could fail.


// would have been a comment, but I wanted to quote the man page at some length

dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
  • Which means it is very important to verify the return value of `flock()` to know that the lock actually took place (whereas a mutex always succeeds unless it blocks your process forever). – Alexis Wilke Nov 07 '19 at 20:08