Linux資料管理——檔案鎖定

來源:互聯網
上載者:User
一、什麼是檔案鎖定對於鎖這個字,大家一定不會陌生,因為我們生活中就存在著大量的鎖,它們各個方面發揮著它的作用,現在世界中的鎖的功能都可歸結為一句話,就是阻止某些人做某些事,例如,門鎖就是阻止除了屋主之外的人進入這個房子,你進入不到這個房子,也就不能使用房子裡面的東西。而因為程式經常需要共用資料,而這通常又是通過檔案來實現的,試想一個情況,A進程正在對一個檔案進行寫操作,而另一個程式B需要對同一個檔案進行讀操作,並以讀取到的資料作為自己程式運行時所需要的資料,這會發生什麼情況呢?進程B可能會讀到錯亂的資料,因為它並不知道另一個進程A正在改寫這個檔案中的資料。為瞭解決類似的問題,就出現了檔案鎖定,簡單點來說,這是檔案的一種安全的更新方式,當一個程式正在對檔案進行寫操作時,檔案就會進入一種暫時狀態,在這個狀態下,如果另一個程式嘗試讀這個檔案,它就會自動停下來等待這個狀態結束。Linux系統提供了很多特性來實現檔案鎖定,其中最簡單的方法就是以原子操作的方式建立鎖檔案。用回之前的例子就是,檔案鎖就是當檔案在寫的時候,阻止其他的需要寫或者要讀檔案的進程來操作這個檔案。二、建立鎖檔案建立一個鎖檔案是非常簡單的,我們可以使用open系統調用來建立一個鎖檔案,在調用open時oflags參數要增加參數O_CREAT和O_EXCL標誌,如file_desc = open("/tmp/LCK.test", O_RDWR|O_CREAT|O_EXCL, 0444);就可以建立一個鎖檔案/tmp/LCK.test。O_CREAT|O_EXCL,可以確保調用者可以建立出檔案,使用這個模式可以防止兩個程式同時建立同一個檔案,如果檔案(/tmp/LCK.test)已經存在,則open調用就會失敗,返回-1。如果一個程式在它執行時,只需要獨佔某個資源一段很短的時間,這個時間段(或代碼區)通常被叫做臨界區,我們需要在進入臨界區之前使用open系統調用建立鎖檔案,然後在退出臨界區時用unlink系統調用刪除這個鎖檔案。注意:鎖檔案只是充當一個指標的角色,程式間需要通過相互協作來使用它們,也就是說鎖檔案只是建議鎖,而不是強制鎖,並不會真正阻止你讀寫檔案中的資料。可以看看下面的例子:源檔案檔案名稱為filelock1.c,代碼如下:
#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <fcntl.h>#include <errno.h>int main(){const char *lock_file = "/tmp/LCK.test1";int n_fd = -1;int n_tries = 10;while(n_tries--){                //建立鎖檔案n_fd = open(lock_file, O_RDWR|O_CREAT|O_EXCL, 0444);if(n_fd == -1){                        //建立失敗printf("%d - Lock already present\n", getpid());sleep(2);}else{                        //建立成功printf("%d - I have exclusive access\n", getpid());sleep(1);close(n_fd);                        //刪除鎖檔案,釋放鎖unlink(lock_file);sleep(2);}}return 0;}
同時運行同一個程式的兩個執行個體,運行結果為:從啟動並執行結果可以看出兩個程式交叉地對對檔案進行鎖定,但是真實的操作卻是,每次調用open函數去檢查/tmp/LCK.test1這個檔案是否存在,如果存在open調用就失敗,顯示有進程已經把這個檔案鎖定了,如果這個檔案不存在,就建立這個檔案,並顯示許可資訊。但是這種做法有一定的缺憾,我們可以看到檔案/tmp/LCK.test1被建立了很多次,也被unlink刪除了很多次,也就是說我們不能使用已經事先有資料的檔案作為這種鎖檔案,因為如果檔案已經存在,則open調用總是失敗。給我的感覺是,這更像是一種對進程工作的協調性安排,更像是二進位訊號量的作用,檔案存在為0,不存在為1,而不是真正的檔案鎖定。三、地區鎖定我們還有一個問題,就是如果同一個檔案有多個進程需要對它進行讀寫,而一個檔案同一時間只能被一個進程進行寫操作,但是多個進程讀寫的地區互不相關,如果總是要等一個進程寫完其他的進程才能對其進行讀寫,效率又太低,那麼是否可以讓多個進程同時對檔案進行讀寫以提高資料讀寫的效率呢?為瞭解決上面提到的問題,和出現在第二點中的問題,即不能把檔案鎖定到指定的已存在的資料檔案上的問題,我們提出了一種新的解決方案,就是地區鎖定。簡單點來說,地區鎖定就是,檔案中的某個部分被鎖定了,但其他程式可以訪問這個檔案中的其他部分。然而,地區鎖定的建立和使用都比上面說的檔案鎖定複雜很多。1、建立地區鎖定在Linux上為實現這一功能,我們可以使用fcntl系統調用和lockf調用,但是下面以fcntl系統調用來講解地區鎖定的建立。fctnl的函數原理為:int fctnl(int fildes, int command, ...);它對一個開啟的檔案描述進行操作,並能根據command參數的設定完成不同的任務,它有三個可選的任務:F_GETLK,F_SETLK,F_SETLKW,至於這三個參數的意義下面再詳述。而當使用這些命令時,fcntl的第三個參數必須是一個指向flock結構的指標,所以在實際應用中,fctnl的函數原型一般為:int fctnl(int fildes, int command, struct
flock *flock_st);2、flock結構準確來說,flock結構依賴具體的實現,但是它至少包括下面的成員:short l_type;檔案鎖的類型,對應於F_RDLCK(讀鎖,也叫共用鎖定),F_UNLCK(解鎖,也叫清除鎖),F_WRLCK(寫鎖,也叫獨佔鎖)中的一個。short l_whence;從檔案的哪個相對位置開始計算,對應於SEEK_SET(檔案頭),SEEK_CUR(當前位置),SEEK_END(檔案尾)中的一個。off_t l_start;從l_whence開始的第l_start個位元組開始計算。off_t l_len;鎖定的地區的長度。pid_t l_pid;用來記錄參持有鎖的進程。成員l_whence、l_start和l_len定義了一個檔案中的一個地區,即一個連續的位元組集合,例如:struct flock region;region.l_whence = SEEK_SET;region.l_start = 10;region.l_len = 20;則表示fcntl函數伺服器用戶端檔案鎖的地區為檔案頭開始的第10到29個位元組之間的這20個位元組。3、檔案鎖的類型從上面的flock的成員l_type的取值我們可以知道,檔案鎖的類型主要有三種,這裡對他們進行詳細的解說。F_RDLCK:從它的名字我們就可以知道,它是一個讀鎖,也叫共用鎖定。許多不同的進程可以擁有檔案同一(或重疊)地區上的讀(共用)鎖。而且只要任一進程擁有一把讀(共用)鎖,那麼就沒有進程可以再獲得該地區上的寫(獨佔)鎖。為了獲得一把共用鎖定,檔案必須以“讀”或“讀/寫”方式開啟。簡單點來說就是,當一個進程在讀檔案中的資料時,檔案中的資料不能被改變或改寫,這是為了防止資料被改變而使讀資料的程式讀取到錯亂的資料,而檔案中的同一個地區能被多個進程同時讀取,這是容易理解的,因為讀不會破壞資料,或者說讀操作不會改變檔案的資料。F_WRLCK:從它的名字,我們就可以知道,它是一個寫鎖,也叫獨佔鎖。只有一個進程可以在檔案中的任一特定地區擁有一把寫(獨佔)鎖。一旦一個進程擁有了這樣一把鎖,任何其他進程都無法在該地區上獲得任何類型的鎖。為了獲得一把寫(獨佔)鎖,檔案也必須以“讀”或“讀/寫”方式開啟。簡單點來說,就是一個檔案同一地區(或重疊)地區進在同一時間,只能有一個進程能對其進行寫操作,並且在寫操作進行期間,其他的進程不能對該地區進行讀取資料。這個要求是顯然易見的,因為如果兩個進程同時對一個檔案進行寫操作,就會使檔案的內容錯亂起來,而由於寫時會改變檔案中的資料,所以它也不允許其他進程對檔案的資料進行讀取和刪除檔案等操作。F_UNLCK:從它的名字就可以知道,它用於把一個鎖定的地區解鎖。4、不同的command的意義在前面說到fcntl函數的command參數時,說了三個命令選項,這裡將對它們進行詳細的解說。F_GETLK命令,它用於擷取fildes(fcntl的第一個參數)開啟的檔案的鎖資訊,它不會嘗試去鎖定檔案,調用進程可以把自己想建立的鎖類型資訊傳遞給fcntl,函數調用就會返回將會阻止擷取鎖的任何資訊,即它可以測試你想建立的鎖是否能成功被建立。fcntl調用成功時,返回非-1,如果鎖請求可以成功執行,flock結構將保持不變,如果鎖請求被阻止,fcntl會用相關的資訊覆蓋flock結構。失敗時返回-1。所以,如果調用成功,調用程式則可以通過檢查flock結構的內容來判斷其是否被修改過,來檢查鎖請求能否被成功執行,而又因為l_pid的值會被設定成擁有鎖的進程的標識符,所以大多數情況下,可以通過檢查這個欄位是否發生變化來判斷flock結構是否被修改過。使用F_GETLK的fcntl函數調用後會立即返回。舉個例子來說,例如,有一個flock結構的變數,flock_st,flock_st.l_pid = -1,檔案的第10~29個位元組已經存在一個讀鎖,檔案的第40~49個位元組中已經存在一個寫鎖,則調用fcntl時,如果用F_GETLK命令,來測試在第10~29個位元組中是否可以建立一個讀鎖,因為這個鎖可以被建立,所以,fcntl返回非-1,同時,flock結構的內容也不會改變,flock_st.l_pid
= -1。而如果我們測試第40~49個位元組中是否可以建立一個寫鎖時,由於這個地區已經存在一個寫鎖,測試失敗,但是fcntl還是會返回非-1,只是flock結構會被這個地區相關的鎖的資訊覆蓋了,flock_st.l_pid為擁有這個寫鎖的進程的進程標識符。F_SETLK命令,這個命令試圖對fildes指向的檔案的某個地區加鎖或解鎖,它的功能根據flock結構的l_type的值而定。而對於這個命令來說,flock結構的l_pid欄位是沒有意義的。如果加鎖成功,返回非-1,如果失敗,則返回-1。使用F_SETLK的fcntl函數調用後會立即返回。F_SETLKW命令,這個命令與前面的F_SETLK,命令作用相同,但不同的是,它在無法擷取鎖時,即測試不能加鎖時,會一直等待直到可以被加鎖為止。5、例子看了這麼多的說明,可能你已經很亂了,就用下面的例子來整清你的思想吧。源檔案名稱為filelock2.c,用於建立資料檔案,並將檔案地區加鎖,代碼如下:
#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <fcntl.h>int main(){const char *test_file = "test_lock.txt";int file_desc = -1;int byte_count = 0;char *byte_to_write = "A";struct flock region_1;struct flock region_2;int res = 0;//開啟一個檔案描述符file_desc = open(test_file, O_RDWR|O_CREAT, 0666);if(!file_desc){fprintf(stderr, "Unable to open %s for read/write\n", test_file);exit(EXIT_FAILURE);}//給檔案添加100個‘A’字元的資料for(byte_count = 0; byte_count < 100; ++byte_count){write(file_desc, byte_to_write, 1);}//在檔案的第10~29位元組設定讀鎖(共用鎖定)region_1.l_type = F_RDLCK;region_1.l_whence = SEEK_SET;region_1.l_start = 10;region_1.l_len = 20;//在檔案的40~49位元組設定寫鎖(獨佔鎖)region_2.l_type = F_WRLCK;region_2.l_whence = SEEK_SET;region_2.l_start = 40;region_2.l_len = 10;printf("Process %d locking file\n", getpid());//鎖定檔案res = fcntl(file_desc, F_SETLK, ion_1);if(res == -1){fprintf(stderr, "Failed to lock region 1\n");}res = fcntl(file_desc, F_SETLK, ion_2);if(res == -1){fprintf(stderr, "Failed to lock region 2\n");}//讓程式休眠一分鐘,用於測試sleep(60);printf("Process %d closing file\n", getpid());close(file_desc);exit(EXIT_SUCCESS);}

