An important issue in any computer running multiple
processes is coordination and synchronization of access to shared
objects, such as files.
Windows can lock files, in
whole or in part, so that no other process (running program) or thread
within the process can access the locked file region. File locks can be
read-only (shared) or read-write (exclusive). Most importantly, the
locks belong to the process. Any attempt to access part of a file (using
ReadFile or WriteFile) in
violation of an existing lock will fail because the locks are mandatory
at the process level. Any attempt to obtain a conflicting lock will also
fail even if the process already owns the lock.
The most general function is LockFileEx, and there is a less general function, LockFile.
LockFileEx is a member of the extended I/O class of functions, and the overlapped structure, used earlier to specify file position to ReadFile and WriteFile, is necessary to specify the 64-bit file position and range of the file region to be locked.
BOOL LockFileEx ( HANDLE hFile, DWORD dwFlags, DWORD dwReserved, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh, LPOVERLAPPED lpOverlapped)
|
LockFileEx locks a byte range in an open file for either shared (multiple readers) or exclusive (one reader-writer) access.
Parameters
hFile is the handle of an open file. The handle must have at least GENERIC_READ.
dwFlags determines the lock mode and whether to wait for the lock to become available.
LOCKFILE_EXCLUSIVE_LOCK, if set, indicates a request for an exclusive, read-write lock. Otherwise, it requests a shared (read-only) lock.
LOCKFILE_FAIL_IMMEDIATELY, if set, specifies that the function should return immediately with FALSE if the lock cannot be acquired. Otherwise, the call blocks until the lock becomes available.
dwReserved must be 0. The two parameters with the length of the byte range are self-explanatory.
lpOverlapped points to an OVERLAPPED
data structure containing the start of the byte range. The overlapped
structure contains three data members that must be set (the others are
ignored); the first two determine the start location for the locked
region.
A file lock is removed using a corresponding UnlockFileEx call; all the same parameters are used except dwFlags.
BOOL UnlockFileEx ( HANDLE hFile, DWORD dwReserved, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh, LPOVERLAPPED lpOverlapped)
|
You should consider several factors when using file locks.
The unlock must use
exactly the same range as a preceding lock. It is not possible, for
example, to combine two previous lock ranges or unlock a portion of a
locked range. An attempt to unlock a region that does not correspond
exactly with an existing lock will fail; the function returns FALSE and the system error message indicates that the lock does not exist.
Locks cannot overlap existing locked regions in a file if a conflict would result.
It
is possible to lock beyond the range of a file’s length. This approach
could be useful when a process or thread extends a file.
Locks are not inherited by a newly created process.
The
lock and unlock calls require that you specify the lock range start and
size as separate 32-bit integers. There is no way to specify these
values directly with LARGE_INTEGER values as there is with SetFilePointerEx.
Table 1 shows the lock logic when all or
part of a range already has a lock. This logic applies even if the lock
is owned by the same process that is making the new request.
Table 1. Lock Request Logic
| Requested Lock Type |
---|
Existing Lock | Shared Lock | Exclusive Lock |
---|
None | Granted | Granted |
Shared lock (one or more) | Granted | Refused |
Exclusive lock | Refused | Refused |
Table 2
shows the logic when a process attempts a read or write operation on a
file region with one or more locks, owned by a separate process, on all
or part of the read-write region. A failed read or write may take the
form of a partially completed operation if only a portion of the read or
write record is locked.
Table 2. Locks and I/O Operation
| I/O Operation |
---|
Existing Lock | Read | Write |
---|
None | Succeeds | Succeeds |
Shared lock (one or more) | Succeeds. It is not necessary for the calling process to own a lock on the file region. | Fails |
Exclusive lock | Succeeds if the calling process owns the lock. Fails otherwise. | Succeeds if the calling process owns the lock. Fails otherwise. |
Read and write operations are normally in the form of ReadFile and WriteFile calls or their extended versions, ReadFileEx and WriteFileEx . Diagnosing a read or write failure requires calling GetLastError.
Accessing memory mapped to a file is another form of file I/O. Lock conflicts are not detected at the time of memory reference; rather, they are detected at the time that the MapViewOfFileEx function is called. This function makes a part of the file available to the process, so the lock must be checked at that time.
The LockFile function is a legacy, limited, special case and is a form of advisory locking. Only exclusive access is available, and LockFile returns immediately. That is, LockFile does not block. Test the return value to determine whether you obtained the lock.
Releasing File Locks
Every successful LockFileEx call must be followed by a single matching call to UnlockFileEx (the same is true for LockFile and UnlockFile).
If a program fails to release a lock or holds the lock longer than
necessary, other programs may not be able to proceed, or, at the very
least, their performance will be negatively impacted. Therefore,
programs should be carefully designed and implemented so that locks are
released as soon as possible, and logic that might cause the program to
skip the unlock should be avoided.
Lock Logic Consequences
Although the file lock logic in Tables 1 and 2 is natural, it has consequences that may be unexpected and cause unintended program defects. Here are some examples.
Suppose that
process A and process B periodically obtain shared locks on a file, and
process C blocks when attempting to gain an exclusive lock on the same
file after process A gets its shared lock. Process B may now gain its
shared lock even though C is still blocked, and C will remain blocked
even after A releases the lock. C will remain blocked until all
processes release their shared locks even if they obtained them after C
blocked. In this scenario, it is possible that C will be blocked forever
even though all the other processes manage their shared locks properly.
Assume
that process A has a shared lock on the file and that process B
attempts to read the file without obtaining a shared lock first. The
read will still succeed even though the reading process does not own any
lock on the file because the read operation does not conflict with the
existing shared lock.
These statements apply both to entire files and to file regions.
File locking can produce deadlocks in the same way as with mutual exclusion locks .
A
read or write may be able to complete a portion of its request before
encountering a conflicting lock. The read or write will return FALSE, and the byte transfer count will be less than the number requested.
Using File Locks
UNIX has advisory file locking; an attempt to obtain a lock may fail (the logic is the same as in Table 3-1),
but the process can still perform the I/O. Therefore, UNIX can achieve
locking between cooperating processes, but any other process can violate
the protocol.
To obtain an advisory lock, use options to the fcntl function. The commands (the second parameter) are F_SETLK, F_SETLKW (to wait), and F_GETLK. An additional block data structure contains a lock type that is one of F_RDLCK, F_WRLCK, or F_UNLCK and the range.
Mandatory locking is also available in some UNIX systems using a file’s set-group-ID and group-execute, both using chmod.
UNIX file locking behavior differs in many ways. For example, locks are inherited through an exec call.
The C library does not support locking, although Visual C++ does supply nonstandard locking extensions.