小何講Linux: 檔案鎖及其例項
1. 檔案鎖基本概念
Linux中軟體、硬體資源都是檔案(一切皆檔案),檔案在多使用者環境中是可共享的。
檔案鎖是用於解決資源的共享使用的一種機制:當多個使用者需要共享一個檔案時,Linux通常採用的方法是給檔案上鎖,來避免共享的資源產生競爭的狀態。
檔案鎖包括建議性鎖和強制性鎖:
建議性鎖:要求每個使用上鎖檔案的程序都要檢查是否有鎖存在,並且尊重已有的鎖。在一般情況下,核心和系統都不使用建議性鎖,它們依靠程式設計師遵守這個規定。
強制性鎖:是由核心執行的鎖,當一個檔案被上鎖進行寫入操作的時候,核心將阻止其他任何檔案對其進行讀寫操作。採用強制性鎖對效能的影響很大,每次讀寫操作都必須檢查是否有鎖存在。
在Linux中,實現檔案上鎖的函式有lockf()和fcntl()
- lockf()用於對檔案施加建議性鎖
- fcntl()不僅可以施加建議性鎖,還可以施加強制鎖。
- fcntl()還能對檔案的某一記錄上鎖,也就是記錄鎖。
- 記錄鎖又可分為讀取鎖和寫入鎖,其中讀取鎖又稱為共享鎖,它能夠使多個程序都能在檔案的同一部分建立讀取鎖。
- 寫入鎖又稱為排斥鎖,在任何時刻只能有一個程序在檔案的某個部分建立寫入鎖。
- 在檔案的同一部分不能同時建立讀取鎖和寫入。
2. fcntl()函式格式
fcntl是一個非常通用的函式,它可以對已開啟的檔案進行各種操作,包括管理檔案鎖、獲得和設定檔案描述符標誌、獲得和設定檔案狀態標誌、檔案描述符的複製等很多功能,
所需標頭檔案
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
函式原型:int fcntl(int fd,int cmd,...);
int fcntl(int fd,int cmd,long arg);
int fcntl(int fd, int cmd, struct flock *lock)
函式傳入值fd:被引數cmd操作的檔案描述符
函式原型
int fcntl(int fd,int cmd,long arg);
int fcnt1(int fd, int cmd, struct flock *lock)
函式傳入值cmd
F_DUPFD:複製一個現存的描述符
F_GETFD:獲得fd的close-on-exec(執行時關閉)檔案描述符標誌,若標誌未設定,則檔案經過exec()函式之後仍保持開啟狀態
F_SETFD:設定close-on-exec 標誌,該標誌由引數arg 的FD_CLOEXEC位決定
F_GETFL:得到open設定的標誌
F_SETFL :改變open設定的標誌
F_GETLK:根據lock引數值,決定是否可以上檔案鎖
F_SETLK:設定lock引數值的檔案鎖
關於close_on_exec
close_on_exec 是一個程序所有檔案描述符(檔案控制代碼)的點陣圖標誌,每個位元位代表一個開啟的檔案描述符,用於確定在呼叫系統呼叫execve()時需要關閉的檔案控制代碼(參見include/fcntl.h)。當一個程式使用fork()函式建立了一個子程序時,通常會在該子程序中呼叫execve()函式載入執行另一個新程式。此時子程序將完全被新程式替換掉,並在子程序中開始執行新程式。若一個檔案描述符在close_on_exec中的對應位元位被設定,那麼在執行execve()時該描述符將被關閉,否則該描述符將始終處於開啟狀態。
函式傳入值cmd
F_SETLKW:這是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。在無法加鎖時,會進入睡眠狀態;如果可以加鎖或者捕捉到訊號則會返回
lock:是一個指向flock結構的指標,設定記錄鎖的具體狀態
函式返回值
成功時,返回值依賴於第二個引數cmd
-1:出錯
cmd取值方式
F_GETLK, F_SETLK或F_SETLKW: 獲得/設定記錄鎖的功能,成功則返回0,若有錯誤則返回-1,錯誤原因存於errno。
F_GETLK:
測試由lock所描述的鎖是否能使用。如果存在一把鎖,它阻止建立由lock所描述的鎖,則將這把現存鎖的資訊寫到lock指向的結構中(l_type-已有鎖的型別,l_pid-加鎖的程序號)。如果不存在這種情況,則除了將l_type設定為F_UNLCK之外,lock所指向的結構中的其他資訊保持不變。
F_SETLK :按照第三個引數lock指向的flock結構體所描述的鎖的資訊設定或者清除一個檔案的鎖。
F_SETLK:被用來實現共享(或讀)鎖(F_RDLCK)或獨佔(寫)鎖(F_WRLCK),同樣可以去掉這兩種鎖(F_UNLCK)。如果共享鎖或獨佔鎖不能被設定,fcntl()將立即返回EAGAIN
3. fcntl()使用例項
在該下面的例項中,首先給flock結構體的對應欄位賦予相應的值。
接著使用兩次fcntl()函式,分別用於判斷檔案是否可以上鎖和給相關檔案上鎖,這裡用到的cmd值分別為F_GETLK和F_SETLK(或F_SETLKW)。
用 F_GETLK 命令判斷是否可以進行flock 結構所描述的鎖操作:
若可以加鎖,則flock結構的l_type會被設定為F_UNLCK,其他域不變;
否則,則l_pid被設定為擁有檔案鎖的程序號,l_type被設定為已有鎖的型別,其他域不變。
- 檔案記錄所功能原始碼如下(檔案儲存為 mylock.c):
/* 檔案儲存為 mylock.c */
int lock_set(int fd,int type)
{
struct flock old_lock,lock;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_type = type;
lock.l_pid = -1;
fcntl(fd,F_GETLK,&lock);
if(lock.l_type != F_UNLCK)
{
if (lock.l_type == F_RDLCK)
{
printf("Read lock already set by %d\n",lock.l_pid);
}
else if (lock.l_type == F_WRLCK)
{
printf("Write lock already set by %d\n",lock.l_pid);
}
}
lock.l_type = type;
if ((fcntl(fd,F_SETLKW,&lock)) < 0)
{
printf("Lock failed : type = %d\n",lock.l_type);
return 1;
}
switch (lock.l_type)
{
case F_RDLCK:
{
printf("Read lock set by %d\n",getpid());
}
break;
case F_WRLCK:
{
printf("write lock set by %d\n",getpid());
}
break;
case F_UNLCK:
{
printf("Release lock by %d\n",getpid());
return 1;
}
break;
default:
break;
}
return 0;
}
- 下面的例項是檔案寫入鎖的測試用例,檔名為,wirte_lock.c 。
這裡首先建立了一個hello 檔案,之後對其上寫入鎖,最後釋放寫入鎖,程式碼如下所示:
#include<stdio.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include "mylock.c"
int main(void)
{
int fd;/* 首先開啟檔案*/
fd = open("hello",O_RDWR | O_CREAT, 0644);if(fd < 0)
{
printf("Open file error\n");
exit(1);
}
lock_set(fd, F_WRLCK); /* 給檔案上寫入鎖*/
getchar(); /*程式暫停,按回車鍵繼續*/
lock_set(fd, F_UNLCK); /* 給檔案解鎖*/
getchar();
close(fd);
exit(0);
return 0;
}
執行截圖如下:
在PC機上測試:
開啟兩個終端,並且在兩個終端上同時執行該程式,以達到多個程序操作一個檔案的效果。
首先在終端1執行,然後在終端2上執行,注意終端二中的第一行輸出。
由此可見,寫入鎖為互斥鎖,同一時刻只能有一個寫入鎖存在。
接下來的程式是檔案讀取鎖的測試用例,原理和上面的程式一樣。檔名為read_lock.c。
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include "mylock.c"
int main(void)
{
int fd;fd = open("hello",O_RDWR | O_CREAT, 0644);if(fd < 0)
{
printf("Open file error\n");
exit(1);
}
lock_set(fd, F_RDLCK); /* 給檔案上讀取鎖*/
getchar();
lock_set(fd, F_UNLCK); /* 給檔案解鎖*/
getchar();
close(fd);
exit(0);
return 0;
}
執行結果如下:
同樣開啟兩個終端,並首先啟動終端一上的程式,其執行結果如下所示:
觀察可知,讀鎖是共享鎖。