1. 程式人生 > >Linux 2.6 中的檔案鎖

Linux 2.6 中的檔案鎖

原文連結:http://www.ibm.com/developerworks/cn/linux/l-cn-filelock/

在多工作業系統環境中,如果一個程序嘗試對正在被其他程序讀取的檔案進行寫操作,可能會導致正在進行讀操作的程序讀取到一些被破壞或者不完整的資料;如果兩個程序併發對同一個檔案進行寫操作,可能會導致該檔案遭到破壞。因此,為了避免發生這種問題,必須要採用某種機制來解決多個程序併發訪問同一個檔案時所面臨的同步問題,由此而產生了檔案加鎖方面的技術。

早期的 UNIX 系統只支援對整個檔案進行加鎖,因此無法執行資料庫之類的程式,因為此類程式需要實現記錄級的加鎖。在 System V Release 3 中,通過 fcntl 提供了記錄級的加鎖,此後發展成為 POSIX 標準的一部分。本文將基於 2.6.23 版本的核心來探討 Linux 中檔案鎖的相關技術。

Linux 中的檔案鎖

Linux 支援的檔案鎖技術主要包括勸告鎖(advisory lock)和強制鎖(mandatory lock)這兩種。此外,Linux 中還引入了兩種強制鎖的變種形式:共享模式強制鎖(share-mode mandatory lock)和租借鎖(lease)。

在 Linux 中,不論程序是在使用勸告鎖還是強制鎖,它都可以同時使用共享鎖和排他鎖(又稱為讀鎖和寫鎖)。多個共享鎖之間不會相互干擾,多個程序在同一時刻可以對同一個檔案加共享鎖。但是,如果一個程序對該檔案加了排他鎖,那麼其他程序則無權再對該檔案加共享鎖或者排他鎖,直到該排他鎖被釋放。所以,對於同一個檔案來說,它可以同時擁有很多讀者,但是在某一特定時刻,它只能擁有一個寫者,它們之間的相容關係如表 1 所示。

表 1. 鎖間的相容關係
是否滿足請求
當前加上的鎖 共享鎖 排他鎖
共享鎖
排他鎖

勸告鎖

勸告鎖是一種協同工作的鎖。對於這一種鎖來說,核心只提供加鎖以及檢測檔案是否已經加鎖的手段,但是核心並不參與鎖的控制和協調。也就是說,如果有程序不遵守“遊戲規則”,不檢查目標檔案是否已經由別的程序加了鎖就往其中寫入資料,那麼核心是不會加以阻攔的。因此,勸告鎖並不能阻止程序對檔案的訪問,而只能依靠各個程序在訪問檔案之前檢查該檔案是否已經被其他程序加鎖來實現併發控制。程序需要事先對鎖的狀態做一個約定,並根據鎖的當前狀態和相互關係來確定其他程序是否能對檔案執行指定的操作。從這點上來說,勸告鎖的工作方式與使用訊號量保護臨界區的方式非常類似。

勸告鎖可以對檔案的任意一個部分進行加鎖,也可以對整個檔案進行加鎖,甚至可以對檔案將來增大的部分也進行加鎖。由於程序可以選擇對檔案的某個部分進行加鎖,所以一個程序可以獲得關於某個檔案不同部分的多個鎖。

強制鎖

與勸告鎖不同,強制鎖是一種核心強制採用的檔案鎖,它是從 System V Release 3 開始引入的。每當有系統呼叫 open()、read() 以及write() 發生的時候,核心都要檢查並確保這些系統呼叫不會違反在所訪問檔案上加的強制鎖約束。也就是說,如果有程序不遵守遊戲規則,硬要往加了鎖的檔案中寫入內容,核心就會加以阻攔:

如果一個檔案已經被加上了讀鎖或者共享鎖,那麼其他程序再對這個檔案進行寫操作就會被核心阻止;

如果一個檔案已經被加上了寫鎖或者排他鎖,那麼其他程序再對這個檔案進行讀取或者寫操作就會被核心阻止。

如果其他程序試圖訪問一個已經加有強制鎖的檔案,程序行為取決於所執行的操作模式和檔案鎖的型別,歸納如表 2 所示:

表 2. 進行對已加強制鎖的檔案進行操作時的行為
當前鎖型別 阻塞讀 阻塞寫 非阻塞讀 非阻塞寫
讀鎖 正常讀取資料 阻塞 正常讀取資料 EAGAIN
寫鎖 阻塞 阻塞 EAGAIN EAGAIN

