文件鎖是一種用來保證多個進程對同一個文件的安全訪問的機制。文件鎖可以分為兩種類型:建議性鎖和強制性鎖。建議性鎖是一種協作式的鎖,它只有在所有參與的進程都遵守鎖的規則時才有效。強制性鎖是一種強制式的鎖,它由內核或文件系統來強制執行,不需要進程的配合。本文將主要介紹建議性鎖的實現方法和相關函數。
1. flock函數
flock函數是一種使用文件描述符來實現文件鎖的方法。flock函數的功能是對一個已打開的文件描述符fd進行鎖定或解鎖操作,它的函數原型如下:
#include <sys/file.h>
int flock(int fd, int operation);
flock函數的用法如下:
- 打開一個文件,獲得一個文件描述符fd。
- 調用flock函數,傳入fd和想要的鎖類型,例如LOCK_EX。
- 如果成功,返回0,表示獲得了鎖,可以對文件進行讀寫操作。
- 如果失敗,返回-1,并設置errno,表示沒有獲得鎖,可能是因為文件已經被其他進程鎖定,或者其他錯誤發生。
- 在完成文件操作后,調用flock函數,傳入fd和LOCK_UN,釋放鎖,關閉文件。
flock函數的參數如下:
- fd:一個已打開的文件描述符,必須是可讀或可寫的,不能是只執行的。
- operation:一個表示鎖類型的整數,可以是LOCK_SH、LOCK_EX、LOCK_UN或LOCK_NB的組合。
- LOCK_SH:共享鎖,允許多個進程同時對文件進行讀操作,但不允許寫操作。
- LOCK_EX:獨占鎖,只允許一個進程對文件進行讀寫操作,其他進程都不能訪問文件。
- LOCK_UN:解鎖,釋放之前的鎖定,允許其他進程訪問文件。
- LOCK_NB:非阻塞,如果不能立即獲得鎖,不會等待,而是返回錯誤。
flock函數的注意事項如下:
- flock函數只能對整個文件進行鎖定,不能對文件的部分區域進行鎖定。
- flock函數的鎖是與進程相關的,而不是與文件描述符相關的。也就是說,如果一個進程對一個文件加了鎖,那么該進程的其他文件描述符也可以訪問該文件,而不受鎖的影響。同樣,如果一個進程關閉了一個文件描述符,那么該進程的其他文件描述符仍然保持鎖定狀態,直到該進程退出或顯式解鎖。
- flock函數的鎖是建議性的,也就是說,它只有在所有參與的進程都遵守鎖的規則時才有效。如果有一個進程不使用flock函數,而是直接對文件進行讀寫操作,那么flock函數的鎖就會失效,造成數據的不一致或損壞。
flock函數的代碼示例如下:
// 一個使用flock函數的簡單示例,對一個文件加上獨占鎖,寫入一些數據,然后解鎖
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <string.h>int main() {// 打開一個文件,獲得一個文件描述符int fd = open("test.txt", O_WRONLY | O_CREAT, 0644);if (fd == -1) {perror("open");exit(1);}// 調用flock函數,傳入fd和LOCK_EX,加上獨占鎖if (flock(fd, LOCK_EX) == -1) {perror("flock");exit(1);}printf("Locked file\n");// 對文件進行寫操作,寫入一些數據char *data = "Hello, world!\n";if (write(fd, data, strlen(data)) == -1) {perror("write");exit(1);}printf("Wrote data to file\n");// 調用flock函數,傳入fd和LOCK_UN,解鎖文件if (flock(fd, LOCK_UN) == -1) {perror("flock");exit(1);}printf("Unlocked file\n");// 關閉文件close(fd);return 0;
}
2. fcntl函數
fcntl函數是一種使用文件描述符來實現文件鎖的方法。fcntl函數的功能是對一個已打開的文件描述符fd進行各種控制操作。它的函數原型如下:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
fcntl函數的用法如下:
- 打開一個文件,獲得一個文件描述符fd。
- 調用fcntl函數,傳入fd和F_GETLK,以及一個指向struct flock結構體的指針,獲取文件鎖的信息。
- 如果文件沒有被其他進程鎖定,或者鎖的類型和想要的類型不沖突,可以繼續調用fcntl函數,傳入fd和F_SETLK或F_SETLKW,以及一個指向struct flock結構體的指針,設置文件鎖的信息。
- 如果成功,返回0,表示獲得了鎖,可以對文件進行讀寫操作。
- 如果失敗,返回-1,并設置errno,表示沒有獲得鎖,可能是因為文件已經被其他進程鎖定,或者其他錯誤發生。
- 在完成文件操作后,調用fcntl函數,傳入fd和F_SETLK或F_SETLKW,以及一個指向struct flock結構體的指針,設置文件鎖的類型為F_UNLCK,釋放鎖,關閉文件。
fcntl函數的參數如下:
- fd:一個已打開的文件描述符,必須是可讀或可寫的,不能是只執行的。
- cmd:一個表示控制操作的整數,可以是F_DUPFD、F_GETFD、F_SETFD、F_GETFL、F_SETFL、F_GETLK、F_SETLK或F_SETLKW之一。
- F_DUPFD:復制文件描述符,返回一個新的文件描述符,指向同一個文件。
- F_GETFD:獲取文件描述符的標志,返回一個整數,表示是否設置了FD_CLOEXEC標志,該標志表示在執行exec函數時,自動關閉文件描述符。
- F_SETFD:設置文件描述符的標志,傳入一個整數,表示是否設置FD_CLOEXEC標志。
- F_GETFL:獲取文件狀態標志,返回一個整數,表示文件的訪問模式和其他標志,例如O_RDONLY、O_WRONLY、O_APPEND等。
- F_SETFL:設置文件狀態標志,傳入一個整數,表示文件的訪問模式和其他標志,例如O_RDONLY、O_WRONLY、O_APPEND等。
- F_GETLK:獲取文件鎖的信息,傳入一個指向struct flock結構體的指針,返回該結構體的內容,表示文件是否被其他進程鎖定,以及鎖的類型、起始位置、長度和持有者等信息。
- F_SETLK:設置文件鎖,傳入一個指向struct flock結構體的指針,表示要設置的鎖的類型、起始位置、長度等信息,如果成功,返回0,表示獲得了鎖,如果失敗,返回-1,并設置errno,表示沒有獲得鎖,可能是因為文件已經被其他進程鎖定,或者其他錯誤發生。
- F_SETLKW:設置文件鎖,與F_SETLK類似,但是如果不能立即獲得鎖,會阻塞等待,直到獲得鎖或者被信號中斷。
- arg:一個可選的參數,根據cmd的不同,可以是一個整數、一個指針或者省略。如果是一個整數,表示要設置的標志或文件描述符。如果是一個指針,表示要傳入或返回的struct flock結構體的地址。如果省略,表示不需要傳入任何參數。
fcntl函數的注意事項如下:
- fcntl函數可以對文件的部分區域進行鎖定,而不是整個文件。這可以通過設置struct flock結構體的l_whence、l_start和l_len字段來實現。l_whence表示鎖的起始位置的參考點,可以是SEEK_SET(文件開頭)、SEEK_CUR(當前位置)或SEEK_END(文件結尾)。l_start表示鎖的起始位置的偏移量,可以是正數、負數或零。l_len表示鎖的長度,如果是正數,表示從起始位置向后鎖定的字節數,如果是負數,表示從起始位置向前鎖定的字節數,如果是零,表示從起始位置到文件結尾的所有字節。
- fcntl函數的鎖是與文件描述符相關的,而不是與進程相關的。也就是說,如果一個進程對一個文件加了鎖,那么該進程的其他文件描述符不能訪問該文件,而受鎖的影響。同樣,如果一個進程復制了一個文件描述符,那么復制的文件描述符也會繼承鎖的狀態,直到所有的文件描述符都關閉或顯式解鎖。
- fcntl函數的鎖是建議性的,也就是說,它只有在所有參與的進程都遵守鎖的規則時才有效。如果有一個進程不使用fcntl函數,而是直接對文件進行讀寫操作,那么fcntl函數的鎖就會失效,造成數據的不一致或損壞。
fcntl函數的代碼示例如下:
// 一個使用fcntl函數的簡單示例,對一個文件的前10個字節加上共享鎖,讀取數據,然后解鎖
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>int main() {// 打開一個文件,獲得一個文件描述符int fd = open("test.txt", O_RDONLY);if (fd == -1) {perror("open");exit(1);}// 定義一個struct flock結構體,表示要設置的鎖的信息struct flock lock;lock.l_type = F_RDLCK; // 共享鎖lock.l_whence = SEEK_SET; // 文件開頭lock.l_start = 0; // 起始位置lock.l_len = 10; // 長度// 調用fcntl函數,傳入fd和F_SETLK,以及lock的地址,設置文件鎖if (fcntl(fd, F_SETLK, &lock) == -1) {perror("fcntl");exit(1);}printf("Locked file\n");// 對文件進行讀操作,讀取前10個字節的數據char buf[11]; // 定義一個緩沖區,多留一個字節存放'\0'if (read(fd, buf, 10) == -1) {perror("read");exit(1);}buf[10] = '\0'; // 添加字符串結束符printf("Read data from file: %s\n", buf); // 打印讀取的數據// 調用fcntl函數,傳入fd和F_SETLK,以及lock的地址,設置文件鎖的類型為F_UNLCK,解鎖文件lock.l_type = F_UNLCK; // 解鎖if (fcntl(fd, F_SETLK, &lock) == -1) {perror("fcntl");exit(1);}printf("Unlocked file\n");// 關閉文件close(fd);return 0;
}