Linux鎖的技術文檔
第一節Unix支持的文件鎖技術介紹
在多任務操作系統環境中,如果一個進程嘗試對正在被其他進程讀取的文件進行寫操作,可能會導致正在進行讀操作的進程讀取到一些被破壞或者不完整的數據;如果兩個進程並發對同一個文件進行寫操作,可能會導致該文件遭到破壞。因此,為了避免發生這種問題,必須要采用某種機制來解決多個進程並發訪問同一個文件時所面臨的同步問題,由此而產生了文件加鎖方面的技術。
早期的 UNIX 系統只支持對整個文件進行加鎖,因此無法運行數據庫之類的程序,因為此類程序需要實現記錄級的加鎖。在 System V Release 3 中,通過 fcntl 提供了記錄級的加鎖,此后發展成為 POSIX 標准的一部分。本文將基於 2.6.23 版本的內核來探討 Linux 中文件鎖的相關技術。
Linux 支持的文件鎖技術主要包括勸告鎖(advisory lock)和強制鎖(mandatory lock)這兩種。此外,Linux 中還引入了兩種強制鎖的變種形式:共享模式強制鎖(share-mode mandatory lock)和租借鎖(lease)。
1.1 讀鎖與寫鎖
Unix系統對文件加鎖有兩種粒度:文件鎖和記錄鎖,文件鎖用來鎖定整個文件,而記錄鎖可以鎖定文件的部分區域甚至一個字節,進程通過為文件設置多個記錄鎖,可以實現文件中不同區域的數據的讀寫同步,因此記錄鎖最為常見。
記錄鎖根據訪問方式的不同,又分為讀鎖和寫鎖。
讀鎖允許多個進程同時進行讀操作,也稱共享鎖。文件加了讀鎖就不能再設置寫鎖,但仍允許其他進程在同一區域再設置讀鎖。
寫鎖的主要目的是隔離文件使所寫內容不被其他進程的讀寫干擾,以保證數據的完整性。寫鎖一旦加上,只有上鎖的人可以操作,其他進程無論讀還是寫只有等待寫鎖釋放后才能執行,故寫鎖又稱互斥鎖,寫鎖與任何鎖都必須互斥使用。
在 Linux 中,不論進程是在使用勸告鎖還是強制鎖,它都可以同時使用共享鎖和排他鎖(又稱為讀鎖和寫鎖)。
|
|
是否滿足請求 |
|
| 當前加上的鎖 |
共享鎖(讀鎖) |
排他鎖(寫鎖) |
| 無 |
是 |
是 |
| 共享鎖(讀鎖) |
是 |
否 |
| 排他鎖(寫鎖) |
否 |
否 |
表1. 鎖間的兼容關系
1.2 建議鎖和強制鎖
Unix文件鎖根據實現機制的不同,又可分為建議鎖和強制鎖兩種類型。建議鎖由應用層實現,內核只為用戶提供程序接口,並不參與鎖的控制和協調,也不對讀寫操作做內部檢查和強制保護,也就是說,如果有進程不遵守“游戲規則”,不檢查目標文件是否已經由別的進程加了鎖就往其中寫入數據,那么內核是不會加以阻攔的。因此,勸告鎖並不能阻止進程對文件的訪問,而只能依靠各個進程在訪問文件之前檢查該文件是否已經被其他進程加鎖來實現並發控制。進程需要事先對鎖的狀態做一個約定,並根據鎖的當前狀態和相互關系來確定其他進程是否能對文件執行指定的操作。從這點上來說,勸告鎖的工作方式與使用信號量保護臨界區的方式非常類似。
強制鎖則由內核強制實施,每當有進程調用read或write時,內核都要檢查讀寫操作是否與已加的鎖沖突,如果沖突,阻塞方式下該進程將被阻塞直到鎖被釋放,非阻塞方式下系統將立即以錯誤返回。顯然,使用強制鎖來控制對已鎖文件或文件區域的訪問,是更安全可靠的同步形式,適用於網絡連接、終端或串並行端口之類須獨占使用的設備文件,因為對用戶都可讀的文件加一把強制讀鎖,就能使其他人不能再寫該文件,從而保證了設備的獨占使用。
由於強制鎖運行在內核空間,處理機從用戶空間切換到內核空間,系統開銷大,影響性能,所以應用程序很少使用。建議鎖開銷小,可移植性好,符合POSIX標准的文件鎖實現,在數據庫系統中應用廣泛,特別是當多個進程交叉讀寫文件的不同部分時,建議鎖有更好的並行性和實時性。
| 文件存在鎖的類型 |
阻塞描述符,試圖 |
非阻塞描述符,試圖 |
||
| read |
write |
read |
write |
|
| 讀鎖 |
允許 |
阻塞 |
允許 |
EAGAIN錯誤 |
| 寫鎖 |
阻塞 |
阻塞 |
EAGAIN錯誤 |
EAGAIN錯誤 |
表2 強制性鎖對其他進程讀、寫的影響
| 系統 |
建議性鎖 |
強制性鎖 |
| Linux2.4.22 |
支持 |
支持 |
| Solaris 9 |
支持 |
支持 |
| Mac OS X 10.3 |
支持 |
不支持 |
| FreeBSD 5.2.1 |
支持 |
不支持 |
表3 不同系統的支持情況
另外,unlink() 系統調用並不會受到強制鎖的影響,原因在於一個文件可能存在多個硬鏈接,此時刪除文件時並不會修改文件本身的內容,而是只會改變其父目錄中 dentry 的內容。
然而,在有些應用中並不適合使用強制鎖,所以索引節點結構中的 i_flags 字段中定義了一個標志位MS_MANDLOCK用於有選擇地允許或者不允許對一個文件使用強制鎖。在 super_block 結構中,也可以將 s_flags 這個標志為設置為1或者0,用以表示整個設備上的文件是否允許使用強制鎖,1允許,0不允許。
要想對一個文件采用強制鎖,必須按照以下步驟執行:
使用 -o mand 選項來掛載文件系統。這樣在執行 mount() 系統調用時,會傳入 MS_MANDLOCK 標記,從而將 super_block 結構中的 s_flags 設置為 1,用來表示在這個文件系統上可以采用強制鎖。例如:
# mount -o mand /dev/sdb7 /mnt # mount | grep mnt /dev/sdb7 on /mnt type ext3 (rw,mand)
1.修改要加強制鎖的文件的權限:設置 SGID 位,並清除組可執行位。這種組合通常來說是毫無意義的,系統用來表示該文件被加了強制鎖。例如:
# touch /mnt/testfile # ls -l /mnt/testfile -rw-r--r-- 1 root root 0 Jun 22 14:43 /mnt/testfile # chmod g+s /mnt/testfile # chmod g-x /mnt/testfile # ls -l /mnt/testfile -rw-r-Sr-- 1 root root 0 Jun 22 14:43 /mnt/testfile
2.使用 fcntl() 系統調用對該文件進行加鎖或解鎖操作。
共享模式鎖
Linux 中還引入了兩種特殊的文件鎖:共享模式強制鎖和租借鎖。這兩種文件鎖可以被看成是強制鎖的兩種變種形式。共享模式強制鎖可以用於某些私有網絡文件系統,如果某個文件被加上了共享模式強制鎖,那么其他進程打開該文件的時候不能與該文件的共享模式強制鎖所設置的訪問模式相沖突。但是由於可移植性不好,因此並不建議使用這種鎖。
租借鎖
采用強制鎖之后,如果一個進程對某個文件擁有寫鎖,只要它不釋放這個鎖,就會導致訪問該文件的其他進程全部被阻塞或不斷失敗重試;即使該進程只擁有讀鎖,也會造成后續更新該文件的進程的阻塞。為了解決這個問題,Linux 中采用了一種新型的租借鎖。
當進程嘗試打開一個被租借鎖保護的文件時,該進程會被阻塞,同時,在一定時間內擁有該文件租借鎖的進程會收到一個信號。收到信號之后,擁有該文件租借鎖的進程會首先更新文件,從而保證了文件內容的一致性,接着,該進程釋放這個租借鎖。如果擁有租借鎖的進程在一定的時間間隔內沒有完成工作,內核就會自動刪除這個租借鎖或者將該鎖進行降級,從而允許被阻塞的進程繼續工作。
系統默認的這段間隔時間是 45 秒鍾,定義如下:
137 int lease_break_time = 45;
這個參數可以通過修改 /proc/sys/fs/lease-break-time 進行調節(當然,/proc/sys/fs/leases-enable 必須為 1 才行)。
第二節 文件鎖的總體實現和設置
2.1 內核相關的數據結構
Unix文件鎖是在共享索引節點共享文件的情況下設計的,因此與鎖機制相關的內核數據結構主要有虛擬索引節點(V-node)、系統打開文件表、進程打開文件表等數據表項。
其中V-node中的索引節點(i-node)中含有一個指向鎖鏈表的首指針,該鎖鏈表主要由對文件所加的全部的鎖通過指針鈎鏈而成。只要進程為文件或區域加鎖成功,內核就創建鎖結構file_lock,並根據用戶設定的參數初始化后將其插入鎖鏈表flock中。flock是鎖存在的唯一標志,也是內核感知、管理及控制文件鎖的主要依據,其結構和成員參見下面的struct flock。
如下所示,描述了進程為打開的文件設置文件鎖的內核相關表項及各數據結構之間的關聯,從中可以看出,鎖同時與進程和文件相關,當進程終止或文件退出時即使是意外退出,進程對文件所加的鎖將全部釋放。
在 Linux 內核中,所有類型的文件鎖都是由數據結構 file_lock 來描述的,file_lock 結構是在文件中定義的,如下所示:
-
struct file_lock {
-
struct file_lock *fl_next; /* singly linked list for this inode */
-
struct list_head fl_link; /* doubly linked list of all locks */
-
struct list_head fl_block; /* circular list of blocked processes */
-
fl_owner_t fl_owner;
-
unsigned int fl_pid;
-
wait_queue_head_t fl_wait;
-
struct file *fl_file;
-
unsigned char fl_flags;
-
unsigned char fl_type;
-
loff_t fl_start;
-
loff_t fl_end;
-
-
struct fasync_struct * fl_fasync; /* for lease break notifications */
-
unsigned long fl_break_time; /* for nonblocking lease breaks */
-
-
struct file_lock_operations *fl_ops; /* Callbacks for filesystems */
-
struct lock_manager_operations *fl_lmops; /* Callbacks for lockmanagers */
-
union {
-
struct nfs_lock_info nfs_fl;
-
struct nfs4_lock_info nfs4_fl;
-
struct {
-
struct list_head link; /* link in AFS vnode's pending_locks list */
-
int state; /* state of grant or error if -ve */
-
} afs;
-
} fl_u;
-
};
-
-
-
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) */
-
...
-
};
file_lock 數據結構的字段
| 類型 | 字段 | 字段描述 |
|---|---|---|
| struct file_lock* | fl_next | 與索引節點相關的鎖列表中下一個元素 |
| struct list_head | fl_link | 指向活躍列表或者被阻塞列表 |
| struct list_head | fl_block | 指向鎖等待列表 |
| struct files_struct * | fl_owner | 鎖擁有者的 files_struct |
| unsigned int | fl_pid | 進程擁有者的 pid |
| wait_queue_head_t | fl_wait | 被阻塞進程的等待隊列 |
| struct file * | fl_file | 指向文件對象 |
| unsigned char | fl_flags | 鎖標識 |
| unsigned char | fl_type | 鎖類型 |
| loff_t | fl_start | 被鎖區域的開始位移 |
| loff_t | fl_end | 被鎖區域的結束位移 |
| struct fasync_struct * | fl_fasync | 用於租借暫停通知 |
| unsigned long | fl_break_time | 租借的剩余時間 |
| struct file_lock_operations * | fl_ops | 指向文件鎖操作 |
| struct lock_manager_operations * | fl_mops | 指向鎖管理操作 |
| union | fl_u | 文件系統特定信息 |
一個 file_lock 結構就是一把“鎖”,結構中的 fl_file 就指向目標文件的 file 結構,而 fl_start 和 fl_end 則確定了該文件要加鎖的一個區域。
當進程發出系統調用來請求對某個文件加排他鎖時,如果這個文件上已經加上了共享鎖,那么排他鎖請求不能被立即滿足,這個進程必須先要被阻塞。這樣,這個進程就被放進了等待隊列,file_lock 結構中的 fl_wait 字段就指向這個等待隊列。指向磁盤上相同文件的所有 file_lock 結構會被鏈接成一個單鏈表 file_lock_list,索引節點結構中的 i_flock 字段會指向該單鏈表結構的首元素,fl_next 用於指向該鏈表中的下一個元素;當前系統中所有被請求,但是未被允許的鎖被串成一個鏈表:blocked_list。fl_link 字段指向這兩個列表其中一個。對於被阻塞列表(blocked_list)上的每一個鎖結構來說,fl_next 字段指向與該鎖產生沖突的當前正在使用的鎖。所有在等待同一個鎖的那些鎖會被鏈接起來,這就需要用到字段 fl_block,新來的等待者會被加入到等待列表的尾部。 此外,fl_type 表示鎖的性質,如讀、寫。fl_flags 是一些標志位,在 linux 2.6中,這些標志位的定義如下所示:
清單 2. 標志位的定義
773 #define FL_POSIX 1 774 #define FL_FLOCK 2 775 #define FL_ACCESS 8 /* not trying to lock, just looking */ 776 #define FL_EXISTS 16 /* when unlocking, test for existence */ 777 #define FL_LEASE 32 /* lease held on this file */ 778 #define FL_CLOSE 64 /* unlock on close */ 779 #define FL_SLEEP 128 /* A blocking lock */
FL_POSIX 鎖是通過系統調用 fcntl() 創建的;而 FL_FLOCK 鎖是通過系統調用 flock()創建的(詳細內容請參見后文中的介紹)。FL_FLOCK 鎖永遠都和一個文件對象相關聯,打開這個文件的進程擁有該 FL_FLOCK 鎖。當一個鎖被請求或者允許的時候,內核就會把這個進程在同一個文件上的鎖都替換掉。FL_POSIX 鎖則一直與一個進程以及一個索引節點相關聯。當進程死亡或者文件描述符被關閉的時候,這個鎖會被自動釋放。
2.2 鎖的使用
Unix提供了3個文件鎖相關的系統調用:flock、lockf和fcntl。其中fcntl功能強大,使用靈活,移植性好,既支持建議式記錄鎖也支持強制式記錄鎖,函數原型定義為:int fcntl(int fd,int cmd,int arg),參數fd表示需要加鎖的文件的描述符;參數cmd指定要進行的鎖操作,如設置、解除及測試鎖等;參數arg是指向鎖結構flock的指針。
無論哪種類型的文件鎖,使用的流程是確定的:首先以匹配的方式打開文件,然后各進程調用上鎖操作同步對已鎖區域的讀寫,讀或寫完成時再調用解鎖操作,最后關閉文件。鎖操作代碼的編寫方法基本類似,首先形成適當的flock結構,然后調用fcntl完成實際的鎖操作。為避免每次分配flock並填充各成員的重復工作,可以預先定義專門的函數來完成頻繁調用的上鎖和解鎖操作。下列代碼就是為一個文件設置讀鎖的實例。
int set_read_lock(intfd, int cmd, int type, off_t start, int whence, off_t len)
{
struct flock lock;
lock.l_type=F_RDLCK;
lock.l_pid=getpid();
lock.l_whence=SEEK_SET;
lock.l_start=0;
lock.l_len=0;
return (fcntl(fd, F_SETLKW, &lock));
}
2.3 強制性鎖的設置
強制性鎖的設置需要兩個步驟:第一,對要加鎖的文件需要設置相關權限,打開set-group-ID位,關閉group-execute位。第二,讓linux支持強制性鎖,需要執行命令,掛載文件系統, mount /dev/sda1 /mnt –o mand。
# mount -o mand/dev/sdb7 /mnt
# mount | grep mnt
/dev/sdb7 on /mnt typeext3 (rw,mand)
# touch /mnt/testfile
# ls -l /mnt/testfile
-rw-r--r-- 1 root root 0 Jun 22 14:43 /mnt/testfile
# chmod g+s /mnt/testfile
# chmod g-x /mnt/testfile
# ls -l /mnt/testfile
-rw-r-Sr-- 1 root root 0 Jun 22 14:43 /mnt/testfile
附件中的測試程序(lock.c)可以測試是否支持強制性鎖。
2.4 設置強制性鎖注意問題
在設置強制性鎖注意的問題:幾乎所有的操作都需要root權限。
1. 首先,su到root權限下,輸入:mount 可以看到掛載的所有內容,如果所用磁盤存在其他掛載路徑,要讓mand成功,必須是:所用磁盤(sda1)被掛載的方式有mand存在。
比如存在: /dev/sda1 on /boot type ext3(rw)
/dev/sda1 on /opt/327/XXX typeext3(rw, mand)
則不成功。
需要: /dev/sda1 on /boot typeext3(rw, mand)
/dev/sda1 on /opt/327/XXX type ext3(rw,mand)
2.mount /dev/sda1 /opt/327/XXX/ -o mand,把sda1磁盤掛載到/opt/327/XXX/目錄下,此時,把新文件存在opt/327/XXX/下,實際是存在sda1磁盤中。
3. 此時/opt/327/XXX/的權限變成了root,只用root權限的用戶才能修改這個文件夾下的內容。
第三節 三種加鎖方法詳細介紹和linux內部實現
3.1 Posix lock加鎖的方法(fcntl):
3.1.1使用介紹:
fcntl() 函數的功能很多,可以改變已打開的文件的性質,本文中只是介紹其與獲取/設置文件鎖有關的功能。fcntl() 的函數原型如下所示:
| int fcntl (int fd, int cmd, struct flock *lock); |
其中,參數 fd 表示文件描述符;參數 cmd 指定要進行的鎖操作,由於 fcntl() 函數功能比較多,這里先介紹與文件鎖相關的三個取值 F_GETLK、F_SETLK 以及 F_SETLKW。這三個值均與 flock 結構有關。flock 結構如下所示:
flock 結構:
| 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) */ ... }; |
在 flock 結構中,l_type 用來指明創建的是共享鎖還是排他鎖,其取值有三種:F_RDLCK(共享鎖)、F_WRLCK(排他鎖)和F_UNLCK(刪除之前建立的鎖);l_pid 指明了該鎖的擁有者;l_whence、l_start 和l_end 這些字段指明了進程需要對文件的哪個區域進行加鎖,這個區域是一個連續的字節集合。因此,進程可以對同一個文件的不同部分加不同的鎖。l_whence 必須是 SEEK_SET、SEEK_CUR 或 SEEK_END 這幾個值中的一個,它們分別對應着文件頭、當前位置和文件尾。l_whence 定義了相對於 l_start 的偏移量,l_start 是從文件開始計算的。
可以執行的操作包括:
- F_GETLK:進程可以通過它來獲取通過 fd 打開的那個文件的加鎖信息。執行該操作時,lock 指向的結構中就保存了希望對文件加的鎖(或者說要查詢的鎖)。如果確實存在這樣一把鎖,它阻止 lock 指向的 flock 結構所給出的鎖描述符,則把現存的鎖的信息寫到 lock 指向的 flock 結構中,並將該鎖擁有者的 PID 寫入 l_pid 字段中,然后返回;否則,就將 lock 指向的 flock 結構中的 l_type 設置為 F_UNLCK,並保持 flock 結構中其他信息不變返回,而不會對該文件真正加鎖。
- F_SETLK:進程用它來對文件的某個區域進行加鎖(l_type的值為 F_RDLCK 或 F_WRLCK)或者刪除鎖(l_type 的值為F_UNLCK),如果有其他鎖阻止該鎖被建立,那么 fcntl() 就出錯返回
- F_SETLKW:與 F_SETLK 類似,唯一不同的是,如果有其他鎖阻止該鎖被建立,則調用進程進入睡眠狀態,等待該鎖釋放。一旦這個調用開始了等待,就只有在能夠進行加鎖或者收到信號時才會返回
需要注意的是,F_GETLK 用於測試是否可以加鎖,在 F_GETLK 測試可以加鎖之后,F_SETLK 和 F_SETLKW 就會企圖建立一把鎖,但是這兩者之間並不是一個原子操作,也就是說,在 F_SETLK 或者 F_SETLKW 還沒有成功加鎖之前,另外一個進程就有可能已經插進來加上了一把鎖。而且,F_SETLKW 有可能導致程序長時間睡眠。還有,程序對某個文件擁有的各種鎖會在相應的文件描述符被關閉時自動清除,程序運行結束后,其所加的各種鎖也會自動清除。
fcntl() 既可以用於勸告鎖,也可以用於強制鎖,在默認情況下,它用於勸告鎖。如果它用於強制鎖,當進程對某個文件進行了讀或寫這樣的系統調用時,系統則會檢查該文件的鎖的 O_NONBLOCK 標識,該標識是文件狀態標識的一種,如果設置文件狀態標識的時候設置了 O_NONBLOCK,則該進程會出錯返回;否則,該進程被阻塞。cmd 參數的值 F_SETFL 可以用於設置文件狀態標識。
3.1.2 Posix lock 內部機制:
1) 調用fcntl(fd, F_SETLK, &lock), fcntl()方法調用內核的do_fcntl()方法(linux/fs/fcntl.c)
static long do_fcntl(unsigned int fd, unsigned intcmd, unsigned long arg, struct file * filp)
{
switch (cmd) {
.....
case F_GETLK: /*Posix Lock 操作*/
err = fcntl_getlk(fd, (struct flock *) arg);
break;
case F_SETLK:
case F_SETLKW:
err = fcntl_setlk(fd, cmd, (struct flock *) arg);
break;
...
}
2) do_fcntl()里調用fcntl_setlk()(linux/fs/flock.c)
int fcntl_setlk(unsigned int fd, struct file *filp,unsigned int cmd, struct flock __user *l)
{
structfile_lock *file_lock = locks_alloc_lock();
struct flock flock;
......
//把把客戶傳來的flock __user l拷貝為flock
if (copy_from_user(&flock, l, sizeof(flock)))
goto out;
//把客戶傳來的flock的鎖l變為file_lock
error =flock_to_posix_lock(filp, file_lock, &flock);
......
//阻塞的設置
if (cmd ==F_SETLKW) {
file_lock->fl_flags|= FL_SLEEP;
}
error = -EBADF;
//設置不同類型的鎖信息
switch(flock.l_type) {
case F_RDLCK:
if(!(filp->f_mode & FMODE_READ))
gotoout;
break;
case F_WRLCK:
if(!(filp->f_mode & FMODE_WRITE))
gotoout;
break;
case F_UNLCK:
break;
default:
error =-EINVAL;
goto out;
//鎖文件filp
error = do_lock_file_wait(filp, cmd, file_lock);
spin_lock(¤t->files->file_lock);
f = fcheck(fd);
spin_unlock(¤t->files->file_lock);
......
}
3) fcntl_setlk()調用do_lock_file_wait()
static int do_lock_file_wait(struct file *filp, unsignedint cmd, struct file_lock *fl)
{
......
error =security_file_lock(filp, fl->fl_type);
......
//關鍵
error =vfs_lock_file(filp, cmd, fl, NULL);
......
}
4) do_lock_file_wait()調用vfs_lock_file()
int vfs_lock_file(struct file *filp, unsigned int cmd,struct file_lock *fl, struct file_lock *conf)
{
if (filp->f_op&& filp->f_op->lock)
returnfilp->f_op->lock(filp, cmd, fl);
else
//關鍵
returnposix_lock_file(filp, fl, conf);
}
5) vfs_lock_file()調用posix_lock_file()
int posix_lock_file(struct file *filp, struct file_lock*fl, struct file_lock *conflock)
{
return__posix_lock_file(filp->f_path.dentry->d_inode, fl, conflock);
}
6) posix_lock_file()調用__posix_lock_file()
__posix_lock_file()是真正鎖文件的函數,它具有檢測沖突鎖的功能。具體代碼看locks.c中的__posix_lock_file()。
大致流程是這樣:
遍歷inode->i_flock,找是POSIX鎖且有沖突的鎖(用posix_locks_conflict()函數),若存在或者有阻塞標志或者死鎖了,做一定處理退出;若不存在這樣的鎖,就可以鎖文件。再次遍歷inode->i_flock,找到屬於自己進程的鎖,如果檢測到了鎖並判斷鎖類型,處理記錄相交的部分,設置鎖屬性,最后按照一定方式插入鎖;如果未檢測到鎖,則插入鎖。
3.1.3兩種鎖檢測沖突:
· 建議鎖(Advisory lock)
系統默認的鎖,檢測兩鎖是否沖突的函數:
posix_locks_conflict(structfile_lock *caller_fl, struct file_lock *sys_fl)函數過程:
先判斷兩把鎖是否屬於同一進程,自己不會和自己沖突。
如果它們兩是不同進程的鎖,判斷是否有鎖定區域重合。
· 強制性鎖(Mandatory lock)
檢測強制性鎖沖突的函數,滿足條件則加鎖,locks_mandatory_area()函數過程:
函數里調用了posix_locks_conflict()用於判斷沖突鎖,調用__posix_lock_file()函數用於加鎖。
3.1.3 說明一個問題:如果文件被加了強制性鎖,為什么系統調用read(),write()就會受到限制?
看系統調用的read的實現方法:
1) 內核函數 sys_read() 是 read 系統調用在該層的入口點(read_write.c):
SYSCALL_DEFINE3(read, unsigned int, fd,char __user *, buf, size_t, count)
2) Sys_read()里調用vfs_read()(read_write.c)
3) vfs_read()里調用rw_verify_area(READ,file, pos, count);
4) rw_verify_area()調用了mandatory_lock()和locks_mandatory_area()(mandatory_lock在fs.h中,locks_mandatory_area在locks.c中)
5) mandatory_lock()調用了IS_MANDLOCK()和__mandatory_lock(),其中,IS_MANDLOCK()判斷文件系統是否有MS_MANDLOCK,__mandatory_lock()判斷文件是否setgid和去掉組執行位
6) locks_mandatory_area()檢查是否有鎖沖突,若有沖突,讀操作出錯。
結果:所以系統調用read就不能在有強制性鎖的情況下讀文件。Write和open是類似的道理。
3.2 flock的加鎖方法(flock):
3.2.1 使用介紹:
flock() 的函數原型如下所示:
| int flock(int fd, int operation); |
其中,參數 fd 表示文件描述符;參數 operation 指定要進行的鎖操作,該參數的取值有如下幾種:LOCK_SH, LOCK_EX, LOCK_UN 和 LOCK_MANDphost2008-07-03T00:00:00
man page 里面沒有提到,其各自的意思如下所示:
- LOCK_SH:表示要創建一個共享鎖,在任意時間內,一個文件的共享鎖可以被多個進程擁有
- LOCK_EX:表示創建一個排他鎖,在任意時間內,一個文件的排他鎖只能被一個進程擁有
- LOCK_UN:表示刪除該進程創建的鎖
- LOCK_MAND:它主要是用於共享模式強制鎖,它可以與 LOCK_READ 或者 LOCK_WRITE 聯合起來使用,從而表示是否允許並發的讀操作或者並發的寫操作(盡管在 flock() 的手冊頁中沒有介紹 LOCK_MAND,但是閱讀內核源代碼就會發現,這在內核中已經實現了)
通常情況下,如果加鎖請求不能被立即滿足,那么系統調用 flock() 會阻塞當前進程。比如,進程想要請求一個排他鎖,但此時,已經由其他進程獲取了這個鎖,那么該進程將會被阻塞。如果想要在沒有獲得這個排他鎖的情況下不阻塞該進程,可以將 LOCK_NB 和 LOCK_SH 或者 LOCK_EX 聯合使用,那么系統就不會阻塞該進程。flock() 所加的鎖會對整個文件起作用。
說明:共享模式強制鎖可以用於某些私有網絡文件系統,如果某個文件被加上了共享模式強制鎖,那么其他進程打開該文件的時候不能與該文件的共享模式強制鎖所設置的訪問模式相沖突。但是由於可移植性不好,因此並不建議使用這種鎖。
3.2.2 flock的內部機制:
flock和posix lock都共享一組機制,系統調用fcntl() 符合 POSIX 標准的文件鎖實現,功能比flock強大,可以支持記錄鎖。flock() 系統調用是從 BSD 中衍生出來的,只能支持整個文件的鎖。
flock_lock_file()與posix lock機制類似,詳細看代碼(locks.c)
3.3 lease鎖的方法:
3.3.1使用說明:
系統調用 fcntl() 還可以用於租借鎖,此時采用的函數原型如下:
| int fcntl(int fd, int cmd, long arg); |
與租借鎖相關的 cmd 參數的取值有兩種:F_SETLEASE 和 F_GETLEASE。其含義如下所示:
- F_SETLEASE:根據下面所描述的 arg 參數指定的值來建立或者刪除租約:
- F_RDLCK:設置讀租約。當文件被另一個進程以寫的方式打開時,擁有該租約的當前進程會收到通知
- F_WRLCK:設置寫租約。當文件被另一個進程以讀或者寫的方式打開時,擁有該租約的當前進程會收到通知
- F_UNLCK:刪除以前建立的租約
- F_GETLEASE:表明調用進程擁有文件上哪種類型的鎖,這需要通過返回值來確定,返回值有三種:F_RDLCK、F_WRLCK和F_UNLCK,分別表明調用進程對文件擁有讀租借、寫租借或者根本沒有租借
某個進程可能會對文件執行其他一些系統調用(比如 OPEN() 或者 TRUNCATE()),如果這些系統調用與該文件上由 F_SETLEASE 所設置的租借鎖相沖突,內核就會阻塞這個系統調用;同時,內核會給擁有這個租借鎖的進程發信號,告知此事。擁有此租借鎖的進程會對該信號進行反饋,它可能會刪除這個租借鎖,也可能會減短這個租借鎖的租約,從而可以使得該文件可以被其他進程所訪問。如果擁有租借鎖的進程不能在給定時間內完成上述操作,那么系統會強制幫它完成。通過 F_SETLEASE 命令將 arg 參數指定為 F_UNLCK 就可以刪除這個租借鎖。不管對該租借鎖減短租約或者干脆刪除的操作是進程自願的還是內核強迫的,只要被阻塞的系統調用還沒有被發出該調用的進程解除阻塞,那么系統就會允許這個系統調用執行。即使被阻塞的系統調用因為某些原因被解除阻塞,但是上面對租借鎖減短租約或者刪除這個過程還是會執行的。
需要注意的是,租借鎖也只能對整個文件生效,而無法實現記錄級的加鎖。
采用強制鎖之后,如果一個進程對某個文件擁有寫鎖,只要它不釋放這個鎖,就會導致訪問該文件的其他進程全部被阻塞或不斷失敗重試;即使該進程只擁有讀鎖,也會造成后續更新該文件的進程的阻塞。為了解決這個問題,Linux 中采用了一種新型的租借鎖。
當進程嘗試打開一個被租借鎖保護的文件時,該進程會被阻塞,同時,在一定時間內擁有該文件租借鎖的進程會收到一個信號。收到信號之后,擁有該文件租借鎖的進程會首先更新文件,從而保證了文件內容的一致性,接着,該進程釋放這個租借鎖。如果擁有租借鎖的進程在一定的時間間隔內沒有完成工作,內核就會自動刪除這個租借鎖或者將該鎖進行降級,從而允許被阻塞的進程繼續工作。
系統默認的這段間隔時間是 45 秒鍾,定義如下:
| 137 int lease_break_time = 45; |
這個參數可以通過修改 /proc/sys/fs/lease-break-time 進行調節(當然,/proc/sys/fs/leases-enable 必須為 1 才行)。
3.3.2 leaseLock內部實現:
在fs/locks.c中:
fcntl_getlease():查詢目前活躍的租借鎖
fcntl_setlease():為打開的文件設置一個租借鎖
__break_lease():這個函數是在open和truncate的時候進行的租約判定
第四節 測試結果
4.1 JAVA鎖、C鎖和系統之間的關系
JAVA是用文件Channel中的tryLock()和lock()方法來對文件加鎖。
下表是在建議鎖的情況下的測試結果:
| JAVA和JAVA之間的鎖: l Test1程序先鎖住一個文件file1,Test2可以檢測到file1上有鎖。 l 在windows下,Test1程序先鎖住一個文件file1,Test2不可以直接讀寫文件,報異常:IOException。 l 在linux下,Test1程序先鎖住一個文件file1,Test2可以直接讀寫文件! |
| JAVA和C之間的鎖: l 如果文件被加鎖,相互可以檢測到鎖的存在,但仍然可以直接往文件寫數據。 |
| JAVA和系統之間的鎖: l 文件被加鎖,cp和vi等系統命令,仍然可以對文件進行讀寫。Vi很多版本用的是勸告鎖。 |
下表是在強制性鎖的情況下的測試結果:
| JAVA和JAVA之間的鎖: l Java程序Test1先鎖住一個文件file1,Java程序Test2(lock)去鎖file1,則Test2會阻塞,直到Test2獲得鎖。 l Java程序Test1先鎖住一個文件file1,Java程序Test2(tryLock)去鎖file1,則Test2會報異常:FileNotFoundException。 l Java程序Test1先鎖住一個文件file1,Java程序Test2去讀file1文件,則Test2被阻塞,直到Test2獲得鎖,讀操作成功。 l Java程序Test1先鎖住一個文件file1,Java程序Test2去寫file1文件,則Test2被阻塞,直到Test2獲得鎖,寫操作成功。 |
| JAVA和C之間的鎖: l 如果文件被c程序加鎖,java程序讀文件被阻塞,直到java程序獲得鎖,讀取成功。 l 如果文件被c程序加鎖,java程序寫文件被阻塞,直到java程序獲得鎖,寫入成功。 l 如果文件被java程序加鎖,c程序讀文件被阻塞,直到c程序獲得鎖,讀取成功。 l 如果文件被java程序加鎖,c程序寫文件被阻塞,直到c程序獲得鎖,寫入成功。
l 如果文件已被c程序加鎖,java程序用lock()方法也去鎖文件,java程序會阻塞,直到java程序獲得鎖。 l 如果文件已被c程序加鎖,java程序用tryLock()方法也去鎖文件,java程序報異常:FileNotFoundException。 l 如果文件已被java程序加鎖,c程序用F_SETLK方法也去鎖文件,c程序報打開錯誤(因為去鎖文件,都需要有open獲得文件描述符fd)。 l 如果文件已被java程序加鎖,c程序用F_SETLKW方法也去鎖文件,c程序報打開錯誤(因為去鎖文件,都需要有open獲得文件描述符fd)。 |
| JAVA和系統之間的鎖: l cp命令: 1. java程序鎖住文件file1,系統命令cp file2 file1,返回錯誤: cannot create regular file `file1`: Resource temporarily unavailable 2. java程序鎖住文件file1,系統命令cp file1 file2,被阻塞,直到java程序獲得鎖,cp方可成功。 l vi命令: java程序鎖住文件file1,此時vi file1,被阻塞,直到vi獲得鎖,才可打開文件。 |
http://blog.csdn.net/yebanghua/article/details/7301904
參考文獻:
http://www.ibm.com/developerworks/cn/linux/l-cn-filelock/
http://www.linuxpk.com/archiver/tid-13992.html
