1. 文件記錄鎖介紹
文件鎖鎖定的是整個文件,而記錄鎖定還可以鎖定文件的某一特定部分,即從文件的某一相對位置開始的一段連續的字節流。
當一個進程正在讀取或者修改文件的某個部分時,使用文件記錄鎖可以阻止其他進程修改同一文件的相同區域。它可以用來鎖定文件的某個區域或者整個文件,SylixOS 支持多種文件記錄鎖 API。
注:SylixOS 支持多種設備驅動模型,但是目前只有 NEW_1 型設備驅動支持文件記錄鎖功能,此類驅動文件節點類似於UNIX 系統的 vnode。
2. 文件記錄鎖設置
SylixOS可以通過fcntl 函數操作文件記錄鎖的功能。
2.1 fcntl原型
#include<fcntl.h> int fcntl (int iFd, int iCmd, ...) |
函數fcntl原型分析:
1. 此函數成功時根據參數iCmd的不同而返回不同的值,失敗返回-1並設置錯誤號;
2. 參數 iFd 是文件描述符;
3. 參數 iCmd 是命令;
4. 參數 ...是命令參數。
fcntl設置文件記錄鎖時iCmd對應3個命令:F_GETLK、F_SETLK 和 F_SETLKW。命令解釋分別是:F_GETLK表示獲取文件鎖;F_SETLK表示設置文件鎖(非阻塞);F_SETLKW表示設置文件鎖(阻塞)。第 3 個參數是一個 flock 結構體指針,結構體成員如程序清單 2-1所示。
程序清單 2-1 flock結構體成員
struct flock { short l_type; /* F_RDLCK, F_WRLCK, or F_UNLCK */ short l_whence; /* flag to choose starting */ /* offset */ off_t l_start; /* relative offset, in bytes */ off_t l_len; /* length, in bytes; 0 means */ /* lock to EOF */ pid_t l_pid; /* returned with F_GETLK */ long l_xxx[4]; /* reserved for future use */ }; |
l_type表示鎖的類型分別爲:F_RDLOCK(共享讀鎖)、F_WRLOCK(獨佔寫鎖)和F_UNLCK(解鎖);
l_whence表示文件記錄鎖的起始位置,其值如圖 2-1所示。
圖 2-1 l_Whence值相關
iWhence值 | oftOffset說明 |
SEEK_SET | 將文件的偏移量設置爲距文件開始處 oftOffset 個字節 |
SEEK_CUR | 將文件的偏移量設置爲當前值加oftOffset個字節,oftOffset可爲負 |
SEEK_END | 將文件的偏移量設置爲文件長度加oftOffset個字節,oftOffset可爲負 |
l_start是相對l_whence偏移開始位置(注意不可以從文件開始的之前部分鎖起);
l_len是鎖定區域長度,如果爲0則鎖定文件尾(EOF),如果向文件中追加數據也將被鎖;
l_pid是已佔用鎖的進程ID(由命令F_GETLK返回)。
2.2 文件記錄鎖使用規則
文件記錄鎖中的F_RDLOCK(共享讀鎖)和F_WRLOCK(獨佔寫鎖)的基本規則是:任意多個進程在一個給定字節上可以有一把共享的讀鎖,但是在一個給定字節上只能有一個進程有一把獨佔的寫鎖。進一步而言,如果在一個給定字節上已經有一把或多把讀鎖,則不能在該字節上再加寫鎖;如果在一個給定字節上有一把寫鎖,則不能再加任何鎖。基本規則如表 2-1所示。
表 2-1 記錄鎖規則
當前字節區鎖狀態 | 請求 | |
讀鎖 | 寫鎖 | |
無鎖 | 允許 | 允許 |
一個或多個讀鎖 | 允許 | 拒絕 |
一個寫鎖 | 拒絕 | 拒絕 |
上面的規則適用於不同進程提出的鎖請求,並不適用於單個進程提出的鎖請求。也就是說,如果一個進程對一個文件區間已經有了一把鎖,後來該進程又企圖在同一個區間再加一把鎖,那麼也是可以的,這個時候新鎖將替換已有鎖。因此,如果一個進程將某個文件加了一把寫鎖,然後又企圖給文件加一把讀鎖,那麼將會成功執行,原來的寫鎖會被替換爲讀鎖。
2.3 文件記錄鎖特點
記錄鎖採用(pid,start,end)三元組作爲鎖標識,一個文件可擁有多個記錄鎖,同一區域只允許有一個記錄鎖。
當進程終止(正常/不正常),該進程擁有的所有記錄鎖都將釋放。同一個進程中,指向同一文件(i-node)的fd都可以操作該文件上的記錄鎖:如釋放、修改等。顯式調用F_UNLCK和close(fd)都將釋放鎖,close將釋放整個文件中該進程擁有的所有記錄鎖。
記錄鎖不被spawn的子進程繼承(PID不同)。
記錄鎖的類型轉換、改變鎖範圍等操作均爲原子操作。
未設置FD_CLOEXEC時,記錄鎖將被exec後的進程繼承(PID相同)。
記錄鎖對文件打開mode有要求:加讀鎖要求文件句柄fd有讀權限,加寫鎖要求fd有寫權限。
3. 文件記錄鎖使用
比如進程A對文件“/apps/file”加上寫鎖(進程A先上鎖),當A進程用戶操作結束後會釋放鎖給進程B操作,進程A代碼如程序清單 3-1所示。
程序清單 3-1 進程A代碼
#include<stdio.h> #include<unistd.h> #include<fcntl.h>
#define FILE_PATH "/apps/file" intmain(intargc, char *argv[]) { int iFd = 0; struct flock flck; short sLockt = F_WRLCK;
iFd = open(FILE_PATH, O_RDWR); /*打開文件*/ if (iFd < 0) { fprintf(stderr, "open file failed.\n"); return -1; } /* * l_whence = SEEK_SET;l_start = 0;表示從文件開始起偏移量爲0開始上鎖 * l_len = 0;表示鎖定到文件尾 */ flck.l_type = sLockt; /* 文件記錄鎖類型設置爲獨寫鎖 */ flck.l_whence = SEEK_SET; flck.l_start = 0; flck.l_len = 0;
if (fcntl(iFd, F_SETLK, &flck) < 0) { /* fcntl設置文件記錄鎖 */ fprintf(stderr, "add write lock failed.\n"); close(iFd); return -1; } /* * 用戶對文件被鎖定區域操作 */ sLockt = F_UNLCK; /* 文件記錄鎖類型設置爲解鎖 */ flck.l_type = sLockt; flck.l_whence = SEEK_SET; flck.l_start = 0; flck.l_len = 0;
if (fcntl(iFd, F_SETLK, &flck) < 0) { fprintf(stderr, "unlock failed.\n"); close(iFd); return -1; } close(iFd); return 0; } |
進程B也對文件“/apps/file”操作,區別是進程B設置爲F_SETLKW(阻塞等待解鎖)。進程B阻塞等待進程A解鎖方可對文件進行操作,進程B代碼如程序清單 3-2所示。
程序清單 3-2 進程B代碼
#include<stdio.h> #include<unistd.h> #include<fcntl.h>
#define FILE_PATH "/apps/file" intmain(intargc, char *argv[]) { int iFd = 0; struct flock flck; short sLockt = F_WRLCK;
iFd = open(FILE_PATH, O_RDWR); /* 打開文件 */ if (iFd < 0) { fprintf(stderr, "open file failed.\n"); return -1; } /* * l_whence = SEEK_SET;l_start = 0;表示從文件開始起偏移量爲0開始上鎖 * l_len = 0;表示鎖定到文件尾 */ flck.l_type = sLockt; /* 文件記錄鎖類型設置爲獨寫鎖 */ flck.l_whence = SEEK_SET; flck.l_start = 0; flck.l_len = 0;
if (fcntl(iFd, F_SETLKW, &flck) < 0) { /* fcntl設置文件記錄鎖 */ fprintf(stderr, "add write lock failed.\n"); close(iFd); return -1; } /* * 用戶對文件被鎖定區域進行操作 */ close(iFd); return 0; } |