In older UNIX implementations that lacked file locking, a number of ad hoc locking techniques were employed. Although all of these have been superseded by fcntl() record locking, we describe them here since they still appear in some older programs. All of these techniques are advisory in nature.
SUSv3 requires that an open() call with the flags O_CREAT
and O_EXCL
perform the steps of checking for the existence of a file and creating it atomically (Atomicity and Race Conditions). This means that if two processes attempt to create a file specifying these flags, it is guaranteed that only one of them will succeed. (The other process will receive the error EEXIST
from open().) Used in conjunction with the unlink() system call, this provides the basis for a locking mechanism. Acquiring the lock is performed by successfully opening the file with the O_CREAT
and O_EXCL
flags, followed by an immediate close(). Releasing the lock is performed using unlink(). Although workable, this technique has several limitations:
If the open() fails, indicating that some other process has the lock, then we must retry the open() in some kind of loop, either polling continuously (which wastes CPU time) or with a delay between each attempt (which means that there may be some delay between the time the lock becomes available and when we actually acquire it). With fcntl(), we can use F_SETLKW
to block until the lock becomes free.
Acquiring and releasing locks using open() and unlink() involves file-system operations that are rather slower than the use of record locks. (On one of the author’s x86-32 systems running Linux 2.6.31, acquiring and releasing 1 million locks on an ext3 file using the technique described here required 44 seconds. Acquiring and releasing 1 million record locks on the same byte of a file required 2.5 seconds.)
If a process accidentally exits without deleting the lock file, the lock is not released. There are ad hoc techniques for handling this problem, including checking the last modification time of the file and having the lock holder write its process ID to the file so that we can check if the process still exists, but none of these techniques is foolproof. By comparison, record locks are released automatically when a process terminates.
If we are placing multiple locks (i.e., using multiple lock files), deadlocks are not detected. If a deadlock arises, the processes involved in the deadlock will remain blocked indefinitely. (Each process will be spinning, checking to see if it can obtain the lock it requires.) By contrast, the kernel provides deadlock detection for fcntl() record locks.
NFS version 2 doesn’t support O_EXCL
semantics. Linux 2.4 NFS clients also fail to implement O_EXCL
correctly, even for NFS version 3 and later.
The fact that the link() system call fails if the new link already exists has also been used as a locking mechanism, again employing unlink() to perform the unlock function. The usual approach is to have each process that needs to acquire the lock create a unique temporary filename, typically one including the process ID (and possibly the hostname, if the lock file is created on a network file system). To acquire the lock, this temporary file is linked to some agreed-upon standard pathname. (The semantics of hard links require that the two pathnames reside in the same file system.) If the link() call succeeds, we have obtained the lock. If it fails (EEXIST
), then another process has the lock and we must try again later. This technique suffers the same limitations as the open(file, O_CREAT | O_EXCL,...) technique described above.
The fact that calling open() on an existing file fails if O_TRUNC
is specified and write permission is denied on the file can be used as the basis of a locking technique. To obtain a lock, we use the following code (which omits error checking) to create a new file:
fd = open(file, O_CREAT | O_TRUNC | O_WRONLY, (mode_t) 0); close(fd);
For an explanation of why we use the (mode_t) cast in the open() call above, see Appendix C.
If the open() call succeeds (i.e., the file didn’t previously exist), we have the lock. If it fails with EACCES
(i.e., the file exists and has no permissions for anyone), then another process has the lock, and we must try again later. This technique suffers the same limitations as the previous techniques, with the added caveat that we can’t employ it in a program with superuser privileges, since the open() call will always succeed, regardless of the permissions that are set on the file.