記錄鎖至關於線程同步中讀寫鎖的一種擴展類型,能夠用來對有親緣或無親緣關係的進程進行文件讀與寫的同步,經過fcntl函數來執行上鎖操做。儘管讀寫鎖也能夠經過在共享內存區來進行進程的同步,可是fcntl記錄上鎖每每更容易使用,且效率更高。函數
記錄鎖的功能:當一個進程正在讀或修改文件的某個部分是,它能夠阻止其餘進程修改同一文件區。對於這個功能闡述我認爲有三點要解釋的:測試
記錄上鎖的POSIX接口函數fcntl以下:spa
/* Do the file control operation described by CMD on FD. The remaining arguments are interpreted depending on CMD. */ int fcntl (int __fd, int __cmd, ...); //根據cmd的不一樣有如下三種類型的調用 int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); int fcntl(int fd, int cmd, struct flock *lock);
由函數名稱可知fcntl的功能是對文件的控制操做,根據傳入不一樣的操做類型命令cmd,fcntl會執行不一樣的操做,fcnt根據cmd不一樣,接收可變的參數。具體有如下五種類型的操做:.net
/* cmd = F_DUPFD,複製一個文件描述符; */ int fcntl(int fd, int cmd); /* cmd = F_GETFD,得到文件描述符標誌; cmd = F_SETFD,設置文件描述符標誌;arg = 描述符標誌的值,目前只定義了一個標誌: FD_CLOEXEC int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); */ /* cmd = F_GETFL,得到文件狀態標誌; cmd = F_SETFL,設置文件狀態標誌;arg = 狀態標誌的值 int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); */ /* cmd = F_GETOWN,得到當前接收SIGIO和SIGURG信號的進程ID或進程組ID cmd = F_SETOWN,設置接收SIGIO和SIGURG信號的進程ID或進程組ID;arg = 進程ID或進程組ID int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); */ /* Return value: 對於成功的調用,根據操做類型cmd不一樣,有如下幾種狀況: F_DUPFD 返回新的文件描述符 F_GETFD 返回文件描述符標誌 F_GETFL 返回文件狀態標誌 F_GETOWN 進程ID或進程組ID All other commands 返回0 調用失敗, 返回-1,並設置errno。 */
上面四個功能都是fcntl提供的很經常使用的操做,關於記錄鎖的功能就是fcntl提供的第五個功能,具體使用以下:線程
int fcntl(int fd, int cmd, struct flock *lock); /* cmd = F_GETLK,測試可否創建一把鎖 cmd = F_SETLK,設置鎖 cmd = F_SETLKW, 阻塞設置一把鎖 */ //POSIX只定義fock結構中必須有如下的數據成員,具體實現能夠增長 struct flock { short l_type; /* 鎖的類型: F_RDLCK, F_WRLCK, F_UNLCK */ short l_whence; /* 加鎖的起始位置:SEEK_SET, SEEK_CUR, SEEK_END */ off_t l_start; /* 加鎖的起始偏移,相對於l_whence */ off_t l_len; /* 上鎖的字節數*/ pid_t l_pid; /* 已經佔用鎖的PID(只對F_GETLK 命令有效) */ /*...*/ }; //Return value: 前面已經說明;
l F_SETLK:獲取(l_type爲F_RDLCK或F_WRLCK)或釋放由lock指向flock結構所描述的鎖,若是沒法獲取鎖時,該函數會當即返回一個EACCESS或EAGAIN錯誤,而不會阻塞。code
l F_SETLKW:F_SETLKW和F_SETLK的區別是,沒法設置鎖的時候,調用線程會阻塞到該鎖可以受權位置。blog
l F_GETLK:IF_GETLK主要用來檢測是否有某個已存在鎖會妨礙將新鎖授予調用進程,若是沒有這樣的鎖,lock所指向的flock結構的l_type成員就會被置成F_UNLCK,不然已存在的鎖的信息將會寫入lock所指向的flock結構中繼承
這裏須要注意的是,用F_GETLK測試可否創建一把鎖,而後接着用F_SETLK或F_SETLKW企圖創建一把鎖,因爲這二者不是一個原子操做,因此不能保證兩次fcntl之間不會有另一個進程插入並創建一把相關的鎖,從而使一開始的測試狀況無效。因此通常不但願上鎖時阻塞,會直接經過調用F_SETLK,並對返回結果進行測試,以判斷是否成功創建所要求的鎖。接口
前面咱們說了記錄鎖至關於讀寫鎖的一種擴展類型,記錄鎖和讀寫鎖同樣也有兩種鎖:共享讀鎖(F_RDLCK)和獨佔寫鎖(F_WRLCK)。在使用規則上和讀寫鎖也基本同樣:進程
以下表所示:
須要說明的是:上面所闡述的規則只適用於不一樣進程提出的鎖請求,並不適用於單個進程提出的多個鎖請求。即若是一個進程對一個文件區間已經有了一把鎖,後來該進程又試圖在同一文件區間再加一把鎖,那麼新鎖將會覆蓋老鎖。
下面進行測試;第一個程序是在同一進程中測試可否在加寫鎖後,繼續加讀寫鎖。第二個程序是在在父進程中加寫鎖後,而後再子進程中測試可否繼續加讀寫鎖。
//調用的函數,在文章末尾貼出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); cout<<lock_test(fd, F_WRLCK, SEEK_SET, 0, 0)<<endl; cout<<lock_test(fd, F_RDLCK, SEEK_SET, 0, 0)<<endl; unlock(fd); return 0; }
執行結果爲:
0 0
代表同一進程能夠對已加鎖的同一文件區間,仍然能得到加鎖權限;
//調用的函數,在文章末尾貼出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); if (fork() == 0) { cout<<lock_test(fd, F_WRLCK, SEEK_SET, 0, 0)<<endl; cout<<lock_test(fd, F_RDLCK, SEEK_SET, 0, 0)<<endl; exit(0); } sleep(3); unlock(fd); return 0; }
執行結果爲:
24791 24791
代表不一樣進程不能對已加寫鎖的同一文件區間,得到加鎖權限;
還有就是:加鎖時,該進程必須對該文件有相應的文件訪問權限,即加讀鎖,該文件必須是讀打開,加寫鎖時,該文件必須是寫打開。
這裏要提到兩個概念:記錄上鎖和文件上鎖。
記錄上鎖:對於UNIX系統而言,「記錄」這一詞是一種誤用,由於UNIX系統內核根本沒有使用文件記錄這種概念,更適合的術語應該是字節範圍鎖,由於它鎖住的只是文件的一個區域。用粒度來表示被鎖住文件的字節數目。對於記錄上鎖,粒度最大是整個文件。
文件上鎖:是記錄上鎖的一種特殊狀況,即記錄上鎖的粒度是整個文件的大小。
之因此有文件上鎖的概念是由於有些UNIX系統支持對整個文件上鎖,但沒有給文件內的字節範圍上鎖的能力。
關於記錄鎖的繼承和釋放有三條規則,以下:
(1)鎖與進程和文件兩方面有關,體如今:
對於第一個方面,能夠創建以下測試代碼:
//調用的函數,在文章末尾貼出 //process 1 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); cout<<"process 1 get write lock..."<<endl; sleep(10); cout<<"process 1 exit..."<<endl; return 0; } //process 2 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); cout<<"process 2 get write lock..."<<endl; unlock(fd); return 0; }
先啓動進程1,而後當即啓動進程2,執行結果以下:
process 1 get write lock... process 1 exit... process 2 get write lock...
對於第二個方面,能夠進行以下測試:
//調用的函數,在文章末尾貼出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); if (fork() == 0) { int fd_1 = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); readw_lock(fd_1); cout<<"child get read lock..."<<endl; sleep(3); close(fd_1); cout<<"close the file descriptor..."<<endl; pause(); } sleep(1); writew_lock(fd); cout<<"parent get write lock..."<<endl; unlock(fd); return 0; }
程序的執行結果以下:
child get read lock... close the file descriptor... parent get write lock...
可見,當關閉文件描述符時,與該文件描述符有關的鎖都被釋放,一樣經過dup拷貝獲得的文件描述符也會致使這種狀況;
(2)由fork產生的子進程不繼承父進程所設置的鎖。即對於父進程創建的鎖而言,子進程被視爲另外一個進程。記錄鎖自己就是用來同步不一樣進程對同一文件區進行操做,若是子進程繼承了父進程的鎖,那麼父子進程就能夠同時對同一文件區進行操做,這有違記錄鎖的規則,因此存在這麼一條規則。
下面是測試代碼(上面已經用過該代碼進行測試):
//調用的函數,在文章末尾貼出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); if (fork() == 0) { cout<<lock_test(fd, F_WRLCK, SEEK_SET, 0, 0)<<endl; cout<<lock_test(fd, F_RDLCK, SEEK_SET, 0, 0)<<endl; exit(0); } sleep(3); unlock(fd); return 0; }
咱們知道在前面已經說過,同一個進程能夠重複對同一個文件區間加鎖,後加的鎖將覆蓋前面加的鎖。那麼再假設若是子進程繼承了父進程的鎖,那麼子進程能夠對該鎖進行覆蓋,那麼在子進程內對該鎖是否能得到權限的測試應該是能夠,但測試結果爲:
24791 24791
代表已經進程24791已經佔用該鎖,因此假設不成立,子進程不會繼承父進程的鎖;
(3)執行exec後,新程序能夠繼承原執行程序的鎖。可是,若是一個文件描述符設置了close-on-exec標誌,在執行exec時,會關閉該文件描述符,因此對應的鎖也就被釋放了,也就無所謂繼承了。
在讀寫鎖中,我曾經測試過Linux 2.6.18中提供的讀寫鎖函數是優先考慮等待讀模式佔用鎖的線程,這種實現的一個很大缺陷就是出現寫入線程餓死的狀況。 那麼在記錄鎖中是什麼樣的規則呢,須要說明的是這在POSIX標準中是沒有說明的,要看具體實現。
具體進行如下2個方面測試:
測試1:父進程得到對文件的讀鎖,而後子進程1請求加寫鎖,隨即進入睡眠,而後子進程2請求讀鎖,看進程2是否可以得到讀鎖。
//調用的函數,在文章末尾貼出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); readw_lock(fd); //child 1 if (fork() == 0) { cout<<"child 1 try to get write lock..."<<endl; writew_lock(fd); cout<<"child 1 get write lock..."<<endl; unlock(fd); cout<<"child 1 release write lock..."<<endl; exit(0); } //child 2 if (fork() == 0) { sleep(3); cout<<"child 2 try to get read lock..."<<endl; readw_lock(fd); cout<<"child 2 get read lock..."<<endl; unlock(fd); cout<<"child 2 release read lock..."<<endl; exit(0); } sleep(10); unlock(fd); return 0; }
在Linux 2.6.18下執行結果以下:
child 1 try to get write lock... child 2 try to get read lock... child 2 get read lock... child 2 release read lock... child 1 get write lock... child 1 release write lock...
可知在有寫入進程等待的狀況下,對於讀出進程的請求,系統會一直給予的。那麼這也就可能致使寫入進程餓死的局面。
測試2:父進程得到寫入鎖,而後子進程1和子進程2分別請求得到寫入鎖和讀寫鎖,看二者的響應順序;
//調用的函數,在文章末尾貼出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); //child 1 if (fork() == 0) { sleep(3); cout<<"child 1 try to get write lock..."<<endl; writew_lock(fd); cout<<"child 1 get write lock..."<<endl; unlock(fd); cout<<"child 1 release write lock..."<<endl; exit(0); } //child 2 if (fork() == 0) { cout<<"child 2 try to get read lock..."<<endl; readw_lock(fd); cout<<"child 2 get read lock..."<<endl; unlock(fd); cout<<"child 2 release read lock..."<<endl; exit(0); } sleep(10); unlock(fd); return 0; }
在Linux 2.6.18下執行結果:
child 2 try to get read lock... child 1 try to get write lock... child 2 get read lock... child 2 release read lock... child 1 get write lock... child 1 release write lock...
將上面代碼該成child2 sleep 3s,child1不sleep
//調用的函數,在文章末尾貼出 int main() { int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd); //child 1 if (fork() == 0) { cout<<"child 1 try to get write lock..."<<endl; writew_lock(fd); cout<<"child 1 get write lock..."<<endl; unlock(fd); cout<<"child 1 release write lock..."<<endl; exit(0); } //child 2 if (fork() == 0) { sleep(3); cout<<"child 2 try to get read lock..."<<endl; readw_lock(fd); cout<<"child 2 get read lock..."<<endl; unlock(fd); cout<<"child 2 release read lock..."<<endl; exit(0); } sleep(10); unlock(fd); return 0; }
在Linux 2.6.18下執行結果以下:
child 1 try to get write lock... child 2 try to get read lock... child 1 get write lock... child 1 release write lock... child 2 get read lock... child 2 release read lock...
由上可知在Linux 2.6.18下,等待的寫入鎖進程和讀出鎖進程的優先級由FIFO的請求順序進程響應。
void lock_init(flock *lock, short type, short whence, off_t start, off_t len) { if (lock == NULL) return; lock->l_type = type; lock->l_whence = whence; lock->l_start = start; lock->l_len = len; } int readw_lock(int fd) { if (fd < 0) { return -1; } struct flock lock; lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0) { return -1; } return 0; } int writew_lock(int fd) { if (fd < 0) { return -1; } struct flock lock; lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0) { return -1; } return 0; } int unlock(int fd) { if (fd < 0) { return -1; } struct flock lock; lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0) { return -1; } return 0; } pid_t lock_test(int fd, short type, short whence, off_t start, off_t len) { flock lock; lock_init(&lock, type, whence, start, len); if (fcntl(fd, F_GETLK, &lock) == -1) { return -1; } if(lock.l_type == F_UNLCK) return 0; return lock.l_pid; }
Jun 28, 2013 PM16:06 @lab 困呀。。。