下面的源檔案filelock3.c用於測試上一個檔案設定的鎖,測試可否對兩個地區都加上一個讀鎖,代碼如下:

#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <fcntl.h>int main(){const char *test_file = "test_lock.txt";int file_desc = -1;int byte_count = 0;char *byte_to_write = "A";struct flock region_1;struct flock region_2;int res = 0;//開啟資料檔案file_desc = open(test_file, O_RDWR|O_CREAT, 0666);if(!file_desc){fprintf(stderr, "Unable to open %s for read/write\n", test_file);exit(EXIT_FAILURE);}//設定地區1的鎖類型struct flock region_test1;region_test1.l_type = F_RDLCK;region_test1.l_whence = SEEK_SET;region_test1.l_start = 10;region_test1.l_len = 20;region_test1.l_pid = -1;//設定地區2的鎖類型struct flock region_test2;region_test2.l_type = F_RDLCK;region_test2.l_whence = SEEK_SET;region_test2.l_start = 40;region_test2.l_len = 10;region_test2.l_pid = -1;//對地區1的是否可以加一個讀鎖進行測試res = fcntl(file_desc, F_GETLK, ion_test1);if(res == -1){fprintf(stderr, "Failed to get RDLCK\n");}if(region_test1.l_pid == -1){//可以加一個讀鎖printf("test: Possess %d could lock\n", getpid());}else{//不允許加一個讀鎖printf("test:Prossess %d  get lock failure\n", getpid());}//對地區2是否可以加一個讀鎖進行測試res = fcntl(file_desc, F_GETLK, ion_test2);if(res == -1){fprintf(stderr, "Failed to get RDLCK\n");}if(region_test2.l_pid == -1){//可以加一個讀鎖printf("test: Possess %d could lock\n", getpid());}else{//不允許加一個讀鎖printf("test:Prossess %d  get lock failure\n", getpid());}exit(EXIT_SUCCESS);}

運行結果如下:
因為地區1中存在的是讀鎖,所以在其之上再加一個讀鎖是可以成功的,然而地區2上存在的鎖是寫鎖,在其上不能加任何類型的鎖,所以測試失敗。注意,測試失敗並不是fctnl調用失敗,它還是返回非-1,我們是通過檢查flock結構的成員l_pid來確定測試結果的。三、解空鎖問題如果我要給在本進程中沒有加鎖的地區解鎖會發生什麼事情呢?而如果這個地區中其他的進程有對其進行加鎖又會發生什麼情況呢?如果一個進程實際並未對一個地區進行鎖定,而調用解鎖操作也會成功,但是它並不能解其他的進程加在同一地區上的鎖。也可以說解鎖請求最終的結果取決於這個進程在檔案中設定的任何鎖,沒有加鎖,但對其進行解鎖得到的還是沒有加鎖的狀態。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.