There are two types of locking mechanisms: mandatory and advisory. Mandatory systems will actually prevent read()s and write()s to file. Several Unix systems support them. Nevertheless, I'm going to ignore them throughout this document, preferring instead to talk solely about advisory locks. With an advisory lock system, processes can still read and write from a file while it's locked. Useless? Not quite, since there is a way for a process to check for the existence of a lock before a read or write. See, it's a kind of cooperative locking system. This is easily sufficient for almost all cases where file locking is necessary.
Since that's out of the way, whenever I refer to a lock from now on in this document, I'm referring to advisory locks. So there.
Now, let me break down the concept of a lock a little bit more. There are two types of (advisory!) locks: read locks and write locks (also referred to as shared locks and exclusive locks, respectively.) The way read locks work is that they don't interfere with other read locks. For instance, multiple processes can have a file locked for reading at the same. However, when a process has an write lock on a file, no other process can activate either a read or write lock until it is relinquished. One easy way to think of this is that there can be multiple readers simultaneously, but there can only be one writer at a time.
One last thing before beginning: there are many ways to lock files in Unix systems. System V likes lockf(), which, personally, I think sucks. Better systems support flock() which offers better control over the lock, but still lacks in certain ways. For portability and for completeness, I'll be talking about how to lock files using fcntl(). I encourage you, though, to use one of the higher-level flock()-style functions if it suits your needs, but I want to portably demonstrate the full range of power you have at your fingertips. (If your System V Unix doesn't support the POSIX-y fcntl(), you'll have to reconcile the following information with your lockf() man page.)
struct flock fl; int fd; fl.l_type = F_WRLCK; /* F_RDLCK, F_WRLCK, F_UNLCK */ fl.l_whence = SEEK_SET; /* SEEK_SET, SEEK_CUR, SEEK_END */ fl.l_start = 0; /* Offset from l_whence */ fl.l_len = 0; /* length, 0 = to EOF */ fl.l_pid = getpid(); /* our PID */ fd = open("filename", O_WRONLY); fcntl(fd, F_SETLKW, &fl); /* F_GETLK, F_SETLK, F_SETLKW */What just happened? Let's start with the
In our example, we told it make a lock of type F_WRLCK (a write lock), starting relative to SEEK_SET (the beginning of the file), offset 0, length 0 (a zero value means "lock to end-of-file), with the PID set to getpid().
The next step is to open() the file, since flock() needs a file descriptor of the file that's being locked. Note that when you open the file, you need to open it in the same mode as you have specified in the lock, as shown in Table 1. If you open the file in the wrong mode for a given lock type, open() will return EBADF.
l_type | mode |
---|---|
F_RDLCK | O_RDONLY or O_RDWR |
F_WRLCK | O_WRONLY or O_RDWR |
Finally, the call to fcntl() actually sets, clears, or gets the
lock. See, the second argument (the cmd) to fcntl()
tells it what to do with the data passed to it in the
struct flock fl; int fd; fl.l_type = F_WRLCK; /* F_RDLCK, F_WRLCK, F_UNLCK */ fl.l_whence = SEEK_SET; /* SEEK_SET, SEEK_CUR, SEEK_END */ fl.l_start = 0; /* Offset from l_whence */ fl.l_len = 0; /* length, 0 = to EOF */ fl.l_pid = getpid(); /* our PID */ fd = open("filename", O_WRONLY); /* get the file descriptor */ fcntl(fd, F_SETLKW, &fl); /* set the lock, waiting if necessary */ . . . fl.l_type = F_UNLCK; /* tell it to unlock the region */ fcntl(fd, F_SETLK, &fl); /* set the region to unlocked */Now, I left the old locking code in there for high contrast, but you can tell that I just changed the l_type field to F_UNLCK (leaving the others completely unchanged!) and called fcntl() with F_SETLK as the command. Easy!
Basically, usage is this: if you run lockdemo with no command line arguments, it tries to grab a write lock (F_WRLCK) on its source (lockdemo.c). If you start it with any command line arguments at all, it tries to get a read lock (F_RDLCK) on it.
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { /* l_type l_whence l_start l_len l_pid */ struct flock fl = { F_WRLCK, SEEK_SET, 0, 0, 0 }; int fd; fl.l_pid = getpid(); if (argc > 1) fl.l_type = F_RDLCK; if ((fd = open("lockdemo.c", O_RDWR)) == -1) { perror("open"); exit(1); } printf("Press <RETURN> to try to get lock: "); getchar(); printf("Trying to get lock..."); if (fcntl(fd, F_SETLKW, &fl) == -1) { perror("fcntl"); exit(1); } printf("got lock\n"); printf("Press <RETURN> to release lock: "); getchar(); fl.l_type = F_UNLCK; /* set to unlock same region */ if (fcntl(fd, F_SETLK, &fl) == -1) { perror("fcntl"); exit(1); } printf("Unlocked.\n"); close(fd); }Compile that puppy up and start messing with it in a couple windows. Notice that when one lockdemo has a read lock, other instances of the program can get their own read locks with no problem. It's only when a write lock is obtained that other processes can't get a lock of any kind.
Another thing to notice is that you can't get a write lock if there are any read locks on the same region of the file. The process waiting to get the write lock will wait until all the read locks are cleared. One upshot of this is that you can keep piling on read locks (because a read lock doesn't stop other processes from getting read locks) and any processes waiting for a write lock will sit there and starve. There isn't a rule anywhere that keeps you from adding more read locks if there is a process waiting for a write lock. You must be careful.
Practically, though, you will probably mostly be using write locks to guarantee exclusive access to a file for a short amount of time while it's being updated; that is the most common use of locks as far as I've seen. And I've seen them all...well, I've seen one...a small one...a picture--well, I've heard about them.
Copyright © 1997 by Brian "Beej" Hall. This guide may be reprinted in any medium provided that its content is not altered, it is presented in its entirety, and this copyright notice remains intact. Contact beej@ecst.csuchico.edu for more information.