Содержащиеся в
Найти другие документы
Ресурсы поддержки
| Загрузить это руководство в формате PDF
File and Record Locking
2
- Mandatory and advisory file and record locking both are available in the SunOS 5.x system. These provide a synchronization mechanism for programs simultaneously accessing the same stores of data. Such processing is characteristic of many multiuser applications.
- Use advisory file and record locking with processes that cooperate to achieve synchronization. In mandatory locking, the I/O functions enforce the locking protocol. In this way, at the cost of a little efficiency, mandatory locking double checks the programs against accessing the data out of sequence.
Supported File Systems
- This chapter describes file locking for local file systems. These include the following file system types.
-
-
ufs--the default disk-based file system
-
fifofs--a pseudo file system of named pipe files that give processes common access to data.
-
namefs--a pseudo file system used mostly by STREAMS for dynamic mounts of file descriptors on top of files.
-
specfs--a pseudo file system that provides access to special character and block devices.
- File locking is not supported for the proc and fd file systems. NFS supports advisory file locking only, and uses the Network Lock Manager and the Status Monitor to support remote requests for advisory file locking.
- The remainder of this chapter describes how you can use file and record locking. Examples show how to use record locking correctly. Misconceptions about the amount of protection that record locking affords are dispelled--programs should view record locking as a synchronization mechanism, not as a security mechanism.
- The manual pages for the fcntl(2) function, the lockf(3C) library function, and fcntl(5) data structures and commands are referred to throughout this section. Read them before continuing.
Choosing A Locking Type
- Mandatory locking forces processes to wait until file segments are free by suspending them. Advisory locking simply returns a result indicating whether the lock was obtained or not. Processes can ignore the result and go ahead and do the I/O anyway. Advisory locking is intended for use with "well-behaved" or cooperating processes that can be relied on to follow the advisory results.
- You can have both mandatory and advisory file locking on the same file at the same time. Rather, the mode of the file at the time of I/O access determines whether the existing locks on the file are treated as mandatory or advisory.
- Of the two basic locking calls, the lockf(3C) routine should be your default choice because library routines are generally safer for application programs than are system calls. fcntl is a kernel service and should be used if your software is striving for the last ounce of performance (though if this is the case, seek a program design that doesn't require file locking). lockf(2) is implemented by calling fcntl.
-
Note - Only advisory locking is supported on remote (NFS-accessed) file systems. Note that file locking can be fragile under stressful file processing conditions such as large numbers of locks.
Terminology
- Before discussing how to use record locking, here are some important definitions:
-
Record
- A contiguous set of bytes in a file. The UNIX operating system does not impose a record structure on files. This can be done by the programs that use the files.
-
Cooperating Processes
- Processes that work together in some well-defined fashion to accomplish the tasks at hand. Processes that share files must request permission to access the files before using them. File access permissions must be carefully set to restrict noncooperating processes from accessing those files. The term "process" is used interchangeably with "cooperating process" to refer to a task obeying such protocols.
-
Read (Share) Locks
- These are used to gain limited access to sections of files. When a read lock is in place on a record, other processes can also lock that record for reading, in whole or in part. No other process, however, can have or obtain a write lock on an overlapping section of the file. This access method also permits many processes to read the given record. This is useful when searching a file, to avoid the contention involved if a write or exclusive lock were to be used.
-
Write (Exclusive) Locks
- These are used to gain complete control over sections of files. When a write lock is in place on a record, no other process can read or write lock that record, in whole or in part. If a process holds a write lock it can assume that no other process will be reading or writing that record at the same time.
-
Advisory Locking
- A form of record locking that does not interact with the I/O subsystem. Advisory locking is not enforced, for example, by creat(2), open(2), read(2), or write(2). The control over records is accomplished by requiring an appropriate record lock request before I/O operations. If appropriate requests are always made by all processes accessing the file, then the accessibility of the file will be controlled by the interaction of these requests. Advisory locking depends on the individual processes to enforce the record locking protocol; it does not require an accessibility check at the time of each I/O request.
-
Mandatory Locking
- A form of record locking that does interact with the I/O subsystem. Access to locked records is enforced by the creat(2), open(2), read(2), and write(2) functions. If a record is locked, then accessing that record by any other process is restricted according to the type of lock on the record. The control over records should still be performed explicitly by requesting an appropriate record lock before I/O operations, but an additional check is made by the system before each I/O operation to ensure the record locking protocol is being honored. Mandatory locking offers an extra synchronization check, but at the cost of some additional system overhead.
Opening a File for Record Locking
- The first requirement for locking a file or segment of a file is having a valid open file descriptor. If read locks are used, then the file must be opened with at least read accessibility. For write locks, the file must be opened with write accessibility.
- In the example, a file is opened for both read and write access:
-
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
int fd; /* file descriptor */
char *filename;
main(int argc, char *argv[])
{
extern void exit(), perror();
/* get data base file name from command line and open the
* file for read and write access.
*/
if (argc != 2) {
(void) fprintf(stderr, "usage: %s filename\n", argv[0]);
exit(2);
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0) {
perror(filename);
exit(2);
}
.
.
.
|
- The file is now open for both locking and I/O functions. Proceed with the task of setting a lock.
-
Note - Mapped files cannot be locked: if a file has been mapped, any attempt to use file or record locking on the file fails. See mmap(2).
Setting a File Lock
- There are several ways to set a lock on a file. In part, these methods depend on how the lock interacts with the rest of the program. There are also questions of performance as well as portability. Two methods are given here, one using the POSIX standard-compatible fcntl(2) function, the other using the lockf library function call.
- Locking an entire file is just a special case of record locking. In both cases the effect of the lock is the same. The file is locked starting at a given byte offset and for a particular size. In the case of locking an entire file the offset is zero, and by convention the size is also set to zero.
- The code using the fcntl function is as follows:
-
#include <fcntl.h>
#define MAX_TRY 10
int try;
struct flock lck;
try = 0;
/* set up the record locking structure, the address of which
* is passed to the fcntl function.
*/
lck.l_type = F_WRLCK; /* setting a write lock */
lck.l_whence = 0; /* offset l_start from beginning of file */
lck.l_start = (off_t)0;
lck.l_len = (off_t)0; /* until the end of the file address space */
/* Attempt locking MAX_TRY times before giving up.
*/
while (fcntl(fd, F_SETLK, &lck) <0) {
if (errno == EAGAIN || errno == EACCES) {
/* There might be other error cases in which
* you might try again.
*/
if (++try < MAX_TRY) {
(void) sleep (2);
continue;
}
(void) fprintf(stderr, "File busy try again later!\n");
return;
}
perror("fcntl");
exit (2);
}
.
.
.
|
- This portion of code tries to lock a file. This is attempted several times until one of the following happens.
-
- The file is successfully locked, or
- An error occurs, or
-
-
MAX_TRY is exceeded, and the program gives up trying to lock the file
- To perform the same task using the lockf function, the code is as follows:
-
#include <unistd.h>
#define MAX_TRY 10
int try;
try = 0;
/* make sure the file pointer
* is at the beginning of the file.
*/
lseek(fd, (off_t)0, 0);
/* Attempt locking MAX_TRY times before giving up.
*/
while (lockf(fd, F_TLOCK, 0L) < 0) {
if (errno == EAGAIN || errno == EACCES) {
/* There might be other error cases in which
* you might try again.
*/
if (++try < MAX_TRY) {
sleep(2);
continue;
}
(void) fprintf(stderr,"File busy try again later!\n");
return;
}
perror("lockf");
exit(2);
}
.
.
.
|
- Note that the lockf(3C) example appears to be simpler, but the fcntl(2) example shows more flexibility. Using the fcntl(2) method, you can set the type and start of the lock request by setting a few structure variables. The lockf method sets only write (exclusive) locks; an additional function, lseek, is required to specify the start of the lock.
Setting and Removing Record Locks
- Locking a record is done the same way as locking a file except for the different starting point and length of the lock. Here is an interesting and real problem. Two records (in the same or different files) must be updated simultaneously so that other processes get a consistent view of this information. (This type of problem comes up, for example, when updating the inter-record pointers in a doubly linked list.)
- To update two records simultaneously, answer the following questions:
-
- What do you want to lock?
- For multiple locks, in what order do you want to lock and unlock the records?
- What do you do if you get all the required locks?
- What do you do if you do not get all the locks?
- In managing record locks, you must plan a failure strategy if you cannot obtain all the required locks. It is because of contention for these records that record locking is being used, so different programs might:
-
- Wait a certain amount of time, then try again
- Abort the procedure and warn the user
- Let the process sleep until signaled that the lock has been freed
- Do some combination of the above
- The next example demonstrates inserting an entry into a doubly linked list that is stored in a file of list element records. For the example, assume that the record after which the new record is to be inserted has a read lock on it already. The lock on this record must be changed or promoted to a write lock so that the record can be edited.
- Promoting a lock (generally from read lock to write lock) is permitted if no other process is holding a read lock in the same section of the file. When processes with pending write locks are sleeping on the same section of the file, the lock promotion succeeds and the other (sleeping) locks wait. Changing a write lock to a read lock carries no restrictions. In either case, the lock is merely reset with the new lock type. Because the lockf function does not have read locks, lock promotion does not apply to that call.
- An example of record locking with lock promotion follows:
-
struct record {
.
./* data portion of record */
.
off_t prev;/* index to previous record in the list */
off_t next;/* index to next record in the list */
};
/* Lock promotion using fcntl(2)
* When this routine is entered it is assumed that there are read
* locks on "here" and "next."
* If write locks on "here" and "next" are obtained;
* Set a write lock on "this."
* Return index to "this" record.
* If any write lock is not obtained;
* Restore read locks on "here" and "next."
* Remove all other locks.
* Return a -1.
*/
off_t
set3lock (this, here, next)
off_t this, here, next;
{
struct flock lck;
lck.l_type = F_WRLCK;/* setting a write lock */
lck.l_whence = 0;/* offset l_start from beginning of file */
lck.l_start = here;
lck.l_len = sizeof(struct record);
/* promote lock on "here" to write lock */
if (fcntl(fd, F_SETLKW, &lck) < 0) {
return (-1);
}
/* lock "this" with write lock */
lck.l_start = this;
if (fcntl(fd, F_SETLKW, &lck) < 0) {
/* Lock on "this" failed;
* demote lock on "here" to read lock.
*/
lck.l_type = F_RDLCK;
lck.l_start = here;
(void) fcntl(fd, F_SETLKW, &lck);
return (-1);
}
|
-
/* promote lock on "next" to write lock */
lck.l_start = next;
if (fcntl(fd, F_SETLKW, &lck) < 0) {
/* Lock on "next" failed;
* demote lock on "here" to read lock,
*/
lck.l_type = F_RDLCK;
lck.l_start = here;
(void) fcntl(fd, F_SETLK, &lck);
/* and remove lock on "this".
*/
lck.l_type = F_UNLCK;
lck.l_start = this;
(void) fcntl(fd, F_SETLK, &lck);
return (-1); /* cannot set lock, try again or quit */
}
return (this);
}
|
- The locks on these three records were all set to wait (sleep) if another process was blocking them from being set. This was done with the F_SETLKW command. If the F_SETLK command were used instead, the fcntl functions would fail if blocked. The program would then have to be changed to handle the blocked condition in each of the error-return sections.
- The next example shows the lockf function. Because there are no read locks, all write locks will be referred to generically as locks:
-
/* lockf(3C)
* When this routine is entered it is assumed that there are
* no locks on "here" and "next".
* If locks are obtained: set a lock on "this"; return index to "this" record.
* If any lock is not obtained: remove all other locks; return a -1.
*/
#include <unistd.h>
long
set3lock (this, here, next)
long this, here, next;
{
|
-
/* lock "here" */
(void) lseek(fd, here, 0);
if (lockf(fd, F_LOCK, sizeof(struct record)) < 0) {
return (-1);
}
/* lock "this" */
(void) lseek(fd, this, SEEK_SET);
if (lockf(fd, F_LOCK, sizeof(struct record)) < 0) {
/* Lock on "this" failed.
* Clear lock on "here".
*/
(void) lseek(fd, here, 0);
(void) lockf(fd, F_ULOCK, sizeof(struct record));
return (-1);
}
/* lock "next" */
(void) lseek(fd, next, 0);
if (lockf(fd, F_LOCK, sizeof(struct record)) < 0) {
/* Lock on "next" failed.
* Clear lock on "here",
*/
(void) lseek(fd, here, 0);
(void) lockf(fd, F_ULOCK, sizeof(struct record));
/* and remove lock on "this".
*/
(void) lseek(fd, this, 0);
(void) lockf(fd, F_ULOCK, sizeof(struct record));
return (-1);/* cannot set lock, try again or quit */
}
return (this);
}
|
- Locks are removed in the same manner as they are set--only the lock type is different (F_ULOCK). An unlock cannot be blocked by another process and will affect only locks that were placed by this process. The unlock affects only the section of the file defined in the previous example by lck.
- It is possible to unlock a section of a previously locked region, or (in the case of flock(1)) to change the type of the lock in a previously locked region. If this is done in the middle of a previously locked region it will cause the creation of an additional lock (in other words, the extant lock has been broken in two). This
- can cause an additional lock (two locks for one function) to be used by the operating system. This occurs if the subsection is from the middle of the previously set lock.
Getting Lock Information
- You can determine which processes, if any, are blocking a lock from being set. This can be used as a simple test or to find locks on a file. A lock is set up as in the previous examples and the F_GETLK command is used in the fcntl call.
- If the lock passed to fcntl would be blocked, the first blocking lock is returned to the process through the structure passed to fcntl. That is, the lock data passed to fcntl is overwritten by blocking lock information. This information includes two pieces of data that have not been discussed yet, l_pid and l_sysid, used only by F_GETLK. These fields uniquely identify the process holding the lock, and, when locking is over the network, the system.
- If a lock passed to fcntl using the F_GETLK command would not be blocked by another process's lock, then the l_type field is changed to F_UNLCK and the remaining fields in the structure are unaffected. Use this ability to print all the segments locked by other processes. If there are several read locks over the same segment, only one of these will be found.
-
struct flock lck;
/* Find and print "write lock" blocked segments of this file. */
(void) printf("sysid pid type start length\n");
lck.l_whence = 0;
lck.l_start = 0L;
lck.l_len = 0L;
do {
lck.l_type = F_WRLCK;
(void) fcntl(fd, F_GETLK, &lck);
if (lck.l_type != F_UNLCK) {
(void) printf("%d %d %c %8d %8d\n",
lck.l_sysid,
lck.l_pid,
(lck.l_type == F_WRLCK) ? 'W' : 'R',
lck.l_start,
lck.l_len);
/* if this lock goes to the end of the address
* space, no need to look further, so break out.
*/
if (lck.l_len == 0)
break;
/* otherwise, look for new lock after the one
* just found.
*/
lck.l_start += lck.l_len;
}
} while (lck.l_type != F_UNLCK);
|
- The fcntl function with the F_GETLK command can sleep while waiting for a server to respond, and it can fail (returning ENOLCK) if there is a resource shortage on either the client or server.
- The lockf function with the F_TEST command can also be used to test if a process is blocking a lock. This function does not, however, return the information about where the lock is and which process owns the lock.
- A routine using lockf to test for a lock on a file follows (please note that errno is printed as an integer in this example,. but using perror(3C) or strerror(3C) is better programming practice).
-
/* find a blocked record. */
/* seek to beginning of file */
(void) lseek(fd, 0, 0L);
/* set the size of the test region to zero (0)
* to test until the end of the file address space.
*/
if (lockf(fd, (off_t)0, SEEK_SET) < 0) {
switch (errno) {
case EACCES:
case EAGAIN:
(void) printf("file is locked by another process\n");
break;
case EBADF:
/* bad argument passed to lockf */
perror("lockf");
break;
default:
(void) printf("lockf: unexpected error <%d>\n",
errno);
break;
}
}
|
Forking Locks
- When a process forks, the child receives a copy of the file descriptors that the parent has opened. However, locks are not inherited by the child because the locks are owned by a specific process. The parent and child also share a common file pointer for each file. If the parent were to seek to a point in the file, the child's file pointer would also be at that location. This feature has important implications when using record locking.
- The current value of the file pointer is used as the reference for the offset of the beginning of the lock, as described by l_start, when using a l_whence value of 1. If both the parent and child process set locks on the same file, there is a possibility that a lock will be set using a file pointer that was reset by the
- other process. This problem appears in the lockf(3C) library routine as well as the fcntl(2) system call and is a result of the original /usr/group standards requirements for record locking.
- If a record locking program forks, the child process should close and reopen the file (regardless of the locking method). This will result in the creation of a new and separate file pointer that can be manipulated without this problem occurring. Another solution is to use the fcntl function with a l_whence value of 0 or 2. This makes the range of the lock absolute instead of relative to the pointer, so that even processes sharing file pointers can be locked without difficulty.
Deadlock Handling
- The UNIX locking facilities provide deadlock detection/avoidance.
- Deadlocks can potentially occur only when the system is about to put a record locking function to sleep. A search is made to determine whether a process is about to be put to sleep waiting for a lock that could never be granted because, directly or indirectly, the process the granting of the lock depends on is about to be suspended (for example, process A is waiting for a lock that B holds while B is waiting for a lock that A holds).
- If such a situation is detected, the locking function will fail and set errno to the deadlock error number. Processes setting locks using F_SETLK do not cause a deadlock because they are not suspended when the lock cannot be granted immediately.
Selecting Advisory or Mandatory Locking
- Mandatory locking is not recommended for reasons that will be made clear in "Cautions about Mandatory Locking" on page 44. Whether or not locks are enforced by the I/O functions is determined at the time the calls are made by the permissions on the file; see chmod(2).
- For locks to be under mandatory enforcement, the file must be a regular file with the set-group-ID bit on and the group execute permission off. If either condition fails, all record locks are advisory. Mandatory enforcement can be assured by the following code:
-
#include <sys/types.h>
#include <sys/stat.h>
int mode;
struct stat buf;
.
.
.
if (stat(filename, &buf) < 0) {
perror("program");
exit (2);
}
/* get currently set mode */
mode = buf.st_mode;
/* remove group execute permission from mode */
mode &= ~(S_IEXEC>>3);
/* set 'set group id bit' in mode */
mode |= S_ISGID;
if (chmod(filename, mode) < 0) {
perror("program");
exit(2);
}
.
.
.
|
- Files that are to be record locked should never have any type of execute permission set on them. This is because the operating system does not obey the record locking protocol when executing a file. In practice this is not a problem, as it would be a strange application that wanted to lock portions of a binary executable.
- The chmod(1) command can also be used to set a file to permit mandatory locking. This can be done with the command:
-
- (Note that this is letter "l" and not the number "1".) This command sets two permission bits in the file mode, which the system uses to understand that mandatory locking is enabled on this file. The two bits in the mode are .1./.../..0/...
- Therefore, an idividual file cannot simultaneously be enabled for mandatory locking and have the set-group-ID on execution bit set. Nor can an individual file be enabled for mandatory locking and for group execution. These sets of two attributes are mutually exclusive. In practice this is not a problem because file locking is used for data files, and set-group-ID is used for executable programs. Similarly, the bit is ignored on directory files.
- The ls(1) command shows this setting when you ask for the long listing format with the -l option:
-
- The following information is displayed:
-
-rw---l--- 1 user group size mod_time file
|
- The letter "l" in the permissions indicates that the set-group-ID bit is on, so mandatory locking is enabled, as well as the normal semantics of set group ID.
Cautions about Mandatory Locking
-
- Mandatory locking is available only for local files. It is not supported when accessing files over NFS.
- Mandatory locking protects only those portions of a file that are locked. Other portions of the file that are not locked can be accessed according to normal file permissions.
- If multiple reads or writes are necessary for an atomic transaction, the process should explicitly lock all such pieces before any I/O begins. Advisory enforcement is sufficient for all programs that perform in this way.
- As stated earlier, arbitrary programs should not have unrestricted access permission to files that are important enough to record lock.
-
- Advisory locking is more efficient because a record lock check does not have to be performed for every I/O request.
File and Record Locking
- The system on which the locking process resides can be remote from the system on which the file and record locks reside. In this way multiple processes on different systems can put advisory locks upon a single file that resides on one of these or on another system.
- The record locks for a file reside on the system that maintains the file. Deadlock detection is supported over NFS, but only for locks that reside on a particular system. A deadlock involving files residing on more than one NFS server will not be detected. Therefore, a process should hold record locks only on a single system at any given time for the deadlock mechanism to be effective.
-

- If a process needs to maintain locks over several systems,the process can avoid the sleep-when-blocked features of fcntl or lockf and maintain its own deadlock detection. If the process uses the sleep-when-blocked feature, provide a timeout mechanism (see alarm(2)) so that the process does not hang waiting for a lock to be cleared.
- When maintaining deadlock detection is more expensive that you would like, you can define an ordering for obtaining locks rather than relying on deadlock detection. Just figure out which locks need to be held when. If a program will ever hold more than one lock at a time, declare which lock should be held first, which second, and so on. As long as the program obtains the locks in the defined order, the locks will never deadlock. This approach works for either blocking or nonblocking lock requests.
|
|