Lock Files
A lock file (which should not be confused with file/record locking, an I/O technique covered in Section 4.3) can be used by processes as a way to communicate with one another. The processes involved may be different programs or multiple instances of the same program. The use of lock files has a long history in UNIX. Early versions of UNIX (as well as some current versions) use lock files as a means of communication. Lock files are sometimes found in line printer and uucp implementations . In some systems the coordination of access to password and mail files also rely on lock files and/or the locking of a specific file.
The theory behind the use of a lock file as an interprocess communication technique is rudimentary. In brief, by using an agreed-upon file-naming convention, a process examines a prearranged location for the presence or absence of a lock file. Often the location is a temporary directory (e.g., /tmp ) where the files are automatically cleared when the system reboots (or by periodic housecleaning by the system administrator) and where all users normally have read/write/execute permission. In its most basic form, if the file is present, the process takes one set of actions, and if the file is missing, it takes another. For example, suppose we have two processes, Process_One and Process_Two, that seek access to a single non-shareable resource (e.g., a printer or disk). A lock file-based communication convention for the two processes could be as shown in Figure 4.1.
Figure 4.1. Using a lock file for communication with two processes.
It is clear that communication implemented in this manner only conveys a minimal amount of information from one process to another. In essence, the processes are using the presence or absence of the lock file as a binary semaphore. The file's presence or absence communicates, from one process to another, the availability of a resource.
Such a communication technique is fraught with problems. The most apparent problem is that the processes must agree upon the naming convention for the lock file. However, additional, perhaps unforeseen, problems may arise as well. For example,
- What if one of the processes fails to remove the lock file when it is finished with the resource?
- Polling (the constant checking to determine if a certain event has occurred) is expensive (CPU-wise) and is to be avoided. How does the process that does not obtain access to the resource wait for the resource to become free?
- Race conditions whereby both processes find the lock file absent at the same time and, thus, both attempt to simultaneously create it should not happen. Can we make the generation of the lock file atomic (non-divisible, i.e., non-interruptible )?
As we will see, we will be able to address some of these concerns and others we will only be able to limit in scope. A program that implements communications using a lock file is presented below. The code for the main portion of the program is shown in Program 4.1.
Program 4.1 Using a lock filethe main program.
File : p4.1.cxx /* Using a lock file as a process communication technique. */ #include + #include #include "lock_file.h" <-- 1 using namespace std; int 10 main(int argc, char *argv[ ]){ int numb_tries, i = 5; int sleep_time; char *fname; /* + Assign values from the command line */ set_defaults(argc, argv, &numb_tries, &sleep_time, &fname); /* Attempt to obtain lock file 20 */ if (acquire(numb_tries, sleep_time, fname)) { while (i--) { // simulate resource use cout << getpid( )<< " " << i << endl; sleep(sleep_time); + } release(fname); // remove lock file return 0; } else cerr << getpid( ) << " unable to obtain lock file after " 30 << numb_tries << " tries." << endl; return 1; }
(1) This header resides locally.
At line 7 of the program, the local header file lock_file.h is included. This file (Figure 4.2) contains the prototypes for the three functions set_defaults , acquire , and release , that are used to manipulate the lock file. Preprocessor statements are used in the header file to prevent the file from being inadvertently included more than once.
In line 17 of the main program the set_defaults function is called to establish the default values. Once these values have been assigned, the program attempts to obtain the lock file by calling the function acquire (line 21). If the program is successful in creating the lock file, it then accesses the non-shareable resource. In the case of Program 4.1 the resource involved is the screen. When access to the screen is acquired , the program displays a series of integer values. Once the program is finished with the resource (all values have been displayed), the lock file is removed using the release function.
Figure 4.2 The lock_file.h header file.
File : lock_file.h #ifndef LOCK_FILE_H #define LOCK_FILE_H /* Lock file function prototypes + */ void set_defaults(int, char *[], int *, int *, char **); bool acquire(int, int, char *); bool release(char *); #endif
The set_defaults function accepts five arguments. The first two arguments (an integer and an array of character pointers) are the argc and argv values passed to the main program (Program 4.1). As written, the program will allow the user to change some or all of the default values by passing alternate values on the command line when the program is invoked. The remaining three arguments for set_defaults are the number of tries to be made when attempting to generate the lock file, the amount of time to wait in seconds between attempts, and a reference to the name of the lock file.
The acquire function takes three arguments. The first is the number of times to attempt to create the lock file, the second the sleep interval between tries, and the third a reference to the lock file name. The acquire function returns a boolean value indicating its success.
The function release removes the lock file. This function is passed a reference to the lock file and returns a boolean value indicating whether or not it was successful. The code for these functions, which are stored in a separate file, is shown in Figure 4.3.
Figure 4.3 Source code for the set_defaults , acquire , and release functions.
File : lock_file.cxx /* Source code for using lock file. Compile using -c and -D_GNU_SOURCE options. Link object code as needed. */ + #include #include #include #include #include 10 #include #include const int NTRIES = 5; // default values const int SLEEP = 5; const char *LFILE = "/tmp/TEST.LCK"; + using namespace std; void set_defaults(int ac, char *av[ ], int *n_tries, int *s_time, char **f_name){ static char full_name[PATH_MAX]; 20 *n_tries = NTRIES; // Start with defaults *s_time = SLEEP; strcpy(full_name, LFILE); switch (ac) { case 4: // File name was specified + full_name[0] = '
File : lock_file.cxx /* Source code for using lock file. Compile using -c and -D_GNU_SOURCE options. Link object code as needed. */ + #include #include #include #include #include 10 #include #include const int NTRIES = 5; // default values const int SLEEP = 5; const char *LFILE = "/tmp/TEST.LCK"; + using namespace std; void set_defaults(int ac, char *av[ ], int *n_tries, int *s_time, char **f_name){ static char full_name[PATH_MAX]; 20 *n_tries = NTRIES; // Start with defaults *s_time = SLEEP; strcpy(full_name, LFILE); switch (ac) { case 4: // File name was specified + full_name[0] = '