本文的目的是想幫助讀者理清 Linux 2.6中文件鎖的概念以及 Linux 2.6 都提供了何種數(shù)據(jù)結(jié)構(gòu)以及關(guān)鍵的系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)文件鎖,從而可以幫助讀者更好地使用文件鎖來(lái)解決多個(gè)進(jìn)程讀取同一個(gè)文件的互斥問(wèn)題。本文主要描述了 Linux 中各類(lèi)文件鎖的概念,使用場(chǎng)景,內(nèi)核中描述文件鎖的數(shù)據(jù)結(jié)構(gòu)以及與文件鎖密切相關(guān)的系統(tǒng)調(diào)用等內(nèi)容。
在多任務(wù)操作系統(tǒng)環(huán)境中,如果一個(gè)進(jìn)程嘗試對(duì)正在被其他進(jìn)程讀取的文件進(jìn)行寫(xiě)操作,可能會(huì)導(dǎo)致正在進(jìn)行讀操作的進(jìn)程讀取到一些被破壞或者不完整的數(shù)據(jù);如果兩個(gè)進(jìn)程并發(fā)對(duì)同一個(gè)文件進(jìn)行寫(xiě)操作,可能會(huì)導(dǎo)致該文件遭到破壞。因此,為了避免發(fā)生這種問(wèn)題,必須要采用某種機(jī)制來(lái)解決多個(gè)進(jìn)程并發(fā)訪問(wèn)同一個(gè)文件時(shí)所面臨的同步問(wèn)題,由此而產(chǎn)生了文件加鎖方面的技術(shù)。
早期的 UNIX 系統(tǒng)只支持對(duì)整個(gè)文件進(jìn)行加鎖,因此無(wú)法運(yùn)行數(shù)據(jù)庫(kù)之類(lèi)的程序,因?yàn)榇祟?lèi)程序需要實(shí)現(xiàn)記錄級(jí)的加鎖。在 System V Release 3 中,通過(guò) fcntl 提供了記錄級(jí)的加鎖,此后發(fā)展成為 POSIX 標(biāo)準(zhǔn)的一部分。本文將基于 2.6.23 版本的內(nèi)核來(lá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)制鎖,它都可以同時(shí)使用共享鎖和排他鎖(又稱(chēng)為讀鎖和寫(xiě)鎖)。多個(gè)共享鎖之間不會(huì)相互干擾,多個(gè)進(jìn)程在同一時(shí)刻可以對(duì)同一個(gè)文件加共享鎖。但是,如果一個(gè)進(jìn)程對(duì)該文件加了排他鎖,那么其他進(jìn)程則無(wú)權(quán)再對(duì)該文件加共享鎖或者排他鎖,直到該排他鎖被釋放。所以,對(duì)于同一個(gè)文件來(lái)說(shuō),它可以同時(shí)擁有很多讀者,但是在某一特定時(shí)刻,它只能擁有一個(gè)寫(xiě)者,它們之間的兼容關(guān)系如表 1 所示。
表 1. 鎖間的兼容關(guān)系
是否滿(mǎn)足請(qǐng)求 | ||
當(dāng)前加上的鎖 | 共享鎖 | 排他鎖 |
無(wú) | 是 | 是 |
共享鎖 | 是 | 否 |
排他鎖 | 否 | 否 |
勸告鎖是一種協(xié)同工作的鎖。對(duì)于這一種鎖來(lái)說(shuō),內(nèi)核只提供加鎖以及檢測(cè)文件是否已經(jīng)加鎖的手段,但是內(nèi)核并不參與鎖的控制和協(xié)調(diào)。也就是說(shuō),如果有進(jìn)程不遵守“游戲規(guī)則”,不檢查目標(biāo)文件是否已經(jīng)由別的進(jìn)程加了鎖就往其中寫(xiě)入數(shù)據(jù),那么內(nèi)核是不會(huì)加以阻攔的。因此,勸告鎖并不能阻止進(jìn)程對(duì)文件的訪問(wèn),而只能依靠各個(gè)進(jìn)程在訪問(wèn)文件之前檢查該文件是否已經(jīng)被其他進(jìn)程加鎖來(lái)實(shí)現(xiàn)并發(fā)控制。進(jìn)程需要事先對(duì)鎖的狀態(tài)做一個(gè)約定,并根據(jù)鎖的當(dāng)前狀態(tài)和相互關(guān)系來(lái)確定其他進(jìn)程是否能對(duì)文件執(zhí)行指定的操作。從這點(diǎn)上來(lái)說(shuō),勸告鎖的工作方式與使用信號(hào)量保護(hù)臨界區(qū)的方式非常類(lèi)似。
勸告鎖可以對(duì)文件的任意一個(gè)部分進(jìn)行加鎖,也可以對(duì)整個(gè)文件進(jìn)行加鎖,甚至可以對(duì)文件將來(lái)增大的部分也進(jìn)行加鎖。由于進(jìn)程可以選擇對(duì)文件的某個(gè)部分進(jìn)行加鎖,所以一個(gè)進(jìn)程可以獲得關(guān)于某個(gè)文件不同部分的多個(gè)鎖。
與勸告鎖不同,強(qiáng)制鎖是一種內(nèi)核強(qiáng)制采用的文件鎖,它是從 System V Release 3 開(kāi)始引入的。每當(dāng)有系統(tǒng)調(diào)用 open()、read() 以及write() 發(fā)生的時(shí)候,內(nèi)核都要檢查并確保這些系統(tǒng)調(diào)用不會(huì)違反在所訪問(wèn)文件上加的強(qiáng)制鎖約束。也就是說(shuō),如果有進(jìn)程不遵守游戲規(guī)則,硬要往加了鎖的文件中寫(xiě)入內(nèi)容,內(nèi)核就會(huì)加以阻攔:
如果一個(gè)文件已經(jīng)被加上了讀鎖或者共享鎖,那么其他進(jìn)程再對(duì)這個(gè)文件進(jìn)行寫(xiě)操作就會(huì)被內(nèi)核阻止;
如果一個(gè)文件已經(jīng)被加上了寫(xiě)鎖或者排他鎖,那么其他進(jìn)程再對(duì)這個(gè)文件進(jìn)行讀取或者寫(xiě)操作就會(huì)被內(nèi)核阻止。
如果其他進(jìn)程試圖訪問(wèn)一個(gè)已經(jīng)加有強(qiáng)制鎖的文件,進(jìn)程行為取決于所執(zhí)行的操作模式和文件鎖的類(lèi)型,歸納如表 2 所示:
表 2. 進(jìn)行對(duì)已加強(qiáng)制鎖的文件進(jìn)行操作時(shí)的行為
當(dāng)前鎖類(lèi)型 | 阻塞讀 | 阻塞寫(xiě) | 非阻塞讀 | 非阻塞寫(xiě) |
讀鎖 | 正常讀取數(shù)據(jù) | 阻塞 | 正常讀取數(shù)據(jù) | EAGAIN |
寫(xiě)鎖 | 阻塞 | 阻塞 | EAGAIN | EAGAIN |
需要注意的是,如果要訪問(wèn)的文件的鎖類(lèi)型與要執(zhí)行的操作存在沖突,那么采用阻塞讀/寫(xiě)操作的進(jìn)程會(huì)被阻塞,而采用非阻塞讀/寫(xiě)操作的進(jìn)程則不會(huì)阻塞,而是立即返回 EAGAIN。
另外,unlink() 系統(tǒng)調(diào)用并不會(huì)受到強(qiáng)制鎖的影響,原因在于一個(gè)文件可能存在多個(gè)硬鏈接,此時(shí)刪除文件時(shí)并不會(huì)修改文件本身的內(nèi)容,而是只會(huì)改變其父目錄中 dentry 的內(nèi)容。
然而,在有些應(yīng)用中并不適合使用強(qiáng)制鎖,所以索引節(jié)點(diǎn)結(jié)構(gòu)中的 i_flags 字段中定義了一個(gè)標(biāo)志位MS_MANDLOCK用于有選擇地允許或者不允許對(duì)一個(gè)文件使用強(qiáng)制鎖。在 super_block 結(jié)構(gòu)中,也可以將 s_flags 這個(gè)標(biāo)志為設(shè)置為1或者0,用以表示整個(gè)設(shè)備上的文件是否允許使用強(qiáng)制鎖。
要想對(duì)一個(gè)文件采用強(qiáng)制鎖,必須按照以下步驟執(zhí)行:
使用 -o mand 選項(xiàng)來(lái)掛載文件系統(tǒng)。這樣在執(zhí)行 mount() 系統(tǒng)調(diào)用時(shí),會(huì)傳入 MS_MANDLOCK 標(biāo)記,從而將 super_block 結(jié)構(gòu)中的 s_flags 設(shè)置為 1,用來(lái)表示在這個(gè)文件系統(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í)行位。這種組合通常來(lái)說(shuō)是毫無(wú)意義的,系統(tǒng)用來(lái)表示該文件被加了強(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)用對(duì)該文件進(jìn)行加鎖或解鎖操作。
1.3. 共享模式鎖
Linux 中還引入了兩種特殊的文件鎖:共享模式強(qiáng)制鎖和租借鎖。這兩種文件鎖可以被看成是強(qiáng)制鎖的兩種變種形式。共享模式強(qiáng)制鎖可以用于某些私有網(wǎng)絡(luò)文件系統(tǒng),如果某個(gè)文件被加上了共享模式強(qiáng)制鎖,那么其他進(jìn)程打開(kāi)該文件的時(shí)候不能與該文件的共享模式強(qiáng)制鎖所設(shè)置的訪問(wèn)模式相沖突。但是由于可移植性不好,因此并不建議使用這種鎖。
采用強(qiáng)制鎖之后,如果一個(gè)進(jìn)程對(duì)某個(gè)文件擁有寫(xiě)鎖,只要它不釋放這個(gè)鎖,就會(huì)導(dǎo)致訪問(wèn)該文件的其他進(jìn)程全部被阻塞或不斷失敗重試;即使該進(jìn)程只擁有讀鎖,也會(huì)造成后續(xù)更新該文件的進(jìn)程的阻塞。為了解決這個(gè)問(wèn)題,Linux 中采用了一種新型的租借鎖。
當(dāng)進(jìn)程嘗試打開(kāi)一個(gè)被租借鎖保護(hù)的文件時(shí),該進(jìn)程會(huì)被阻塞,同時(shí),在一定時(shí)間內(nèi)擁有該文件租借鎖的進(jìn)程會(huì)收到一個(gè)信號(hào)。收到信號(hào)之后,擁有該文件租借鎖的進(jìn)程會(huì)首先更新文件,從而保證了文件內(nèi)容的一致性,接著,該進(jìn)程釋放這個(gè)租借鎖。如果擁有租借鎖的進(jìn)程在一定的時(shí)間間隔內(nèi)沒(méi)有完成工作,內(nèi)核就會(huì)自動(dòng)刪除這個(gè)租借鎖或者將該鎖進(jìn)行降級(jí),從而允許被阻塞的進(jìn)程繼續(xù)工作。
系統(tǒng)默認(rèn)的這段間隔時(shí)間是 45 秒鐘,定義如下:
137 int lease_break_time = 45; |
這個(gè)參數(shù)可以通過(guò)修改 /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)核中,所有類(lèi)型的文件鎖都是由數(shù)據(jù)結(jié)構(gòu) file_lock 來(lái)描述的,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 簡(jiǎn)單描述了 file_lock 結(jié)構(gòu)中的各個(gè)字段所表示的含義。
表 3. file_lock 數(shù)據(jù)結(jié)構(gòu)的字段
類(lèi)型 | 字段 | 字段描述 |
struct file_lock* | fl_next | 與索引節(jié)點(diǎn)相關(guān)的鎖列表中下一個(gè)元素 |
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)程的等待隊(duì)列 |
struct file * | fl_file | 指向文件對(duì)象 |
unsigned char | fl_flags | 鎖標(biāo)識(shí) |
unsigned char | fl_type | 鎖類(lèi)型 |
loff_t | fl_start | 被鎖區(qū)域的開(kāi)始位移 |
loff_t | fl_end | 被鎖區(qū)域的結(jié)束位移 |
struct fasync_struct * | fl_fasync | 用于租借暫停通知 |
unsigned long | fl_break_time | 租借的剩余時(shí)間 |
struct file_lock_operations * | fl_ops | 指向文件鎖操作 |
struct lock_manager_operations * | fl_mops | 指向鎖管理操作 |
union | fl_u | 文件系統(tǒng)特定信息 |
一個(gè) file_lock 結(jié)構(gòu)就是一把“鎖”,結(jié)構(gòu)中的 fl_file 就指向目標(biāo)文件的 file 結(jié)構(gòu),而 fl_start 和 fl_end 則確定了該文件要加鎖的一個(gè)區(qū)域。當(dāng)進(jìn)程發(fā)出系統(tǒng)調(diào)用來(lái)請(qǐng)求對(duì)某個(gè)文件加排他鎖時(shí),如果這個(gè)文件上已經(jīng)加上了共享鎖,那么排他鎖請(qǐng)求不能被立即滿(mǎn)足,這個(gè)進(jìn)程必須先要被阻塞。這樣,這個(gè)進(jìn)程就被放進(jìn)了等待隊(duì)列,file_lock 結(jié)構(gòu)中的 fl_wait 字段就指向這個(gè)等待隊(duì)列。指向磁盤(pán)上相同文件的所有 file_lock 結(jié)構(gòu)會(huì)被鏈接成一個(gè)單鏈表 file_lock_list,索引節(jié)點(diǎn)結(jié)構(gòu)中的 i_flock 字段會(huì)指向該單鏈表結(jié)構(gòu)的首元素,fl_next 用于指向該鏈表中的下一個(gè)元素;當(dāng)前系統(tǒng)中所有被請(qǐng)求,但是未被允許的鎖被串成一個(gè)鏈表:blocked_list。fl_link 字段指向這兩個(gè)列表其中一個(gè)。對(duì)于被阻塞列表(blocked_list)上的每一個(gè)鎖結(jié)構(gòu)來(lái)說(shuō),fl_next 字段指向與該鎖產(chǎn)生沖突的當(dāng)前正在使用的鎖。所有在等待同一個(gè)鎖的那些鎖會(huì)被鏈接起來(lái),這就需要用到字段 fl_block,新來(lái)的等待者會(huì)被加入到等待列表的尾部。 此外,fl_type 表示鎖的性質(zhì),如讀、寫(xiě)。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 鎖是通過(guò)系統(tǒng)調(diào)用 fcntl() 創(chuàng)建的;而 FL_FLOCK 鎖是通過(guò)系統(tǒng)調(diào)用 flock()創(chuàng)建的(詳細(xì)內(nèi)容請(qǐng)參見(jiàn)后文中的介紹)。FL_FLOCK 鎖永遠(yuǎn)都和一個(gè)文件對(duì)象相關(guān)聯(lián),打開(kāi)這個(gè)文件的進(jìn)程擁有該 FL_FLOCK 鎖。當(dāng)一個(gè)鎖被請(qǐng)求或者允許的時(shí)候,內(nèi)核就會(huì)把這個(gè)進(jìn)程在同一個(gè)文件上的鎖都替換掉。FL_POSIX 鎖則一直與一個(gè)進(jìn)程以及一個(gè)索引節(jié)點(diǎn)相關(guān)聯(lián)。當(dāng)進(jìn)程死亡或者文件描述符被關(guān)閉的時(shí)候,這個(gè)鎖會(huì)被自動(dòng)釋放。
對(duì)于強(qiáng)制鎖來(lái)說(shuō),在 Linux 中,內(nèi)核提供了 inline 函數(shù) locks_verify_locked() 用于檢測(cè)目標(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 來(lái)檢測(cè)目標(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)系密切的兩個(gè)系統(tǒng)調(diào)用:flock() 和 fcntl()。勸告鎖既可以通過(guò)系統(tǒng)調(diào)用 flock() 來(lái)實(shí)現(xiàn),也可以通過(guò)系統(tǒng)調(diào)用 fcntl() 來(lái)實(shí)現(xiàn)。flock() 系統(tǒng)調(diào)用是從 BSD 中衍生出來(lái)的,在傳統(tǒng)的類(lèi) 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)對(duì)整個(gè)文件進(jìn)行加鎖,而不能實(shí)現(xiàn)記錄級(jí)的加鎖。系統(tǒng)調(diào)用fcntl() 符合 POSIX 標(biāo)準(zhǔn)的文件鎖實(shí)現(xiàn),它也是非常強(qiáng)大的文件鎖,fcntl() 可以實(shí)現(xiàn)對(duì)紀(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 里面沒(méi)有提到,其各自的意思如下所示:
- LOCK_SH:表示要?jiǎng)?chuàng)建一個(gè)共享鎖,在任意時(shí)間內(nèi),一個(gè)文件的共享鎖可以被多個(gè)進(jìn)程擁有
- LOCK_EX:表示創(chuàng)建一個(gè)排他鎖,在任意時(shí)間內(nèi),一個(gè)文件的排他鎖只能被一個(gè)進(jìn)程擁有
- LOCK_UN:表示刪除該進(jìn)程創(chuàng)建的鎖
- LOCK_MAND:它主要是用于共享模式強(qiáng)制鎖,它可以與 LOCK_READ 或者 LOCK_WRITE 聯(lián)合起來(lái)使用,從而表示是否允許并發(fā)的讀操作或者并發(fā)的寫(xiě)操作(盡管在 flock() 的手冊(cè)頁(yè)中沒(méi)有介紹 LOCK_MAND,但是閱讀內(nèi)核源代碼就會(huì)發(fā)現(xiàn),這在內(nèi)核中已經(jīng)實(shí)現(xiàn)了)
通常情況下,如果加鎖請(qǐng)求不能被立即滿(mǎn)足,那么系統(tǒng)調(diào)用 flock() 會(huì)阻塞當(dāng)前進(jìn)程。比如,進(jìn)程想要請(qǐng)求一個(gè)排他鎖,但此時(shí),已經(jīng)由其他進(jìn)程獲取了這個(gè)鎖,那么該進(jìn)程將會(huì)被阻塞。如果想要在沒(méi)有獲得這個(gè)排他鎖的情況下不阻塞該進(jìn)程,可以將 LOCK_NB 和 LOCK_SH 或者 LOCK_EX 聯(lián)合使用,那么系統(tǒng)就不會(huì)阻塞該進(jìn)程。flock() 所加的鎖會(huì)對(duì)整個(gè)文件起作用。
fcntl() 函數(shù)的功能很多,可以改變已打開(kāi)的文件的性質(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)的三個(gè)取值 F_GETLK、F_SETLK 以及 F_SETLKW。這三個(gè)值均與 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 用來(lái)指明創(chuàng)建的是共享鎖還是排他鎖,其取值有三種:F_RDLCK(共享鎖)、F_WRLCK(排他鎖)和F_UNLCK(刪除之前建立的鎖);l_pid 指明了該鎖的擁有者;l_whence、l_start 和l_end 這些字段指明了進(jìn)程需要對(duì)文件的哪個(gè)區(qū)域進(jìn)行加鎖,這個(gè)區(qū)域是一個(gè)連續(xù)的字節(jié)集合。因此,進(jìn)程可以對(duì)同一個(gè)文件的不同部分加不同的鎖。l_whence 必須是 SEEK_SET、SEEK_CUR 或 SEEK_END 這幾個(gè)值中的一個(gè),它們分別對(duì)應(yīng)著文件頭、當(dāng)前位置和文件尾。l_whence 定義了相對(duì)于 l_start 的偏移量,l_start 是從文件開(kāi)始計(jì)算的。
可以執(zhí)行的操作包括:
- F_GETLK:進(jìn)程可以通過(guò)它來(lái)獲取通過(guò) fd 打開(kāi)的那個(gè)文件的加鎖信息。執(zhí)行該操作時(shí),lock 指向的結(jié)構(gòu)中就保存了希望對(duì)文件加的鎖(或者說(shuō)要查詢(xún)的鎖)。如果確實(shí)存在這樣一把鎖,它阻止 lock 指向的 flock 結(jié)構(gòu)所給出的鎖描述符,則把現(xiàn)存的鎖的信息寫(xiě)到 lock 指向的 flock 結(jié)構(gòu)中,并將該鎖擁有者的 PID 寫(xiě)入 l_pid 字段中,然后返回;否則,就將 lock 指向的 flock 結(jié)構(gòu)中的 l_type 設(shè)置為 F_UNLCK,并保持 flock 結(jié)構(gòu)中其他信息不變返回,而不會(huì)對(duì)該文件真正加鎖。
- F_SETLK:進(jìn)程用它來(lái)對(duì)文件的某個(gè)區(qū)域進(jìn)行加鎖(l_type的值為 F_RDLCK 或 F_WRLCK)或者刪除鎖(l_type 的值為F_UNLCK),如果有其他鎖阻止該鎖被建立,那么 fcntl() 就出錯(cuò)返回
- F_SETLKW:與 F_SETLK 類(lèi)似,唯一不同的是,如果有其他鎖阻止該鎖被建立,則調(diào)用進(jìn)程進(jìn)入睡眠狀態(tài),等待該鎖釋放。一旦這個(gè)調(diào)用開(kāi)始了等待,就只有在能夠進(jìn)行加鎖或者收到信號(hào)時(shí)才會(huì)返回
需要注意的是,F(xiàn)_GETLK 用于測(cè)試是否可以加鎖,在 F_GETLK 測(cè)試可以加鎖之后,F(xiàn)_SETLK 和 F_SETLKW 就會(huì)企圖建立一把鎖,但是這兩者之間并不是一個(gè)原子操作,也就是說(shuō),在 F_SETLK 或者 F_SETLKW 還沒(méi)有成功加鎖之前,另外一個(gè)進(jìn)程就有可能已經(jīng)插進(jìn)來(lái)加上了一把鎖。而且,F(xiàn)_SETLKW 有可能導(dǎo)致程序長(zhǎng)時(shí)間睡眠。還有,程序?qū)δ硞€(gè)文件擁有的各種鎖會(huì)在相應(yīng)的文件描述符被關(guān)閉時(shí)自動(dòng)清除,程序運(yùn)行結(jié)束后,其所加的各種鎖也會(huì)自動(dòng)清除。
fcntl() 既可以用于勸告鎖,也可以用于強(qiáng)制鎖,在默認(rèn)情況下,它用于勸告鎖。如果它用于強(qiáng)制鎖,當(dāng)進(jìn)程對(duì)某個(gè)文件進(jìn)行了讀或?qū)戇@樣的系統(tǒng)調(diào)用時(shí),系統(tǒng)則會(huì)檢查該文件的鎖的 O_NONBLOCK 標(biāo)識(shí),該標(biāo)識(shí)是文件狀態(tài)標(biāo)識(shí)的一種,如果設(shè)置文件狀態(tài)標(biāo)識(shí)的時(shí)候設(shè)置了 O_NONBLOCK,則該進(jìn)程會(huì)出錯(cuò)返回;否則,該進(jìn)程被阻塞。cmd 參數(shù)的值 F_SETFL 可以用于設(shè)置文件狀態(tài)標(biāo)識(shí)。
此外,系統(tǒng)調(diào)用 fcntl() 還可以用于租借鎖,此時(shí)采用的函數(shù)原型如下:
int fcntl(int fd, int cmd, long arg); |
與租借鎖相關(guān)的 cmd 參數(shù)的取值有兩種:F_SETLEASE 和 F_GETLEASE。其含義如下所示:
- F_SETLEASE:根據(jù)下面所描述的 arg 參數(shù)指定的值來(lái)建立或者刪除租約:
- F_RDLCK:設(shè)置讀租約。當(dāng)文件被另一個(gè)進(jìn)程以寫(xiě)的方式打開(kāi)時(shí),擁有該租約的當(dāng)前進(jìn)程會(huì)收到通知
- F_WRLCK:設(shè)置寫(xiě)租約。當(dāng)文件被另一個(gè)進(jìn)程以讀或者寫(xiě)的方式打開(kāi)時(shí),擁有該租約的當(dāng)前進(jìn)程會(huì)收到通知
- F_UNLCK:刪除以前建立的租約
- F_GETLEASE:表明調(diào)用進(jìn)程擁有文件上哪種類(lèi)型的鎖,這需要通過(guò)返回值來(lái)確定,返回值有三種:F_RDLCK、F_WRLCK和F_UNLCK,分別表明調(diào)用進(jìn)程對(duì)文件擁有讀租借、寫(xiě)租借或者根本沒(méi)有租借
某個(gè)進(jìn)程可能會(huì)對(duì)文件執(zhí)行其他一些系統(tǒng)調(diào)用(比如 OPEN() 或者 TRUNCATE()),如果這些系統(tǒng)調(diào)用與該文件上由 F_SETLEASE 所設(shè)置的租借鎖相沖突,內(nèi)核就會(huì)阻塞這個(gè)系統(tǒng)調(diào)用;同時(shí),內(nèi)核會(huì)給擁有這個(gè)租借鎖的進(jìn)程發(fā)信號(hào),告知此事。擁有此租借鎖的進(jìn)程會(huì)對(duì)該信號(hào)進(jìn)行反饋,它可能會(huì)刪除這個(gè)租借鎖,也可能會(huì)減短這個(gè)租借鎖的租約,從而可以使得該文件可以被其他進(jìn)程所訪問(wèn)。如果擁有租借鎖的進(jìn)程不能在給定時(shí)間內(nèi)完成上述操作,那么系統(tǒng)會(huì)強(qiáng)制幫它完成。通過(guò) F_SETLEASE 命令將 arg 參數(shù)指定為 F_UNLCK 就可以刪除這個(gè)租借鎖。不管對(duì)該租借鎖減短租約或者干脆刪除的操作是進(jìn)程自愿的還是內(nèi)核強(qiáng)迫的,只要被阻塞的系統(tǒng)調(diào)用還沒(méi)有被發(fā)出該調(diào)用的進(jìn)程解除阻塞,那么系統(tǒng)就會(huì)允許這個(gè)系統(tǒng)調(diào)用執(zhí)行。即使被阻塞的系統(tǒng)調(diào)用因?yàn)槟承┰虮唤獬枞巧厦鎸?duì)租借鎖減短租約或者刪除這個(gè)過(guò)程還是會(huì)執(zhí)行的。
需要注意的是,租借鎖也只能對(duì)整個(gè)文件生效,而無(wú)法實(shí)現(xiàn)記錄級(jí)的加鎖。
![]() ![]() |
![]() |
為了使讀者更深入理解本文中介紹的內(nèi)容,下面我們給出了一個(gè)例子來(lái)詳細(xì)介紹文件鎖的具體用法。這個(gè)例子可以用來(lái)檢測(cè)所使用的文件是否支持強(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)介紹過(guò)了,其基本思想是在首先在父進(jìn)程中對(duì)文件加上寫(xiě)鎖;然后在子進(jìn)程中將文件描述符設(shè)置為非阻塞模式(第 69 行),然后對(duì)文件加讀鎖,并嘗試讀取該文件中的內(nèi)容。如果系統(tǒng)支持強(qiáng)制鎖,則子進(jìn)程中的 read() 系統(tǒng)調(diào)用(代碼中的第 81 行)會(huì)立即返回 EAGAIN;否則,等父進(jìn)程完成寫(xiě)文件操作之后,子進(jìn)程中的 read() 系統(tǒng)調(diào)用就會(huì)返回父進(jìn)程剛剛寫(xiě)入的前 5 個(gè)字節(jié)的數(shù)據(jù)。代碼中的幾個(gè) sleep() 是為了協(xié)調(diào)父進(jìn)程與子進(jìn)程之間的同步而使用的。
該程序在測(cè)試系統(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 選項(xiàng)掛載到了 /mnt 目錄中,而 /dev/sdb6 則么有使用這個(gè)選項(xiàng)掛載到了 /tmp/mnt 目錄中。由于在程序中我們完成了對(duì)測(cè)試文件 SGID 和同組可執(zhí)行位的設(shè)置(第 44 行),因此 /mnt/testfile 可以支持強(qiáng)制鎖,而 /tmp/mnt/testfile 則不能。這也正是為什么前者的 read() 系統(tǒng)調(diào)用會(huì)失敗返回而后者則可以成功讀取到 hello 的原因。
![]() ![]() |
![]() |
Linux 的文件鎖在以共享索引節(jié)點(diǎn)共享文件的情況下設(shè)計(jì)的,文件鎖的實(shí)現(xiàn)可以使得不同用戶(hù)同時(shí)讀寫(xiě)同一文件的并發(fā)問(wèn)題得以解決。本文描述了 Linux 中各類(lèi)文件鎖的概念,使用場(chǎng)景,內(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),以及在對(duì)文件進(jìn)行加鎖時(shí)遇到的死鎖問(wèn)題等其他知識(shí),這里沒(méi)有做詳盡介紹,感興趣的讀者可以自行參考內(nèi)核源代碼。
本文的目的是想幫助讀者理清 Linux 2.6中文件鎖的概念以及 Linux 2.6 都提供了何種數(shù)據(jù)結(jié)構(gòu)以及關(guān)鍵的系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)文件鎖,從而可以幫助讀者更好地使用文件鎖來(lái)解決多個(gè)進(jìn)程讀取同一個(gè)文件的互斥問(wèn)題。
![]() ![]() |
![]() |
- Linux 內(nèi)核源代碼中包含了文件鎖的具體實(shí)現(xiàn),本文中引用的內(nèi)核代碼來(lái)自于 Linux 2.6.23 版本的內(nèi)核。
- File Locking in Linux 2.5一文中介紹了在 Linux 2.5 開(kāi)發(fā)時(shí)對(duì)文件鎖的一些設(shè)計(jì)考慮。
- The Single UNIX® Specification, Version 3 規(guī)范中對(duì) fcntl 系統(tǒng)調(diào)用進(jìn)行了詳細(xì)的規(guī)定。
- Linux 內(nèi)核情景分析(上)一書(shū)的文件系統(tǒng)一章詳細(xì)描述了 Linux 內(nèi)核對(duì)各種不同類(lèi)鎖的管理和控制,講述了 Linux 文件鎖的關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)及其實(shí)現(xiàn),以及文件鎖對(duì)其他的文件模式和操作的影響
- 在 Understanding the Linux Kernel(3rd Edition) 中,有關(guān)于 Linux 中不同種類(lèi)文件鎖詳細(xì)的實(shí)現(xiàn)過(guò)程
- 更多的關(guān)于 Linux 開(kāi)發(fā)的資源,參見(jiàn) developerWorks 上 Linux 專(zhuān)區(qū)。