Notes on Record Locking and getloadavg()

Notes on Record Locking and getloadavg()


Record Locking

Record Locking is the term normally used to describe the ability of a process to prevent other processes from modifying a region of a file, while the first process is reading or modifying that portion of the file. POSIX.1 bases it's record locking mechanism on the fcntl function:

    #include <sys/types.h>
    #include <unistd.h>
    #include <fcntl.h>

    /*
     * Returns:
     *    -1 on error 
     */
    int fcntl(int filedes, int cmd, ... /* struct flock *flockptr */ );

We'll start with the third argument (flockptr), which points to a flock structure:

    struct flock {
        short l_type;     /* F_RDLCK (shared read lock), or 
                           * F_WRLCK (shared write lock), or
                           * F_UNLCK (unlocking a region)
                           */
        off_t l_start;    /* offset in bytes, relative to l_whence */
        short l_whence;   /* SEEK_SET: file's offset is set to 
                           *           l_start bytes from beginning of file
                           * SEEK_CUR: file's offset is set to its current value 
                           *           plus the l_start (which can be + or -)
                           * SEEK_END: file's offset is set to the size of the file
                           *           plus the l_start (which can be + or -)
                           */
        off_t l_len;      /* length of region, in bytes
                           * special case: if (l_len == 0), it means that the lock 
                           * extends to the largest possible offset of the file. 
                           * This allows us to lock a region starting anywhere in
                           * the file, up through and including any data that is 
                           * appended to the file
                           */
        pid_t l_pid;      /* returned when cmd = F_GETLK */
    }

This structure describes:

To lock an entire file, set l_start and l_whence to point to the beginning of the file (i.e. l_start= 0, l_whence= SEEK_SET), and specify a length (l_len) of 0.

Any number of processes can have a shared read lock on a given byte, but only one process can have an exclusive write lock on a given byte. To obtain a read lock the descriptor must be open for reading, and the region cannot have an exclusive write lock. To obtain a write lock the descriptor must be open for writing, and the region cannot have an exclusive write lock nor any read locks.

Now, we will describe the second parameter (cmd) for fcntl. The possible commands are:

F_GETLKDetermine if the lock described by flockptr is blocked by some other lock. If a lock exists that would prevent ours from being created, the information on that existing lock overwrites the information pointed to by flockptr. If no lock exists that would prevent ours from being created, the structure pointed to by flockptr is left unchanged except for the l_type member, which is set to F_UNLCK.
F_SETLKSet the lock described by flockptr. If we are unable to obtain a lock (because of previous locks already granted for the region) then fcntl returns -1 and errno is set to either EACCES or EAGAIN
F_SETLKWThis command is a blocking version of F_SETLK (the W in the command means "wait"). If the requested read lock or write lock cannot be granted because another process currently has some part of the requested region locked, the calling process is put to sleep. This sleep is interrupted is a signal is caught.

Be aware that testing for a lock with F_GETLK and then trying to obtain that lock with F_SETLK or F_SETLKW is not an atomic operation. We have no guarantee that between the two fcntrl calls some other process won't come in and obtain the same lock.

To save ourselves the trouble of allocating a flock structure and filling in all the elements each time, Stevens defines the function lock_reg and a number of macros that call it. Notice that the macros shorten the number of parameters by two, and save us the grief of remembering the F_* constants mentioned above.

#define read_lock(fd, offset, whence, len)    \
         lock_reg  (fd, F_SETLK,  F_RDLCK, offset, whence, len)

#define readw_lock(fd, offset, whence, len)   \
         lock_reg  (fd, F_SETLKW, F_RDLCK, offset, whence, len)

#define write_lock(fd, offset, whence, len)   \
         lock_reg  (fd, F_SETLK,  F_WRLCK, offset, whence, len)

#define writew_lock(fd, offset, whence, len)  \
         lock_reg  (fd, F_SETLKW, F_WRLCK, offset, whence, len)

#define un_lock(fd, offset, whence, len)      \
         lock_reg  (fd, F_SETLK,  F_UNLCK, offset, whence, len)


int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
    struct flock lock;
 
    lock.l_type   = type;     /* F_RDLCK, F_WRLCK, F_UNLCK         */
    lock.l_start  = offset;   /* byte offset, relative to l_whence */
    lock.l_whence = whence;   /* SEEK_SET, SEEK_CUR, SEEK_END      */
    lock.l_len    = len;      /* #bytes (0 means to EOF)           */

    return ( fcntl(fd, cmd, &lock) );
}

There are 3 important rules regarding automatic inheritance and release of record locks:

  1. Locks are associated with a process and a file. When a process terminates, all its locks are released. Whenever a descriptor is closed, any locks on the file referenced by that descriptor for that process are released.
  2. Locks are never inherited by the child across a fork (otherwise we could end up with two processes sharing a write lock)
  3. Locks may be inherited by a new program across an exec. This is not required by POSIX.1 and therefore machine dependent

getloadavg()

Purpose:

getloadavg returns the system load averages.

Declaration:

     #include <sys/loadavg.h>

     /* returns:
      *  on success, the # of samples actually retrieved
      *  on error, -1
      */
     int getloadavg(double loadavg[], int nelem);

Parameters:

References