I. What is file lock? No one will be unfamiliar with the word lock, because there are a lot of locks in our lives, and they play their role in all aspects, the lock function in the world can be summarized as one sentence, that is, to prevent some people from doing something. For example, the lock is to prevent people except the house owner from entering the house. You cannot enter the house, in this case, you cannot use anything in the house. Because programs often need to share data, which is usually implemented through files, imagine that process a is writing a file, another program B needs to read the same file and use the read data as the data required for running the program. What will happen? Process B may read disordered data because it does not know that another process a is rewriting the data in this file. To solve similar problems, file locking occurs. To put it simply, this is a secure file update method. When a program is writing a file, the file enters a temporary state. In this state, if another program attempts to read the file, it will automatically stop and wait until the State ends. The Linux system provides many features to implement File Locking. The simplest method is to create a lock file through atomic operations. In the previous example, the file lock is used to prevent other processes that need to write or read files from operating the file when the file is being written. 2. It is very easy to create a lock file by creating a lock file. We can use an open system call to create a lock file. When calling open, the oflags parameter should add the o_creat and o_excl parameters, for example, file_desc = open ("/tmp/LCK. test ", o_rdwr | o_creat | o_excl, 0444); you can create a lock file/tmp/LCK. test. O_creat | o_excl, which ensures that the caller can create files. This mode prevents two programs from creating the same file at the same time. If the file (/tmp/LCK. test) already exists, the open call will fail, and-1 will be returned. If a program needs to exclusively occupy a resource for a short period of time during its execution, this period (or code area) is usually called a critical section, before entering the critical section, we need to use the open system call to create the lock file, and then use the unlink system call to delete the lock file when exiting the critical section. Note: The lock files only act as indicators and must be used in collaboration between programs. That is to say, the lock files are only recommended for locking, rather than force locking, it does not really stop you from reading and writing data in files. Let's take a look at the following example: the source file name is filelock1.c, and the code is as follows:
# Include <unistd. h> # include <stdlib. h> # include <stdio. h> # include <fcntl. h> # include <errno. h> int main () {const char * lock_file = "/tmp/LCK. test1 "; int n_fd =-1; int n_tries = 10; while (n_tries --) {// create the lock file n_fd = open (lock_file, o_rdwr | o_creat | o_excl, 0444 ); if (n_fd =-1) {// creation failed printf ("% d-lock already present \ n", getpid (); sleep (2 );} else {// printf ("% d-I have exclusive access \ n", getpid (); sleep (1); close (n_fd ); // Delete the lock file and release the lock unlink (lock_file); sleep (2) ;}} return 0 ;}
When two instances of the same program are run at the same time, the running result is: from the running result, we can see that the two programs cross-lock the file, but the actual operation is, check/tmp/LCK every time you call the OPEN function. check whether the test1 file exists. If an open call exists, the file fails. It indicates that a process has locked the file. If the file does not exist, create the file and display the license information. However, this method has some shortcomings. We can see the file/tmp/LCK. test1 has been created many times and unlink has been deleted many times. That is to say, we cannot use a file that already has data as the lock file, because if the file already exists, the open call always fails. It seems to me that it is more like a kind of coordination arrangement for process work, more like the role of a binary semaphore. The file exists as 0 and does not exist as 1, rather than the real file lock. 3. Another problem is that if multiple processes need to read and write the same file, one file can only be written by one process at a time, however, the Read and Write areas of multiple processes are independent from each other. If a process always needs to write other processes to read and write the process, the efficiency is too low, can multiple processes read and write files at the same time to improve data read and write efficiency? To solve the problem mentioned above and the problem that occurs in the second point, that is, the file cannot be locked to the specified existing data file, we propose a new solution, that is, Region locking. To put it simply, Region locking means that a part of the file is locked, but other programs can access other parts of the file. However, the creation and use of region locks are much more complex than the file locks described above. 1. Create a region lock in Linux. To implement this function, we can use the fcntl System Call and lockf call. However, the following describes how to create a region lock by using the fcntl system call. The fctnl function principle is: int fctnl (INT Fildes, int command ,...); it operates on the description of an opened file and completes different tasks according to the command parameter settings. It has three Optional tasks: f_getlk, f_setlk, f_setlkw, the meanings of the three parameters are described in detail below. When using these commands, the third parameter of fcntl must be a pointer to the flock structure. Therefore, in practice, the fctnl function prototype is generally: int fctnl (INT Fildes, int command, struct
Flock * flock_st); 2. the Flock Structure depends on the specific implementation, but it includes at least the following members: Short l_type; file lock type, corresponding to f_rdlck (read lock, shared lock), f_unlck (unlock, also called clear lock), f_wrlck (write lock, also called exclusive lock. Short l_whence; calculates the relative position of the file, which corresponds to one of seek_set (File Header), seek_cur (current position), and seek_end (file end. Off_t Rochelle start; start from the Rochelle start byte starting with Rochelle whence. Off_t l_len; the length of the locked area. Pid_t l_pid; used to record the process in which the parameter holds the lock. Members l_whence, l_start, and l_len define a region in a file, that is, a continuous collection of bytes, such as struct flock region; region. rochelle whence = seek_set; region. rochelle start = 10; region. rochelle Len = 20; this indicates that the fcntl function operation is locked between 10th and 29 bytes starting from the file header. 3. the type of the file lock can be found from the value of Rochelle type, a member of Flock above. There are three types of File locks, which are described in detail here. F_rdlck: from its name, we can know that it is a read lock, also known as a shared lock. Many different processes can have read (SHARE) locks in the same (or overlapping) area of files. As long as any process has a read (SHARE) Lock, no process can obtain the write (exclusive) lock in the region. To obtain a shared lock, the file must be opened in "read" or "read/write" mode. To put it simply, when a process reads data from a file, the data in the file cannot be changed or rewritten. This is to prevent the data from being changed and the program reading the data can read the disordered data, it is easy to understand that the same region of a file can be read by multiple processes at the same time, because reading does not damage data, or the reading operation does not change the data of the file. F_wrlck: from its name, we can know that it is a write lock, also called an exclusive lock. Only one process can have a write (exclusive) Lock in any specific area of the file. Once a process has such a lock, no other process can obtain any type of lock in this region. To obtain a write (exclusive) lock, the file must also be opened in "read" or "read/write" mode. To put it simply, a file is in the same region (or overlap) at the same time. Only one process can perform write operations on it, and during the write operation, other processes cannot read data from the region. This requirement is obviously easy to understand, because if two processes perform write operations on a file at the same time, the file content will be disordered, and because the data in the file will be changed during writing, therefore, it does not allow other processes to read or delete files. F_unlck: it can be known from its name. It is used to unlock a locked area. 4. The meaning of different commands when we talk about the command parameters of the fcntl function, we talk about three Command Options. Here we will explain them in detail. F_getlk command, which is used to obtain the lock information of the file opened by Fildes (the first parameter of fcntl). It does not attempt to lock the file, the calling process can pass the information of the lock type you want to create to fcntl, and the function call will return any information that will prevent the lock from being obtained, that is, it can test whether the lock you want to create can be successfully created. When the fcntl call is successful, non-1 is returned. If the lock request can be successfully executed, the flock structure remains unchanged. If the lock request is blocked, fcntl overwrites the Flock Structure with relevant information. -1 is returned if the request fails. Therefore, if the call is successful, the caller can check whether the flock structure has been modified by checking the content of the flock structure to check whether the lock request has been successfully executed, because the value of Rochelle PID is set to the identifier of a locked process, in most cases, you can check whether this field has changed to determine whether the flock structure has been modified. The fcntl function that uses f_getlk is returned immediately after it is called. For example, there is a flock structure variable, flock_st, flock_st.l_pid =-1, 10th ~ A read lock already exists for 29 bytes, 40th ~ of the file ~ If a write lock exists in the 49 bytes, you can use the f_getlk command to test the value between 10th and when calling fcntl ~ Can a read lock be created in 29 bytes? Because the lock can be created, fcntl returns non-1, and the content of the flock structure will not change, flock_st.l_pid
=-1. And if we test 40th ~ Whether a write lock can be created in 49 bytes. The test fails because a write lock already exists in this region, but fcntl still returns non-1, only the flock structure will be overwritten by the lock information related to this region. flock_st.l_pid is the process identifier of the process that owns the write lock. F_setlk command, which attempts to lock or unlock a certain area of the file pointed by Fildes. Its function depends on the value of l_type in the flock structure. For this command, the l_pid field of the flock structure is meaningless. If the lock is successful, non-1 is returned. If the lock fails,-1 is returned. The fcntl function that uses f_setlk is returned immediately after it is called. F_setlkw command. This command works the same way as the f_setlk command. However, the difference is that when it cannot obtain the lock, that is, it waits until it can be locked. 5. After reading so many examples, you may be confused. Use the following example to clarify your thoughts. The source file named filelock2.c is used to create a data file and lock the file area. The Code is as follows:
# Include <unistd. h> # include <stdlib. h> # include <stdio. h> # include <fcntl. h> int main () {const char * test_file = "test_lock.txt"; int file_desc =-1; int byte_count = 0; char * byte_to_write = "A"; struct flock region_1; struct flock region_2; int res = 0; // open a file descriptor file_desc = open (test_file, o_rdwr | o_creat, 0666); If (! File_desc) {fprintf (stderr, "unable to open % s for read/write \ n", test_file); exit (exit_failure );} // Add 100 'A' characters to the file for (byte_count = 0; byte_count <100; ++ byte_count) {write (file_desc, byte_to_write, 1 );} // In File 10th ~ Set the 29-byte read lock (shared lock) region_1.l_type = f_rdlck; region_1.l_whence = seek_set; region_1.l_start = 10; region_1.l_len = 20; // 40 ~ in the file ~ 49-byte write lock (exclusive lock) region_2.l_type = f_wrlck; region_2.l_whence = seek_set; region_2.l_start = 40; region_2.l_len = 10; printf ("process % d locking file \ n ", getpid (); // lock file res = fcntl (file_desc, f_setlk, ion_1); If (RES =-1) {fprintf (stderr, "failed to lock region 1 \ n");} res = fcntl (file_desc, f_setlk, ion_2); If (RES =-1) {fprintf (stderr, "failed to lock Region 2 \ n");} // sleep the program for one minute for testing sleep (60); printf ("process % d closing file \ n ", getpid (); close (file_desc); exit (exit_success );}
The following source file filelock3.c is used to test the lock set in the previous file. The Code is as follows:
# Include <unistd. h> # include <stdlib. h> # include <stdio. h> # include <fcntl. h> int main () {const char * test_file = "test_lock.txt"; int file_desc =-1; int byte_count = 0; char * byte_to_write = "A"; struct flock region_1; struct flock region_2; int res = 0; // open the data file file_desc = open (test_file, o_rdwr | o_creat, 0666); If (! File_desc) {fprintf (stderr, "unable to open % s for read/write \ n", test_file); exit (exit_failure );} // set the lock type struct flock region_test1; region_test1.l_type = f_rdlck; region = seek_set; region_test1.l_start = 10; region_test1.l_len = 20; region_test1.l_pid =-1; // set the lock type struct flock region_test2; region_test2.l_type = f_rdlck; region = seek_set; region_test2.l_start = 40; region_test2.l_len = 10; region_test2.l_pid =-1; // test whether a read lock can be added to Area 1. Res = fcntl (file_desc, f_getlk, ion_test1); If (RES =-1) {fprintf (stderr, "failed to get rdlck \ n");} If (region_test1.l_pid =-1) {// you can add a read lock printf ("test: possess % d cocould lock \ n ", getpid ();} else {// do not allow a read lock printf ("test: Prossess % d get lock failure \ n", getpid ());} // test whether a read lock can be added to Region 2. Res = fcntl (file_desc, f_getlk, ion_test2); If (RES =-1) {fprintf (stderr, "failed to get rdlck \ n");} If (region_test2.l_pid =-1) {// you can add a read lock printf ("test: possess % d cocould lock \ n ", getpid ();} else {// do not allow a read lock printf ("test: Prossess % d get lock failure \ n", getpid ());} exit (exit_success );}
The running result is as follows:
Because the read lock exists in area 1, adding a read lock on it is successful. However, the lock on Area 2 is a write lock, the test fails because no lock of any type can be applied to the lock. Note that the test fails not because the fctnl call fails. It still returns non-1. We check the flock structure member Rochelle PID to determine the test result. 3. Unlock the empty lock. What will happen if I want to unlock the unlocked area in this process? What happens if other processes in this region lock it? If a process does not actually lock a region, the unlock operation is successful, but it cannot unlock the lock applied by other processes to the same region. It can also be said that the final result of the unlock request depends on any lock set by the process in the file, but the unlocked status is still unlocked.