Linux檔案鎖
一、檔案鎖的分類:
翻閱參考資料,你會發現檔案鎖可以進行很多的分類,最常見的主要有讀鎖與寫鎖,前者也叫共享鎖,後者也叫排斥鎖,值得注意的是,多個讀鎖之間是不會相互干擾的,多個程序可以在同一時刻對同一個檔案加讀鎖;但是,如果已經有一個程序對該檔案加了寫鎖,那麼其他程序則不能對該檔案加讀鎖或者寫鎖,直到這個程序將寫鎖釋放,因此可以總結為:對於同一個檔案而言,它可以同時擁有多個讀者,但是在某一時刻,他只能擁有一個寫者。
根據核心行為來分,檔案鎖可以分成勸告鎖與強制鎖兩大類:
1. 勸告鎖:
勸告鎖講究的是一種協同工作,核心僅負責對檔案加鎖以及檢查檔案是否已經上鎖等操作,而不親自去參與檔案鎖的控制與協調,而這些都需要程式設計師首先要檢查所要訪問的檔案之前是否已經被其他程序加鎖來實現併發控制,勸告鎖不僅可以對檔案的任一部分加鎖,也可以對整個檔案加鎖。下面是加鎖規則:
2.強制鎖:
強制鎖則是核心強制使用的一種檔案鎖,每當有程序違反鎖規則,核心將會進行阻止,具體的加鎖規則如下:
(1)若一個檔案已經加上共享鎖,那麼其他程序在對這個檔案進行寫操作時將會被核心阻止;
(2)若一個檔案已經加上了排他鎖,那麼其他程序對這個檔案的讀取與寫操作都將被阻止;
下表總結了程序試圖訪問已經加有強制鎖的檔案,程序行為如下:
從上表可以看出,若程序要訪問檔案的鎖型別與要進行的操作存在衝突,那麼若操作時在阻塞時進行,則程序將會阻塞;若操作時在非阻塞時程序,則程序將會立即返回EAGIN錯誤(PS:表示資源臨時不可達)。
根據加鎖區域範圍,可以分成整個檔案鎖與區域檔案鎖(記錄鎖),二者很好區分,前者可以鎖定整個檔案,而後者則可以鎖定檔案中的某一區域,甚至是某幾個位元組。
二、檔案鎖相關的系統呼叫:
目前跟檔案加鎖相關的系統呼叫主要有兩個:flock與fcntl, 二者在應用範圍方面也存在著一些差別,早起的flock函式只能處理勸告鎖,在Linux 2.6版本中將其功能擴充至強制鎖,另外flock函式只能對整個檔案加鎖,不能加記錄鎖,而fcntl函式則不僅完全支援加勸告鎖與強制鎖,還支援記錄鎖,另外因為它符合POSIX標準,具有很好的可移植性。值得注意的是,在給檔案加鎖之前,一定要保證檔案以相應的訪問模式開啟,例如要對一個檔案加上共享鎖,一定要首先按讀模式開啟檔案,若要給檔案加上排他鎖,則首先要按寫模式開啟對應檔案若想加兩種鎖,則需要按讀寫模式開啟.
int fcntl(int fd, int cmd, struct flock*lock)
fcntl函式專門用來對檔案描述符操作的,具體的操作行為取決於cmd值,與本文檔案鎖相關的cmd值主要有:
F_GETLK:獲取檔案鎖
F_SETLK:設定檔案鎖(非阻塞版)
F_SETLKW:設定檔案鎖(阻塞版)
值得注意的是,呼叫F_SETLKW命令去設定檔案鎖的請求不能完成,則程序將會進入休眠狀態,直至所要求的鎖被釋放。其它更多的cmd值可以參考《UNIX環境高階程式設計》或者”man fcntl”。
lock引數主要是用來實現指定檔案鎖型別、所鎖定的檔案範圍以及正在鎖定檔案的程序ID(只是在獲取檔案鎖時才會用到),詳細結構如下:
struct flock {
short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock (F_GETLK only) */
};
其中l_type定義所的型別,F_RDLCK表示共享鎖,F_WRLCK表示排他鎖,F_UNLCK表示釋放掉之前已經建立的鎖;l_whence, l_start與l_len共同作用設定所加鎖的範圍,其中l_whence設定鎖的參照起始點,SEEK_SET表示檔案開頭,SEEK_CUR表示檔案當前位置(fseek可以移動檔案指標位置),SEEK_END表示檔案結尾;l_start與l_whence相結合確定了鎖的絕對起始點,l_len則表示從絕對起始點開始需要鎖定的位元組數,其值可正可負,鎖的範圍則是[l_start, l_start+l_len-1],若其值為0,則有特殊的含義,表示鎖的區域從絕對起始點開始到最大可能的偏移量為止,這種情況可用於鎖定整個檔案,此時只需將鎖的絕對起始點設定為檔案開始位置即可。
函式的返回值是:若成功則返回0,否則返回-1.
int flock(int fd, int operation)
相對於fcntl函式,flock顯得更加簡單,因為所加的鎖會影響整個檔案,其中operation引數規定了所加鎖的型別:
LOCK_SH:表示加共享鎖
LOCK_EX:表示排他鎖
LOCK_UN:表示釋放鎖
LOCK_MAND:表示強制鎖
三、鎖的繼承與釋放:
1. 鎖與程序和檔案緊密相連,若程序終止,則有它建立的所有鎖將會自動釋放掉;若關閉檔案描述符,則程序由此描述符引用的檔案上的任何鎖也將會被釋放;
2. 由fork產生的子程序不會繼承父程序的檔案鎖;
3. 在執行exec之後,新程式可以繼承原來程式的檔案鎖。
跟鎖有關的封裝函式(來自於《高階UNIX環境程式設計》如下測試例子:
/***************************************file_lock.h*******************************************/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> int lock_reg(int, int, int, off_t, int, off_t); //register lock pid_t lock_test(int, int, off_t, int, off_t); //test lockable //set lock #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)) //Test lock #define read_lock_pid(fd, offset, whence, len) \ lock_test((fd), F_RDLCK, (offset), (whence), (len)) #define write_lock_pid(fd, offset, whence, len) \ lock_test((fd), F_WRLCK, (offset), (whence), (len)) #define is_read_lockable(fd, offset, whence, len) \ (read_lock_pid == 0) #define is_write_lockable(fd, offset, whence, len) \ (write_lock_pid == 0)
/******************************************file_lock.c****************************************/
#include "file_lock.h"
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));
}
pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type; /* F_RDLCK or F_WRLCK */
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) */
if (fcntl(fd, F_GETLK, &lock) < 0)
{
printf("fcntl error for %s.\n", strerror(errno));
return (-1);
}
if (lock.l_type == F_UNLCK)
{
return(0); /* false, region isn't locked by another proc */
}
return(lock.l_pid); /* true, return pid of lock owner */
}
/*****************************************file_lock.c*****************************************/
#include "file_lock.h"
static void lock_set(int fd, int type) ;
int main(int argc, char **argv)
{
int fd ;
//First open file and choose right mode by lock type
fd = open("/tmp/hello", O_RDWR | O_CREAT, 0666) ;
if (fd < 0)
{
printf("open error ! \n") ;
exit(1) ;
}
//lock
printf("press ENTER to add lock:\n");
getchar() ;
lock_set(fd, F_WRLCK) ;
printf("press ENTER and exit, lock release:\n");
getchar() ;
//release lock
lock_set(fd, F_UNLCK) ; //useless code, lock release if close fd
if (close(fd) < 0)
{
printf("\n close file error ! \n") ;
exit(1) ;
}
return 0 ;
}
void lock_set(int fd, int type)
{
pid_t read_lock_id = 0;
pid_t write_lock_id = 0;
while (1)
{
//set lock according to lock type
switch (type)
{
case F_RDLCK:
if (read_lock(fd, 0, SEEK_SET, 0) == 0)
{
printf("read lock set by %d \n", getpid());
return;
}
break;
case F_WRLCK:
if (write_lock(fd, 0, SEEK_SET, 0) == 0)
{
printf("write lock set by %d \n", getpid());
return;
}
break;
case F_UNLCK:
if (un_lock(fd, 0, SEEK_SET, 0) == 0)
{
printf("release lock by %d \n", getpid());
return;
}
break;
}
//test lock owner
if (type == F_RDLCK)
{
if ((read_lock_id = read_lock_pid(fd, 0, SEEK_SET, 0)) != 0)
{
printf("read lock already set by %d \n", read_lock_id);
}
}
else if (type == F_WRLCK)
{
if ((write_lock_id = read_lock_pid(fd, 0, SEEK_SET, 0)) != 0)
{
printf("write lock already set by %d \n", write_lock_id);
}
}
}
}
/****************************************Makefile*********************************************/
all: file_lock_test
CC = gcc
file_lock_test:
$(CC) file_syn.c file_lock.c -o file_lock_test
clean:
rm -rf *.o file_lock_test