整個文檔中都會使用到的程序.shell
/** * fcntl [-t TYPE][-w WHENCE][-s START][-l LEN][-c OFF] file. * fcntl() 的接口,用於對一個指定的文件添加指定的字節範圍鎖. * 若文件上已經存在一把鎖,阻止建立指定的鎖,則打印該鎖的信息. * -t TYPE | --type=TYPE 字節範圍鎖的類型,TYPE 可取值: wrlck(默認),rdlck. * -w WHENCE|--whence=WHENCE 指定了 l_whence 的值,WHENCE 可取值: cur(默認),set,end. * -s START | --start=START 指定了 l_start 的值.默認爲 0. * -l LEN | --len=LEN 指定了 l_len 的值,默認爲 0. * -c OFF | --cur=OFF 指定了文件讀寫指針的值,默認爲 0,即會在 fcntl() 以前調用 lseek(fd,OFF,SEEK_SET); */
功能: 當一個進程正在讀或寫文件的一部分時,字節範圍鎖能夠阻止其餘進程修改同一文件區.
將文件視爲長度爲 L 的字節數組(L 爲文件長度),則字節範圍鎖能夠確保在任意時刻,對於字節數組的任意部分[begin,end),只有一個進程能夠讀寫,其中begn,end 爲字節數組的索引值.如:數組
struct flock 中的 l_start,l_whence,l_len 域定義了加鎖/解鎖區域的範圍 [begin,end),其中 begin,end 的值以下:ide
if(l_whence==SEEK_SET) l_whence=0; else if(l_whence==SEEK_END) l_whence=lseek(fd,0,SEEK_END);/* 文件長度 */ else l_whence=lseek(fd,0,SEEK_CUR);/* 當前讀寫指針 */ begin=l_whence+l_start; end=begin+l_len;
兼容性與讀寫鎖 pthread_rwlock_t 規則一致:spa
多個進程在一個給定的字節上能夠有一把共享的讀鎖.可是在一個給定的字節上,只能有一個進程獨用的一把寫鎖.3d
若是在一個給定的字節上已經有一把或多把讀鎖,則不能在該字節上再加寫鎖.若是在一個字節上已經有一把獨佔性的寫鎖,則不能再對它加任何讀鎖/寫鎖指針
不過要注意下面兩點:code
一個給定的字節! 當進程 A 在範圍 [33,77) 上擁有一把 F_WRLCK 鎖時,進程 B 在 [76,78) 上添加 F_RDLCK 鎖,是不被容許的,由於字節 [76,77) 上已經有了一把寫鎖.繼承
$ ./fcntl --type=wrlck --start=33 --len=44 mycp.cc 2373: 0x7ffa7c9c3740: 93: 對文件 mycp.cc 加鎖成功,鎖的描述: l_type: F_WRLCK l_start: 33 l_whence: SEEK_SET l_len: 44 l_pid: 0 $ ./fcntl --start=76 --len=2 mycp.cc 2387: 0x7f50750b4740: 97: 對文件 mycp.cc 加鎖失敗,現存鎖的描述: l_type: F_WRLCK l_start: 33 l_whence: SEEK_SET l_len: 44 l_pid: 2373
進程對一個給定的字節上已經擁有了一把鎖,後來該進程又試圖在同一字節再加一把鎖,那麼新鎖將替換老鎖!.索引
int main(int argc,char *argv[]){ signal(SIGINT,default_sigcatch); struct flock filelock; filelock.l_type=F_WRLCK; filelock.l_start=33; filelock.l_whence=SEEK_SET; filelock.l_len=77; int fd; R_1(fd=open(argv[1],O_RDWR|O_CREAT,0666)); R_1(fcntl(fd,F_SETLK,&filelock)); Rep("對文件 %s 加鎖成功,鎖的描述: ",argv[1]); print_flock(&filelock); pause(); filelock.l_type=F_RDLCK; filelock.l_start=44; filelock.l_len=2; R_1(fcntl(fd,F_SETLK,&filelock)); Rep("對文件 %s 加鎖成功,鎖的描述: ",argv[1]); print_flock(&filelock); pause(); } $ ./Debug/Test mycp.cc 2503: 0x7f62bbef5740: 25: 對文件 mycp.cc 加鎖成功,鎖的描述: l_type: F_WRLCK l_start: 33 l_whence: SEEK_SET l_len: 77 l_pid: 4196928 /** * 此時 mycp.cc 上鎖的狀況: * 範圍 擁有進程的進程 ID 鎖的類型 * [33,110) 2503 F_WRLCK */ ^C 2503: 0x7f62bbef5740: 31: 捕捉到信號: Interrupt 2503: 0x7f62bbef5740: 33: 對文件 mycp.cc 加鎖成功,鎖的描述: l_type: F_RDLCK l_start: 44 l_whence: SEEK_SET l_len: 2 l_pid: 4196928 /* * 此時 mycp.cc 上鎖的狀況: * 範圍 擁有進程的進程 ID 鎖的類型 * [33,44) 2503 F_WRLCK * [44,46) 2503 F_RDLCK // 新鎖替換老鎖. * [46,110) 2503 F_WRLCK. * 以下,可使用 ./fcntl 命令驗證: */ $ ./fcntl --start=43 --len=33 mycp.cc 2533: 0x7f57fa676740: 97: 對文件 mycp.cc 加鎖失敗,現存鎖的描述: l_type: F_WRLCK l_start: 33 l_whence: SEEK_SET l_len: 11 l_pid: 2503 $ ./fcntl --start=45 --len=33 mycp.cc 2534: 0x7f9ce58fc740: 97: 對文件 mycp.cc 加鎖失敗,現存鎖的描述: l_type: F_RDLCK l_start: 44 l_whence: SEEK_SET l_len: 2 l_pid: 2503 $ ./fcntl --start=47 --len=33 mycp.cc 2537: 0x7f7929a47740: 97: 對文件 mycp.cc 加鎖失敗,現存鎖的描述: l_type: F_WRLCK l_start: 46 l_whence: SEEK_SET l_len: 64 l_pid: 2503
若進程在 [a,b),[c,d) 上均有一把寫鎖,則當該進程再在 [b,c) 上再加一把寫鎖時,內核會自動將這三把鎖組合成一把寫鎖,範圍爲 [a,d).接口
int main(int argc,char *argv[]){ signal(SIGINT,default_sigcatch); int fd; int a=33,b=44; int c=55,d=77; R_1(fd=open(argv[1],O_RDWR|O_CREAT,0666)); struct flock filelock; filelock.l_type=F_WRLCK; filelock.l_whence=SEEK_SET; /* 在 [a,b) 範圍上寫鎖 */ filelock.l_start=a; filelock.l_len=b-a; R_1(fcntl(fd,F_SETLK,&filelock)); Rep("對文件 %s 範圍 [%d,%d) 加寫鎖成功",argv[1],a,b); /* 在 [c,d) 範圍上寫鎖 */ filelock.l_start=c; filelock.l_len=d-c; R_1(fcntl(fd,F_SETLK,&filelock)); Rep("對文件 %s 範圍 [%d,%d) 加寫鎖成功",argv[1],c,d); pause(); /* 在 [b,c) 範圍上寫鎖 */ filelock.l_start=b; filelock.l_len=c-b; R_1(fcntl(fd,F_SETLK,&filelock)); Rep("對文件 %s 範圍 [%d,%d) 加寫鎖成功",argv[1],b,c); pause(); } $ ./Debug/Test mycp.cc 2692: 0x7f3b3dd8b740: 26: 對文件 mycp.cc 範圍 [33,44) 加寫鎖成功 2692: 0x7f3b3dd8b740: 32: 對文件 mycp.cc 範圍 [55,77) 加寫鎖成功 ^C 2774: 0x7f3b3dd8b740: 31: 捕捉到信號: Interrupt 2774: 0x7f3b3dd8b740: 40: 對文件 mycp.cc 範圍 [44,55) 加寫鎖成功 /* 在另外一個終端上啓動 ./fcntl 驗證 */ $ ./fcntl --start=34 --len=2 mycp.cc 2711: 0x7f4858e86740: 97: 對文件 mycp.cc 加鎖失敗,現存鎖的描述: l_type: F_WRLCK l_start: 33 /* 寫鎖範圍 [33,44) */ l_whence: SEEK_SET l_len: 11 l_pid: 2692 $ ./fcntl --start=56 --len=2 mycp.cc 2712: 0x7f00feaf8740: 97: 對文件 mycp.cc 加鎖失敗,現存鎖的描述: l_type: F_WRLCK l_start: 55 /* 寫鎖範圍 [55,77) */ l_whence: SEEK_SET l_len: 22 l_pid: 2692 $ ./fcntl --start=56 --len=2 mycp.cc 2713: 0x7f7998a38740: 97: 對文件 mycp.cc 加鎖失敗,現存鎖的描述: l_type: F_WRLCK l_start: 33 /* 寫鎖被合併,此時範圍 [33,77) */ l_whence: SEEK_SET l_len: 44 l_pid: 2692
若進程在 [a,b) 上有一個寫鎖,則當進程再在 [c,d) 上加一個讀鎖時,其中 a<c<d<b.則此時 [a,b) 上的寫鎖會自動拆裂爲兩把寫鎖,範圍分別是 [a,c) [d,b) (上上面的程序能夠驗證).
進程 A 進程 B 在範圍 [0,1) 上寫鎖 在範圍 [1,2) 上寫鎖 等待 B 加鎖完畢 等待 A 加鎖完畢 在範圍 [1,2) 上寫鎖 在範圍 [0,1) 上寫鎖 /* 內核能夠檢測出死鎖. */
僅當以 F_SETLKW 命令加鎖時,纔有可能死鎖.當內核檢測出死鎖時,會讓其餘一個進程 fcntl() 出錯返回.而另一個進程阻塞直至加鎖成功或者被信號處理程序中斷.
int main( int argc,char *argv[] ){ int field; R_1(field=open(argv[1],O_RDWR|O_CREAT,0666)); struct flock lock; lock.l_type=F_WRLCK; lock.l_whence=SEEK_SET; lock.l_len=1; signal(SIGUSR1,sigcatch); /** 阻塞 SIGUSR1 信號 */ sigset_t sigs; sigemptyset(&sigs); sigaddset(&sigs,SIGUSR1); sigprocmask(SIG_BLOCK,&sigs,0); sigfillset(&sigs); sigdelset(&sigs,SIGUSR1); pid_t child; R_1(child=fork()); if(child==0){ /* [0,1) 加寫鎖. */ lock.l_start=0; R_1(fcntl(field,F_SETLKW,&lock)); Rep("已經在範圍 [0,1) 上加寫鎖"); kill(getppid(),SIGUSR1); sigsuspend(&sigs);/* 等待父進程加鎖完畢 */ Rep("準備在範圍 [1,2) 上加寫鎖"); /* [1,2) 加寫鎖 */ lock.l_start=1; errno=0; fcntl(field,F_SETLKW,&lock);/* 這裏形成死鎖,此時子進程阻塞,直至成功加鎖. */ Rep("%m\n"); }else{ /* [1,2) 加寫鎖 */ lock.l_start=1; R_1(fcntl(field,F_SETLKW,&lock)); Rep("已經在範圍 [1,2) 上加寫鎖"); kill(child,SIGUSR1); sigsuspend(&sigs);/* 等待子進程加鎖完畢 */ Rep("準備在範圍 [0,1) 上加寫鎖"); /* [0,1) 加寫鎖 */ lock.l_start=0; errno=0; fcntl(field,F_SETLKW,&lock); /* 這裏形成死鎖,父進程在這裏出錯返回. */ Rep("%m\n"); } pause(); return 0; } $ ./Debug/Test mycp.cc & [1] 2940 2940: 0x7f7577bf5740: 56: 已經在範圍 [1,2) 上加寫鎖 2941: 0x7f7577bf5740: 42: 已經在範圍 [0,1) 上加寫鎖 2941: 0x7f7577bf5740: 46: 準備在範圍 [1,2) 上加寫鎖 2940: 0x7f7577bf5740: 60: 準備在範圍 [0,1) 上加寫鎖 2940: 0x7f7577bf5740: 65: Resource deadlock avoided /* 父進程檢測出死鎖,子進程阻塞. */ $ kill -SIGINT 2940 /* 父進程終止,其擁有的文件鎖被釋放. */ 2941: 0x7f7577bf5740: 51: Success /* 因此子進程成功加鎖. */
當進程以 F_SETLK (或者 F_SETLKW ) 而且 l_type==F_UNLCK 調用 fcntl() 時會釋放指定範圍的鎖.如:
int main(int argc,char *argv[]){ signal(SIGINT,default_sigcatch); int a=33,b=77,c=44,d=55; struct flock lock={F_WRLCK,SEEK_SET,a,b-a}; int fd; R_1(fd=open(argv[1],O_RDWR|O_CREAT,0666)); R_1(fcntl(fd,F_SETLK,&lock)); Rep("對文件 %s 範圍 [%d,%d) 加鎖成功",argv[1],a,b); pause(); lock.l_type=F_UNLCK; lock.l_start=c; lock.l_len=d-c; R_1(fcntl(fd,F_SETLK,&lock)); Rep("釋放文件 %s 範圍 [%d,%d) 範圍上的鎖",argv[1],c,d); /* 此時 [a,c),[d,b) 上的寫鎖被保留 */ pause(); }
當進程終止時,其擁有的全部文件鎖都被釋放.
文件描述符 fd 關聯到文件 filename.txt,則當 fd 被進程關閉時,進程在 filename.txt 上的鎖也都將被釋放.
int fd1=open("filename.txt",O_RDWR); fcntl(fd1,F_SETLK,&lock1); fcntl(fd1,F_SETLK,&lock2); fcntl(fd1,F_SETLK,&lock3); int fd2=dup(fd1);/* 或者 int fd2=open("filename",O_RDWR); */ close(fd2); /* fd2 關聯到文件 filename.txt,當 fd2 被進程關閉時, * 進程在 filename.txt 上的鎖 lock1,lock2,lock3 也都被釋放 */
由 fork() 建立的子進程,並不會繼承父進程擁有的文件鎖.很顯然,子進程是一個新的進程嘛.
調用 exec() 後並不會清除原進程擁有的文件鎖,新程序能夠繼承原執行程序的鎖.只是當文件描述符設置了 close-on-exec 標誌後,則做爲 exec() 的一部分關閉該文件描述符時,對相應文件的全部鎖也都被釋放了.
建議性鎖,則其餘進程在不擁有鎖的前提下,也能夠對加鎖範圍進行讀寫.
強制性鎖,則其餘進程在不擁有鎖的前提下,對加鎖範圍進行讀寫可能會阻塞,或者不容許對加鎖範圍進行讀寫.