本文的目的是想幫助讀者理清 Linux 2.6中文件鎖的概念以及 Linux 2.6 都提供了何種數(shù)據(jù)結(jié)構(gòu)以及關(guān)鍵的系統(tǒng)調(diào)用來實(shí)現(xiàn)文件鎖,從而可以幫助讀者更好地使用文件鎖來解決多個進(jìn)程讀取同一個文件的互斥問題。本文主要描述了 Linux 中各類文件鎖的概念,使用場景,內(nèi)核中描述文件鎖的數(shù)據(jù)結(jié)構(gòu)以及與文件鎖密切相關(guān)的系統(tǒng)調(diào)用等內(nèi)容。
在多任務(wù)操作系統(tǒng)環(huán)境中,如果一個進(jìn)程嘗試對正在被其他進(jìn)程讀取的文件進(jìn)行寫操作,可能會導(dǎo)致正在進(jìn)行讀操作的進(jìn)程讀取到一些被破壞或者不完整的數(shù)據(jù);如果兩個進(jìn)程并發(fā)對同一個文件進(jìn)行寫操作,可能會導(dǎo)致該文件遭到破壞。因此,為了避免發(fā)生這種問題,必須要采用某種機(jī)制來解決多個進(jìn)程并發(fā)訪問同一個文件時所面臨的同步問題,由此而產(chǎn)生了文件加鎖方面的技術(shù)。
早期的 UNIX 系統(tǒng)只支持對整個文件進(jìn)行加鎖,因此無法運(yùn)行數(shù)據(jù)庫之類的程序,因為此類程序需要實(shí)現(xiàn)記錄級的加鎖。在 System V Release 3 中,通過 fcntl 提供了記錄級的加鎖,此后發(fā)展成為 POSIX 標(biāo)準(zhǔn)的一部分。本文將基于 2.6.23 版本的內(nèi)核來探討 Linux 中文件鎖的相關(guān)技術(shù)。
Linux 支持的文件鎖技術(shù)主要包括勸告鎖(advisory lock)和強(qiáng)制鎖(mandatory lock)這兩種。此外,Linux 中還引入了兩種強(qiáng)制鎖的變種形式:共享模式強(qiáng)制鎖(share-mode mandatory lock)和租借鎖(lease)。
在 Linux 中,不論進(jìn)程是在使用勸告鎖還是強(qiáng)制鎖,它都可以同時使用共享鎖和排他鎖(又稱為讀鎖和寫鎖)。多個共享鎖之間不會相互干擾,多個進(jìn)程在同一時刻可以對同一個文件加共享鎖。但是,如果一個進(jìn)程對該文件加了排他鎖,那么其他進(jìn)程則無權(quán)再對該文件加共享鎖或者排他鎖,直到該排他鎖被釋放。所以,對于同一個文件來說,它可以同時擁有很多讀者,但是在某一特定時刻,它只能擁有一個寫者,它們之間的兼容關(guān)系如表 1 所示。
表 1. 鎖間的兼容關(guān)系
是否滿足請求 | ||
當(dāng)前加上的鎖 | 共享鎖 | 排他鎖 |
無 | 是 | 是 |
共享鎖 | 是 | 否 |
排他鎖 | 否 | 否 |
勸告鎖是一種協(xié)同工作的鎖。對于這一種鎖來說,內(nèi)核只提供加鎖以及檢測文件是否已經(jīng)加鎖的手段,但是內(nèi)核并不參與鎖的控制和協(xié)調(diào)。也就是說,如果有進(jìn)程不遵守“游戲規(guī)則”,不檢查目標(biāo)文件是否已經(jīng)由別的進(jìn)程加了鎖就往其中寫入數(shù)據(jù),那么內(nèi)核是不會加以阻攔的。因此,勸告鎖并不能阻止進(jìn)程對文件的訪問,而只能依靠各個進(jìn)程在訪問文件之前檢查該文件是否已經(jīng)被其他進(jìn)程加鎖來實(shí)現(xiàn)并發(fā)控制。進(jìn)程需要事先對鎖的狀態(tài)做一個約定,并根據(jù)鎖的當(dāng)前狀態(tài)和相互關(guān)系來確定其他進(jìn)程是否能對文件執(zhí)行指定的操作。從這點(diǎn)上來說,勸告鎖的工作方式與使用信號量保護(hù)臨界區(qū)的方式非常類似。
勸告鎖可以對文件的任意一個部分進(jìn)行加鎖,也可以對整個文件進(jìn)行加鎖,甚至可以對文件將來增大的部分也進(jìn)行加鎖。由于進(jìn)程可以選擇對文件的某個部分進(jìn)行加鎖,所以一個進(jìn)程可以獲得關(guān)于某個文件不同部分的多個鎖。
與勸告鎖不同,強(qiáng)制鎖是一種內(nèi)核強(qiáng)制采用的文件鎖,它是從 System V Release 3 開始引入的。每當(dāng)有系統(tǒng)調(diào)用 open()、read() 以及write() 發(fā)生的時候,內(nèi)核都要檢查并確保這些系統(tǒng)調(diào)用不會違反在所訪問文件上加的強(qiáng)制鎖約束。也就是說,如果有進(jìn)程不遵守游戲規(guī)則,硬要往加了鎖的文件中寫入內(nèi)容,內(nèi)核就會加以阻攔:
如果一個文件已經(jīng)被加上了讀鎖或者共享鎖,那么其他進(jìn)程再對這個文件進(jìn)行寫操作就會被內(nèi)核阻止;
如果一個文件已經(jīng)被加上了寫鎖或者排他鎖,那么其他進(jìn)程再對這個文件進(jìn)行讀取或者寫操作就會被內(nèi)核阻止。
如果其他進(jìn)程試圖訪問一個已經(jīng)加有強(qiáng)制鎖的文件,進(jìn)程行為取決于所執(zhí)行的操作模式和文件鎖的類型,歸納如表 2 所示:
表 2. 進(jìn)行對已加強(qiáng)制鎖的文件進(jìn)行操作時的行為
當(dāng)前鎖類型 | 阻塞讀 | 阻塞寫 | 非阻塞讀 | 非阻塞寫 |
讀鎖 | 正常讀取數(shù)據(jù) | 阻塞 | 正常讀取數(shù)據(jù) | EAGAIN |
寫鎖 | 阻塞 | 阻塞 | EAGAIN | EAGAIN |
需要注意的是,如果要訪問的文件的鎖類型與要執(zhí)行的操作存在沖突,那么采用阻塞讀/寫操作的進(jìn)程會被阻塞,而采用非阻塞讀/寫操作的進(jìn)程則不會阻塞,而是立即返回 EAGAIN。
另外,unlink() 系統(tǒng)調(diào)用并不會受到強(qiáng)制鎖的影響,原因在于一個文件可能存在多個硬鏈接,此時刪除文件時并不會修改文件本身的內(nèi)容,而是只會改變其父目錄中 dentry 的內(nèi)容。
然而,在有些應(yīng)用中并不適合使用強(qiáng)制鎖,所以索引節(jié)點(diǎn)結(jié)構(gòu)中的 i_flags 字段中定義了一個標(biāo)志位MS_MANDLOCK用于有選擇地允許或者不允許對一個文件使用強(qiáng)制鎖。在 super_block 結(jié)構(gòu)中,也可以將 s_flags 這個標(biāo)志為設(shè)置為1或者0,用以表示整個設(shè)備上的文件是否允許使用強(qiáng)制鎖。
要想對一個文件采用強(qiáng)制鎖,必須按照以下步驟執(zhí)行:
使用 -o mand 選項來掛載文件系統(tǒng)。這樣在執(zhí)行 mount() 系統(tǒng)調(diào)用時,會傳入 MS_MANDLOCK 標(biāo)記,從而將 super_block 結(jié)構(gòu)中的 s_flags 設(shè)置為 1,用來表示在這個文件系統(tǒng)上可以采用強(qiáng)制鎖。例如:
# mount -o mand /dev/sdb7 /mnt # mount | grep mnt /dev/sdb7 on /mnt type ext3 (rw,mand) |
1.修改要加強(qiáng)制鎖的文件的權(quán)限:設(shè)置 SGID 位,并清除組可執(zhí)行位。這種組合通常來說是毫無意義的,系統(tǒng)用來表示該文件被加了強(qiáng)制鎖。例如:
# 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() 系統(tǒng)調(diào)用對該文件進(jìn)行加鎖或解鎖操作。
1.3. 共享模式鎖
Linux 中還引入了兩種特殊的文件鎖:共享模式強(qiáng)制鎖和租借鎖。這兩種文件鎖可以被看成是強(qiáng)制鎖的兩種變種形式。共享模式強(qiáng)制鎖可以用于某些私有網(wǎng)絡(luò)文件系統(tǒng),如果某個文件被加上了共享模式強(qiáng)制鎖,那么其他進(jìn)程打開該文件的時候不能與該文件的共享模式強(qiáng)制鎖所設(shè)置的訪問模式相沖突。但是由于可移植性不好,因此并不建議使用這種鎖。
采用強(qiáng)制鎖之后,如果一個進(jìn)程對某個文件擁有寫鎖,只要它不釋放這個鎖,就會導(dǎo)致訪問該文件的其他進(jìn)程全部被阻塞或不斷失敗重試;即使該進(jìn)程只擁有讀鎖,也會造成后續(xù)更新該文件的進(jìn)程的阻塞。為了解決這個問題,Linux 中采用了一種新型的租借鎖。
當(dāng)進(jìn)程嘗試打開一個被租借鎖保護(hù)的文件時,該進(jìn)程會被阻塞,同時,在一定時間內(nèi)擁有該文件租借鎖的進(jìn)程會收到一個信號。收到信號之后,擁有該文件租借鎖的進(jìn)程會首先更新文件,從而保證了文件內(nèi)容的一致性,接著,該進(jìn)程釋放這個租借鎖。如果擁有租借鎖的進(jìn)程在一定的時間間隔內(nèi)沒有完成工作,內(nèi)核就會自動刪除這個租借鎖或者將該鎖進(jìn)行降級,從而允許被阻塞的進(jìn)程繼續(xù)工作。
系統(tǒng)默認(rèn)的這段間隔時間是 45 秒鐘,定義如下:
137 int lease_break_time = 45; |
這個參數(shù)可以通過修改 /proc/sys/fs/lease-break-time 進(jìn)行調(diào)節(jié)(當(dāng)然,/proc/sys/fs/leases-enable 必須為 1 才行)。
![]() ![]() |
Linux 內(nèi)核中關(guān)于文件鎖的實(shí)現(xiàn)
在 Linux 內(nèi)核中,所有類型的文件鎖都是由數(shù)據(jù)結(jié)構(gòu) file_lock 來描述的,file_lock 結(jié)構(gòu)是在 文件中定義的,內(nèi)容如下所示:
清單 1. file_lock 結(jié)構(gòu)
811 struct file_lock { 812 struct file_lock *fl_next; /* singly linked list for this inode */ 813 struct list_head fl_link; /* doubly linked list of all locks */ 814 struct list_head fl_block; /* circular list of blocked processes */ 815 fl_owner_t fl_owner; 816 unsigned int fl_pid; 817 wait_queue_head_t fl_wait; 818 struct file *fl_file; 819 unsigned char fl_flags; 820 unsigned char fl_type; 821 loff_t fl_start; 822 loff_t fl_end; 823 824 struct fasync_struct * fl_fasync; /* for lease break notifications */ 825 unsigned long fl_break_time; /* for nonblocking lease breaks */ 826 827 struct file_lock_operations *fl_ops; /* Callbacks for filesystems */ 828 struct lock_manager_operations *fl_lmops; /* Callbacks for lockmanagers */ 829 union { 830 struct nfs_lock_info nfs_fl; 831 struct nfs4_lock_info nfs4_fl; 832 struct { 833 struct list_head link; /* link in AFS vnode's pending_locks list */ 834 int state; /* state of grant or error if -ve */ 835 } afs; 836 } fl_u; 837 }; |
表 3 簡單描述了 file_lock 結(jié)構(gòu)中的各個字段所表示的含義。
表 3. file_lock 數(shù)據(jù)結(jié)構(gòu)的字段
類型 | 字段 | 字段描述 |
struct file_lock* | fl_next | 與索引節(jié)點(diǎn)相關(guān)的鎖列表中下一個元素 |
struct list_head | fl_link | 指向活躍列表或者被阻塞列表 |
struct list_head | fl_block | 指向鎖等待列表 |
struct files_struct * | fl_owner | 鎖擁有者的 files_struct |
unsigned int | fl_pid | 進(jìn)程擁有者的 pid |
wait_queue_head_t | fl_wait | 被阻塞進(jìn)程的等待隊列 |
struct file * | fl_file | 指向文件對象 |
unsigned char | fl_flags | 鎖標(biāo)識 |
unsigned char | fl_type | 鎖類型 |
loff_t | fl_start | 被鎖區(qū)域的開始位移 |
loff_t | fl_end | 被鎖區(qū)域的結(jié)束位移 |
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 | 文件系統(tǒng)特定信息 |
一個 file_lock 結(jié)構(gòu)就是一把“鎖”,結(jié)構(gòu)中的 fl_file 就指向目標(biāo)文件的 file 結(jié)構(gòu),而 fl_start 和 fl_end 則確定了該文件要加鎖的一個區(qū)域。當(dāng)進(jìn)程發(fā)出系統(tǒng)調(diào)用來請求對某個文件加排他鎖時,如果這個文件上已經(jīng)加上了共享鎖,那么排他鎖請求不能被立即滿足,這個進(jìn)程必須先要被阻塞。這樣,這個進(jìn)程就被放進(jìn)了等待隊列,file_lock 結(jié)構(gòu)中的 fl_wait 字段就指向這個等待隊列。指向磁盤上相同文件的所有 file_lock 結(jié)構(gòu)會被鏈接成一個單鏈表 file_lock_list,索引節(jié)點(diǎn)結(jié)構(gòu)中的 i_flock 字段會指向該單鏈表結(jié)構(gòu)的首元素,fl_next 用于指向該鏈表中的下一個元素;當(dāng)前系統(tǒng)中所有被請求,但是未被允許的鎖被串成一個鏈表:blocked_list。fl_link 字段指向這兩個列表其中一個。對于被阻塞列表(blocked_list)上的每一個鎖結(jié)構(gòu)來說,fl_next 字段指向與該鎖產(chǎn)生沖突的當(dāng)前正在使用的鎖。所有在等待同一個鎖的那些鎖會被鏈接起來,這就需要用到字段 fl_block,新來的等待者會被加入到等待列表的尾部。 此外,fl_type 表示鎖的性質(zhì),如讀、寫。fl_flags 是一些標(biāo)志位,在 linux 2.6中,這些標(biāo)志位的定義如下所示:
清單 2. 標(biāo)志位的定義
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 鎖是通過系統(tǒng)調(diào)用 fcntl() 創(chuàng)建的;而 FL_FLOCK 鎖是通過系統(tǒng)調(diào)用 flock()創(chuàng)建的(詳細(xì)內(nèi)容請參見后文中的介紹)。FL_FLOCK 鎖永遠(yuǎn)都和一個文件對象相關(guān)聯(lián),打開這個文件的進(jìn)程擁有該 FL_FLOCK 鎖。當(dāng)一個鎖被請求或者允許的時候,內(nèi)核就會把這個進(jìn)程在同一個文件上的鎖都替換掉。FL_POSIX 鎖則一直與一個進(jìn)程以及一個索引節(jié)點(diǎn)相關(guān)聯(lián)。當(dāng)進(jìn)程死亡或者文件描述符被關(guān)閉的時候,這個鎖會被自動釋放。
對于強(qiáng)制鎖來說,在 Linux 中,內(nèi)核提供了 inline 函數(shù) locks_verify_locked() 用于檢測目標(biāo)文件或者目標(biāo)文件所在的設(shè)備是否允許使用強(qiáng)制鎖,并且檢查該設(shè)備是否已經(jīng)加上了鎖,相關(guān)函數(shù)如下所示:
清單 3. 與強(qiáng)制鎖相關(guān)的函數(shù)
166 #define __IS_FLG(inode,flg) ((inode)->i_sb->s_flags & (flg)) 173 #define IS_MANDLOCK(inode) __IS_FLG(inode, MS_MANDLOCK) 1047 /** 1048 * locks_mandatory_locked - Check for an active lock 1049 * @inode: the file to check 1050 * 1051 * Searches the inode's list of locks to find any POSIX locks which conflict. 1052 * This function is called from locks_verify_locked() only. 1053 */ 1054 int locks_mandatory_locked(struct inode *inode) 1055 { 1056 fl_owner_t owner = current->files; 1057 struct file_lock *fl; 1058 1059 /* 1060 * Search the lock list for this inode for any POSIX locks. 1061 */ 1062 lock_kernel(); 1063 for (fl = inode->i_flock; fl != NULL; fl = fl->fl_next) { 1064 if (!IS_POSIX(fl)) 1065 continue; 1066 if (fl->fl_owner != owner) 1067 break; 1068 } 1069 unlock_kernel(); 1070 return fl ? -EAGAIN : 0; 1071 } 1368 /* 1369 * Candidates for mandatory locking have the setgid bit set 1370 * but no group execute bit - an otherwise meaningless combination. 1371 */ 1372 #define MANDATORY_LOCK(inode) \ 1373 (IS_MANDLOCK(inode) && ((inode)->i_mode & (S_ISGID | S_IXGRP)) == S_ISGID) 1374 1375 static inline int locks_verify_locked(struct inode *inode) 1376 { 1377 if (MANDATORY_LOCK(inode)) 1378 return locks_mandatory_locked(inode); 1379 return 0; 1380 } |
這里,函數(shù) locks_verify_locked() 利用宏 MANDATORY_LOCK 來檢測目標(biāo)文件是否允許加鎖,條件包括:該文件所在的設(shè)備的 super_block 結(jié)構(gòu)中的 s_flags 必須被置為 1,該文件的 SGID 被置為 1 而且同組可執(zhí)行位被清 0。如果允許,則調(diào)用函數(shù)locks_mandatory_locked(),該函數(shù)從索引節(jié)點(diǎn)的鎖列表中查找是否存在有與其相沖突的鎖,即是否已經(jīng)加上了鎖。
![]() ![]() |
Linux 中關(guān)于文件鎖的系統(tǒng)調(diào)用
這里介紹在 Linux 中與文件鎖關(guān)系密切的兩個系統(tǒng)調(diào)用:flock() 和 fcntl()。勸告鎖既可以通過系統(tǒng)調(diào)用 flock() 來實(shí)現(xiàn),也可以通過系統(tǒng)調(diào)用 fcntl() 來實(shí)現(xiàn)。flock() 系統(tǒng)調(diào)用是從 BSD 中衍生出來的,在傳統(tǒng)的類 UNIX 操作系統(tǒng)中,系統(tǒng)調(diào)用flock() 只適用于勸告鎖。但是,Linux 2.6內(nèi)核利用系統(tǒng)調(diào)用 flock() 實(shí)現(xiàn)了我們前面提到的特殊的強(qiáng)制鎖:共享模式強(qiáng)制鎖。另外,flock() 只能實(shí)現(xiàn)對整個文件進(jìn)行加鎖,而不能實(shí)現(xiàn)記錄級的加鎖。系統(tǒng)調(diào)用fcntl() 符合 POSIX 標(biāo)準(zhǔn)的文件鎖實(shí)現(xiàn),它也是非常強(qiáng)大的文件鎖,fcntl() 可以實(shí)現(xiàn)對紀(jì)錄進(jìn)行加鎖。
flock() 的函數(shù)原型如下所示:
int flock(int fd, int operation); |
其中,參數(shù) fd 表示文件描述符;參數(shù) operation 指定要進(jìn)行的鎖操作,該參數(shù)的取值有如下幾種:LOCK_SH, LOCK_EX, LOCK_UN 和 LOCK_MANDphost2008-07-03T00:00:00
man page 里面沒有提到,其各自的意思如下所示:
- LOCK_SH:表示要創(chuàng)建一個共享鎖,在任意時間內(nèi),一個文件的共享鎖可以被多個進(jìn)程擁有
- LOCK_EX:表示創(chuàng)建一個排他鎖,在任意時間內(nèi),一個文件的排他鎖只能被一個進(jìn)程擁有
- LOCK_UN:表示刪除該進(jìn)程創(chuàng)建的鎖
- LOCK_MAND:它主要是用于共享模式強(qiáng)制鎖,它可以與 LOCK_READ 或者 LOCK_WRITE 聯(lián)合起來使用,從而表示是否允許并發(fā)的讀操作或者并發(fā)的寫操作(盡管在 flock() 的手冊頁中沒有介紹 LOCK_MAND,但是閱讀內(nèi)核源代碼就會發(fā)現(xiàn),這在內(nèi)核中已經(jīng)實(shí)現(xiàn)了)
通常情況下,如果加鎖請求不能被立即滿足,那么系統(tǒng)調(diào)用 flock() 會阻塞當(dāng)前進(jìn)程。比如,進(jìn)程想要請求一個排他鎖,但此時,已經(jīng)由其他進(jìn)程獲取了這個鎖,那么該進(jìn)程將會被阻塞。如果想要在沒有獲得這個排他鎖的情況下不阻塞該進(jìn)程,可以將 LOCK_NB 和 LOCK_SH 或者 LOCK_EX 聯(lián)合使用,那么系統(tǒng)就不會阻塞該進(jìn)程。flock() 所加的鎖會對整個文件起作用。
fcntl() 函數(shù)的功能很多,可以改變已打開的文件的性質(zhì),本文中只是介紹其與獲取/設(shè)置文件鎖有關(guān)的功能。fcntl() 的函數(shù)原型如下所示:
int fcntl (int fd, int cmd, struct flock *lock); |
其中,參數(shù) fd 表示文件描述符;參數(shù) cmd 指定要進(jìn)行的鎖操作,由于 fcntl() 函數(shù)功能比較多,這里先介紹與文件鎖相關(guān)的三個取值 F_GETLK、F_SETLK 以及 F_SETLKW。這三個值均與 flock 結(jié)構(gòu)有關(guān)。flock 結(jié)構(gòu)如下所示:
清單 4. flock 結(jié)構(gò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) */ ... }; |
在 flock 結(jié)構(gòu)中,l_type 用來指明創(chuàng)建的是共享鎖還是排他鎖,其取值有三種:F_RDLCK(共享鎖)、F_WRLCK(排他鎖)和F_UNLCK(刪除之前建立的鎖);l_pid 指明了該鎖的擁有者;l_whence、l_start 和l_end 這些字段指明了進(jìn)程需要對文件的哪個區(qū)域進(jìn)行加鎖,這個區(qū)域是一個連續(xù)的字節(jié)集合。因此,進(jìn)程可以對同一個文件的不同部分加不同的鎖。l_whence 必須是 SEEK_SET、SEEK_CUR 或 SEEK_END 這幾個值中的一個,它們分別對應(yīng)著文件頭、當(dāng)前位置和文件尾。l_whence 定義了相對于 l_start 的偏移量,l_start 是從文件開始計算的。
可以執(zhí)行的操作包括:
- F_GETLK:進(jìn)程可以通過它來獲取通過 fd 打開的那個文件的加鎖信息。執(zhí)行該操作時,lock 指向的結(jié)構(gòu)中就保存了希望對文件加的鎖(或者說要查詢的鎖)。如果確實(shí)存在這樣一把鎖,它阻止 lock 指向的 flock 結(jié)構(gòu)所給出的鎖描述符,則把現(xiàn)存的鎖的信息寫到 lock 指向的 flock 結(jié)構(gòu)中,并將該鎖擁有者的 PID 寫入 l_pid 字段中,然后返回;否則,就將 lock 指向的 flock 結(jié)構(gòu)中的 l_type 設(shè)置為 F_UNLCK,并保持 flock 結(jié)構(gòu)中其他信息不變返回,而不會對該文件真正加鎖。
- F_SETLK:進(jìn)程用它來對文件的某個區(qū)域進(jìn)行加鎖(l_type的值為 F_RDLCK 或 F_WRLCK)或者刪除鎖(l_type 的值為F_UNLCK),如果有其他鎖阻止該鎖被建立,那么 fcntl() 就出錯返回
- F_SETLKW:與 F_SETLK 類似,唯一不同的是,如果有其他鎖阻止該鎖被建立,則調(diào)用進(jìn)程進(jìn)入睡眠狀態(tài),等待該鎖釋放。一旦這個調(diào)用開始了等待,就只有在能夠進(jìn)行加鎖或者收到信號時才會返回
需要注意的是,F(xiàn)_GETLK 用于測試是否可以加鎖,在 F_GETLK 測試可以加鎖之后,F(xiàn)_SETLK 和 F_SETLKW 就會企圖建立一把鎖,但是這兩者之間并不是一個原子操作,也就是說,在 F_SETLK 或者 F_SETLKW 還沒有成功加鎖之前,另外一個進(jìn)程就有可能已經(jīng)插進(jìn)來加上了一把鎖。而且,F(xiàn)_SETLKW 有可能導(dǎo)致程序長時間睡眠。還有,程序?qū)δ硞€文件擁有的各種鎖會在相應(yīng)的文件描述符被關(guān)閉時自動清除,程序運(yùn)行結(jié)束后,其所加的各種鎖也會自動清除。
fcntl() 既可以用于勸告鎖,也可以用于強(qiáng)制鎖,在默認(rèn)情況下,它用于勸告鎖。如果它用于強(qiáng)制鎖,當(dāng)進(jìn)程對某個文件進(jìn)行了讀或?qū)戇@樣的系統(tǒng)調(diào)用時,系統(tǒng)則會檢查該文件的鎖的 O_NONBLOCK 標(biāo)識,該標(biāo)識是文件狀態(tài)標(biāo)識的一種,如果設(shè)置文件狀態(tài)標(biāo)識的時候設(shè)置了 O_NONBLOCK,則該進(jìn)程會出錯返回;否則,該進(jìn)程被阻塞。cmd 參數(shù)的值 F_SETFL 可以用于設(shè)置文件狀態(tài)標(biāo)識。
此外,系統(tǒng)調(diào)用 fcntl() 還可以用于租借鎖,此時采用的函數(shù)原型如下:
int fcntl(int fd, int cmd, long arg); |
與租借鎖相關(guān)的 cmd 參數(shù)的取值有兩種:F_SETLEASE 和 F_GETLEASE。其含義如下所示:
- F_SETLEASE:根據(jù)下面所描述的 arg 參數(shù)指定的值來建立或者刪除租約:
- F_RDLCK:設(shè)置讀租約。當(dāng)文件被另一個進(jìn)程以寫的方式打開時,擁有該租約的當(dāng)前進(jìn)程會收到通知
- F_WRLCK:設(shè)置寫租約。當(dāng)文件被另一個進(jìn)程以讀或者寫的方式打開時,擁有該租約的當(dāng)前進(jìn)程會收到通知
- F_UNLCK:刪除以前建立的租約
- F_GETLEASE:表明調(diào)用進(jìn)程擁有文件上哪種類型的鎖,這需要通過返回值來確定,返回值有三種:F_RDLCK、F_WRLCK和F_UNLCK,分別表明調(diào)用進(jìn)程對文件擁有讀租借、寫租借或者根本沒有租借
某個進(jìn)程可能會對文件執(zhí)行其他一些系統(tǒng)調(diào)用(比如 OPEN() 或者 TRUNCATE()),如果這些系統(tǒng)調(diào)用與該文件上由 F_SETLEASE 所設(shè)置的租借鎖相沖突,內(nèi)核就會阻塞這個系統(tǒng)調(diào)用;同時,內(nèi)核會給擁有這個租借鎖的進(jìn)程發(fā)信號,告知此事。擁有此租借鎖的進(jìn)程會對該信號進(jìn)行反饋,它可能會刪除這個租借鎖,也可能會減短這個租借鎖的租約,從而可以使得該文件可以被其他進(jìn)程所訪問。如果擁有租借鎖的進(jìn)程不能在給定時間內(nèi)完成上述操作,那么系統(tǒng)會強(qiáng)制幫它完成。通過 F_SETLEASE 命令將 arg 參數(shù)指定為 F_UNLCK 就可以刪除這個租借鎖。不管對該租借鎖減短租約或者干脆刪除的操作是進(jìn)程自愿的還是內(nèi)核強(qiáng)迫的,只要被阻塞的系統(tǒng)調(diào)用還沒有被發(fā)出該調(diào)用的進(jìn)程解除阻塞,那么系統(tǒng)就會允許這個系統(tǒng)調(diào)用執(zhí)行。即使被阻塞的系統(tǒng)調(diào)用因為某些原因被解除阻塞,但是上面對租借鎖減短租約或者刪除這個過程還是會執(zhí)行的。
需要注意的是,租借鎖也只能對整個文件生效,而無法實(shí)現(xiàn)記錄級的加鎖。
![]() ![]() |
![]() |
為了使讀者更深入理解本文中介紹的內(nèi)容,下面我們給出了一個例子來詳細(xì)介紹文件鎖的具體用法。這個例子可以用來檢測所使用的文件是否支持強(qiáng)制鎖,其源代碼如下所示:
清單 5. 鎖的使用方法具體示例
# cat -n mandlock.c 1 #include <errno.h> 2 #include <stdio.h> 3 #include <fcntl.h> 4 #include <sys/wait.h> 5 #include <sys/stat.h> 6 7 int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len) 8 { 9 struct flock lock; 10 11 lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLCK */ 12 lock.l_start = offset; /* byte offset, relative to l_whence */ 13 lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */ 14 lock.l_len = len; /* #bytes (0 means to EOF) */ 15 16 return( fcntl(fd, cmd, &lock) ); 17 } 18 19 #define read_lock(fd, offset, whence, len) \ 20 lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, len) 21 #define write_lock(fd, offset, whence, len) \ 22 lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, len) 23 24 #define err_sys(x) { perror(x); exit(1); } 25 26 int main(int argc, char *argv[]) 27 { 28 int fd, val; 29 pid_t pid; 30 char buf[5]; 31 struct stat statbuf; 32 if (argc != 2) { 33 fprintf(stderr, "usage: %s filename\n", argv[0]); 34 exit(1); 35 } 36 if ((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC )) < 0) 37 err_sys("open error"); 38 if (write(fd, "hello world", 11) != 11) 39 err_sys("write error"); 40 41 /* turn on set-group-ID and turn off group-execute */ 42 if (fstat(fd, &statbuf) < 0) 43 err_sys("fstat error"); 44 if (fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0) 45 err_sys("fchmod error"); 46 47 sleep(2); 48 49 if ((pid = fork()) < 0) { 50 err_sys("fork error"); 51 } else if (pid > 0) { /* parent */ 52 /* write lock entire file */ 53 if (write_lock(fd, 0, SEEK_SET, 0) < 0) 54 err_sys("write_lock error"); 55 56 sleep(20); /* wait for child to set lock and read data */ 57 58 if (waitpid(pid, NULL, 0) < 0) 59 err_sys("waitpid error"); 60 61 } else { /* child */ 62 sleep(10); /* wait for parent to set lock */ 63 64 if ( (val = fcntl(fd, F_GETFL, 0)) < 0) 65 err_sys("fcntl F_GETFL error"); 66 67 val |= O_NONBLOCK; /* turn on O_NONBLOCK flag */ 68 69 if (fcntl(fd, F_SETFL, val) < 0) 70 err_sys("fcntl F_SETFL error"); 71 72 /* first let's see what error we get if region is locked */ 73 if (read_lock(fd, 0, SEEK_SET, 0) != -1) /* no wait */ 74 err_sys("child: read_lock succeeded"); 75 76 printf("read_lock of already-locked region returns %d: %s\n", errno, strerror(errno)); 77 78 /* now try to read the mandatory locked file */ 79 if (lseek(fd, 0, SEEK_SET) == -1) 80 err_sys("lseek error"); 81 if (read(fd, buf, 5) < 0) 82 printf("read failed (mandatory locking works)\n"); 83 else 84 printf("read OK (no mandatory locking), buf = %5.5s\n", buf); 85 } 86 exit(0); 87 } 88 |
樣例代碼中所采用的技術(shù)在前文中大都已經(jīng)介紹過了,其基本思想是在首先在父進(jìn)程中對文件加上寫鎖;然后在子進(jìn)程中將文件描述符設(shè)置為非阻塞模式(第 69 行),然后對文件加讀鎖,并嘗試讀取該文件中的內(nèi)容。如果系統(tǒng)支持強(qiáng)制鎖,則子進(jìn)程中的 read() 系統(tǒng)調(diào)用(代碼中的第 81 行)會立即返回 EAGAIN;否則,等父進(jìn)程完成寫文件操作之后,子進(jìn)程中的 read() 系統(tǒng)調(diào)用就會返回父進(jìn)程剛剛寫入的前 5 個字節(jié)的數(shù)據(jù)。代碼中的幾個 sleep() 是為了協(xié)調(diào)父進(jìn)程與子進(jìn)程之間的同步而使用的。
該程序在測試系統(tǒng)的執(zhí)行結(jié)果如下所示:
# mount | grep mnt /dev/sdb7 on /mnt type ext3 (rw,mand) /dev/sdb6 on /tmp/mnt type ext3 (rw) # ./mandlock /mnt/testfile read_lock of already-locked region returns 11: Resource temporarily unavailable read failed (mandatory locking works) # ./mandlock /tmp/mnt/testfile read_lock of already-locked region returns 11: Resource temporarily unavailable read OK (no mandatory locking), buf = hello |
我們可以看到,/dev/sdb7 使用 –o mand 選項掛載到了 /mnt 目錄中,而 /dev/sdb6 則么有使用這個選項掛載到了 /tmp/mnt 目錄中。由于在程序中我們完成了對測試文件 SGID 和同組可執(zhí)行位的設(shè)置(第 44 行),因此 /mnt/testfile 可以支持強(qiáng)制鎖,而 /tmp/mnt/testfile 則不能。這也正是為什么前者的 read() 系統(tǒng)調(diào)用會失敗返回而后者則可以成功讀取到 hello 的原因。
![]() ![]() |
![]() |
Linux 的文件鎖在以共享索引節(jié)點(diǎn)共享文件的情況下設(shè)計的,文件鎖的實(shí)現(xiàn)可以使得不同用戶同時讀寫同一文件的并發(fā)問題得以解決。本文描述了 Linux 中各類文件鎖的概念,使用場景,內(nèi)核中描述文件鎖的數(shù)據(jù)結(jié)構(gòu)以及與文件鎖密切相關(guān)的系統(tǒng)調(diào)用等內(nèi)容,至于與文件鎖相關(guān)的索引節(jié)點(diǎn)數(shù)據(jù)結(jié)構(gòu),以及在對文件進(jìn)行加鎖時遇到的死鎖問題等其他知識,這里沒有做詳盡介紹,感興趣的讀者可以自行參考內(nèi)核源代碼。
本文的目的是想幫助讀者理清 Linux 2.6中文件鎖的概念以及 Linux 2.6 都提供了何種數(shù)據(jù)結(jié)構(gòu)以及關(guān)鍵的系統(tǒng)調(diào)用來實(shí)現(xiàn)文件鎖,從而可以幫助讀者更好地使用文件鎖來解決多個進(jìn)程讀取同一個文件的互斥問題。
![]() ![]() |
![]() |
- Linux 內(nèi)核源代碼中包含了文件鎖的具體實(shí)現(xiàn),本文中引用的內(nèi)核代碼來自于 Linux 2.6.23 版本的內(nèi)核。
- File Locking in Linux 2.5一文中介紹了在 Linux 2.5 開發(fā)時對文件鎖的一些設(shè)計考慮。
- The Single UNIX® Specification, Version 3 規(guī)范中對 fcntl 系統(tǒng)調(diào)用進(jìn)行了詳細(xì)的規(guī)定。
- Linux 內(nèi)核情景分析(上)一書的文件系統(tǒng)一章詳細(xì)描述了 Linux 內(nèi)核對各種不同類鎖的管理和控制,講述了 Linux 文件鎖的關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)及其實(shí)現(xiàn),以及文件鎖對其他的文件模式和操作的影響
- 在 Understanding the Linux Kernel(3rd Edition) 中,有關(guān)于 Linux 中不同種類文件鎖詳細(xì)的實(shí)現(xiàn)過程
- 更多的關(guān)于 Linux 開發(fā)的資源,參見 developerWorks 上 Linux 專區(qū)。