需要注意的是,如果要訪問的檔案的鎖型別與要執行的操作存在衝突,那麼採用阻塞讀/寫操作的程序會被阻塞,而採用非阻塞讀/寫操作的程序則不會阻塞,而是立即返回 EAGAIN。

另外,unlink() 系統呼叫並不會受到強制鎖的影響,原因在於一個檔案可能存在多個硬連結,此時刪除檔案時並不會修改檔案本身的內容,而是隻會改變其父目錄中 dentry 的內容。

然而,在有些應用中並不適合使用強制鎖,所以索引節點結構中的 i_flags 欄位中定義了一個標誌位MS_MANDLOCK用於有選擇地允許或者不允許對一個檔案使用強制鎖。在 super_block 結構中,也可以將 s_flags 這個標誌為設定為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 才行)。

Linux 核心中關於檔案鎖的實現

在 Linux 核心中,所有型別的檔案鎖都是由資料結構 file_lock 來描述的,file_lock 結構是在 檔案中定義的,內容如下所示:

清單 1. file_lock 結構
         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 結構中的各個欄位所表示的含義。

表 3. 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 鎖則一直與一個程序以及一個索引節點相關聯。當程序死亡或者檔案描述符被關閉的時候,這個鎖會被自動釋放。

對於強制鎖來說,在 Linux 中,核心提供了 inline 函式 locks_verify_locked() 用於檢測目標檔案或者目標檔案所在的裝置是否允許使用強制鎖,並且檢查該裝置是否已經加上了鎖,相關函式如下所示:

清單 3. 與強制鎖相關的函式
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 }

這裡,函式 locks_verify_locked() 利用巨集 MANDATORY_LOCK 來檢測目標檔案是否允許加鎖,條件包括:該檔案所在的裝置的 super_block 結構中的 s_flags 必須被置為 1,該檔案的 SGID 被置為 1 而且同組可執行位被清 0。如果允許,則呼叫函式locks_mandatory_locked(),該函式從索引節點的鎖列表中查詢是否存在有與其相沖突的鎖,即是否已經加上了鎖。

Linux 中關於檔案鎖的系統呼叫

這裡介紹在 Linux 中與檔案鎖關係密切的兩個系統呼叫:flock() 和 fcntl()。勸告鎖既可以通過系統呼叫 flock() 來實現,也可以通過系統呼叫 fcntl() 來實現。flock() 系統呼叫是從 BSD 中衍生出來的,在傳統的類 UNIX 作業系統中,系統呼叫flock() 只適用於勸告鎖。但是,Linux 2.6核心利用系統呼叫 flock() 實現了我們前面提到的特殊的強制鎖:共享模式強制鎖。另外,flock() 只能實現對整個檔案進行加鎖,而不能實現記錄級的加鎖。系統呼叫fcntl() 符合 POSIX 標準的檔案鎖實現,它也是非常強大的檔案鎖,fcntl() 可以實現對紀錄進行加鎖。

flock()

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() 所加的鎖會對整個檔案起作用。

fcntl()

fcntl() 函式的功能很多,可以改變已開啟的檔案的性質,本文中只是介紹其與獲取/設定檔案鎖有關的功能。fcntl() 的函式原型如下所示:

       int fcntl (int fd, int cmd, struct flock *lock);

其中,引數 fd 表示檔案描述符;引數 cmd 指定要進行的鎖操作,由於 fcntl() 函式功能比較多,這裡先介紹與檔案鎖相關的三個取值 F_GETLK、F_SETLK 以及 F_SETLKW。這三個值均與 flock 結構有關。flock 結構如下所示:

清單 4. 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 可以用於設定檔案狀態標識。

此外,系統呼叫 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 就可以刪除這個租借鎖。不管對該租借鎖減短租約或者乾脆刪除的操作是程序自願的還是核心強迫的,只要被阻塞的系統呼叫還沒有被髮出該呼叫的程序解除阻塞,那麼系統就會允許這個系統呼叫執行。即使被阻塞的系統呼叫因為某些原因被解除阻塞,但是上面對租借鎖減短租約或者刪除這個過程還是會執行的。

需要注意的是,租借鎖也只能對整個檔案生效,而無法實現記錄級的加鎖。

檔案鎖的使用樣例

為了使讀者更深入理解本文中介紹的內容,下面我們給出了一個例子來詳細介紹檔案鎖的具體用法。這個例子可以用來檢測所使用的檔案是否支援強制鎖,其原始碼如下所示:

清單 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