linux檔案鎖筆記
一、概述
應用程式的一個常見的需求是從一個檔案中讀取資料,修改這些資料,然後將這些資料寫回檔案,當同一時刻只有一個程序使用這個檔案,這麼做不會出現問題,但是當多個程序同時更新一個檔案時,就會出現資源競爭的問題。
檔案鎖是一種檔案讀寫機制,在任何特定的時間只允許一個程序訪問一個檔案。利用這種機制能夠使讀寫單個檔案的過程變得更安全。
主要內容
- 檔案鎖的分類與原理
- 檔案鎖的使用
- 結束語
二、檔案鎖的介紹
檔案鎖主要分為兩種:一種是勸告式的,一種是強制式的鎖;但是在預設的情況下,檔案鎖是勸告式的。
勸告式檔案鎖
這種鎖不會介入write和read的操作,是一種協議式的鎖,在對一個檔案進行讀寫的時候,採用如下的過程
graph LR
加鎖-->讀寫操作
讀寫操作-->解鎖
但是勸告式檔案鎖不會從系統層上強制應用程式遵守這個協議,其他的程式可以不檢測檔案的鎖的狀態而直接操作檔案.
強制式檔案鎖
在程式中可以使用chmod()和fchmod()函式對檔案強制加鎖,強制式的鎖會使read/write函式阻塞或失敗.
三、檔案鎖的使用
檔案鎖有多種實現,這裡將的均是勸告鎖,強制鎖在應用中的價值不高,並且存在很大的弊端(容易鎖死).
在使用檔案鎖時,讀寫檔案的介面必須使用read和write,不能使用庫函式的IO操作函式,因為IO操作函式會帶有緩衝,影響加鎖的結果.
(好像標準庫函式的IO操作函式可以選擇沒有緩衝,但是依舊慎重)
flock
- 功能:為一個已經開啟的檔案 新增或者移除一把勸告鎖(advisory lock)
- 原型:int flock(int fd, int operation);
引數:
- fd 檔案id
- operation 運算元
operation可以使下列的三個值
- LOCK_SH 放置一個共享鎖
- LOCK_EX 放置一個排他鎖
LOCK_UN 解鎖
此外,LOCK_NB可以與前兩個共用,通過’|’,意思是非阻塞的鎖.當使用flock(fd,LOCK_SH|LOCK_NB)時,如果其他程序持有鎖,則不會阻塞,而是返回錯誤,並記錄一個錯誤碼到error.
程序A | 程序B LOCK_SH | 程序B LOCK_EX |
---|---|---|
LOCK_SH | 是 | 否 |
LOCK_EX | 否 | 否 |
一個程序在一個檔案上只能擁有一種型別的鎖,每次呼叫flock會改變鎖的模式.鎖會在檔案描述符關閉的時候自動釋放.
- 不足 :
- 粒度大,只能鎖整個檔案
- 勸告鎖,無法真正的鎖定檔案
- 有些NFS實現不支援flock
fcntl
這種鎖通常稱為”記錄加鎖”,fcntl可以對檔案的任意部分加鎖,而不影響其他部分的使用,記錄加鎖是針對應用程式說的,記錄邊界是應用程式定義的一個位元組範圍
- 原型:int fcntl(int fd, int cmd, … /* arg */ );
- 引數:
- fd 檔案描述符
- cmd :
- F_SET_LK 設定鎖狀態,失敗返回錯誤
- F_SET_LKW 設定鎖狀態,失敗阻塞
- F_GET_LK 獲取鎖狀態
- arg :struct flock 結構指標
fcntl的鎖操作,通過下面這個結構體進行設定
struct flock
{
short int l_type; /* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK. */
short int l_whence; /* Where `l_start' is relative to (like `lseek'). */
#ifndef __USE_FILE_OFFSET64
__off_t l_start; /* Offset where the lock begins. */
__off_t l_len; /* Size of the locked area; zero means until EOF. */
#else
__off64_t l_start; /* Offset where the lock begins. */
__off64_t l_len; /* Size of the locked area; zero means until EOF. */
#endif
__pid_t l_pid; /* Process holding the lock. */
};
鎖有三種狀態,分別是讀鎖/寫鎖和無鎖.
一個用於設定鎖狀態的函式
int pidf_set_lock(int fd,int lock_type)
{
struct flock flockstr;
//default
if(lock_type!=F_RDLCK&&lock_type!=F_WRLCK&&lock_type!=F_UNLCK)
return -1;
flockstr.l_type =lock_type;
flockstr.l_whence =SEEK_SET;
flockstr.l_start =0;
flockstr.l_len =0;
if(fcntl(fd,F_SETLK,&flockstr)==0)
{
if(flockstr.l_type==F_RDLCK)
pr_pidf_debug("set a F_RDLCK,thd pid = %d; \n",getpid());
if(flockstr.l_type==F_WRLCK)
pr_pidf_debug("set a F_WRLCK,thd pid = %d; \n",getpid());
if(flockstr.l_type==F_UNLCK)
pr_pidf_debug("set a F_UNLCK,thd pid = %d; \n",getpid());
return flockstr.l_type;
}
return -1;
}
獲取鎖狀態的函式
int pidf_get_lock(int fd,pid_t *pid)
{
struct flock flockstr;
flockstr.l_type =F_WRLCK;
flockstr.l_whence =SEEK_SET;
flockstr.l_start =0;
flockstr.l_len =0;
if(fcntl(fd,F_GETLK,&flockstr)==0)
{
if(pid!=NULL)
*pid =flockstr.l_pid;
return flockstr.l_type;
}
perror("fcntl F_GETLK");
return -1;
}
繼承和釋放
- fork產生的子程序不會繼承記錄鎖(fcntl),但是會繼承flock
- 記錄鎖在exec()中會得到保留
- 記錄鎖同時和一個程序,一個inode關聯,所以程序結束或者程序對檔案的連結關閉,都會導致其所持有的記錄鎖被釋放
四、結束語
參考
《Linux/UNIX系統程式設計手冊(下)》