|
|
Locking a record is done the same way as locking a file except for the differing starting point and length of the lock. We will now try to solve an interesting and real problem. There are two records (these records may be in the same or different file) that 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 interrecord pointers in a doubly linked list.) To do this you must decide the following questions:
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 we have decided to use record locking in the first place. Different programs might:
Let us now look at our example of inserting an entry into a doubly linked list. For the example, we will 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 may 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. If there are processes with pending write locks that are sleeping on the same section of the file, the lock promotion succeeds and the other (sleeping) locks wait. Promoting (or demoting) 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 /usr/group lockf function does not have read locks, lock promotion is not applicable to that call. An example of record locking with lock promotion follows:
struct record { . . /* data portion of record */ . long prev; /* index to previous record in the list */ long 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. */ long set3lock (this, here, next) long 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 was used instead, the fcntl system calls would fail if blocked. The program would then have to be changed to handle the blocked condition in each of the error return sections.
Let us now look at a similar example using the lockf function. Since there are no read locks, all (write) locks will be referenced generically as locks.
/* Lock promotion using lockf(3) * 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, 0); 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_UNLCK or F_ULOCK). An unlock cannot be blocked by another process and will only affect locks that were placed by this process. The unlock only affects the section of the file defined in the previous example by lck. It is possible to unlock or change the type of lock on a subsection of a previously set lock. This may cause an additional lock (two locks for one system call) to be used by the operating system. This occurs if the subsection is from the middle of the previously set lock.