1. 程式人生 > >小何講Linux: 檔案鎖及其例項

小何講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;
}

執行結果如下:


同樣開啟兩個終端,並首先啟動終端一上的程式,其執行結果如下所示:


觀察可知,讀鎖是共享鎖。