如果同時執行 4 個使用 flock() 對同一個檔案上鎖並讀寫,假如程式碼內迴圈寫的順序是:const size_t sleepTime = 100000;for(size_t I=0;i<1000;i++){flock(fd, LOCK_EX);//…flock(fd, LOCK_UN);usleep(sleepTime);}或是const size_t sleepTime = 100000;for(size_t I=0;i<1000;i++){usleep(sleepTime);flock(fd, LOCK_EX);//…flock(fd, LOCK_UN);}只要等待是在上鎖前或是開鎖後,4 個程式一起執行的時間就會非常接近只有 1 個程式自己執行的時間。因為在等待的時候所是打開的狀態,其他程式就能趁這個空檔去讀寫檔案。但是如果把等待放在上鎖期間,那執行時間就會接近於 4 倍的「 1 個程式自己執行的時間」。因為其他程式要先等待成功上鎖的程式把鎖解開後才能去讀寫。const size_t sleepTime = 100000;for(size_t I=0;i<1000;i++){flock(fd, LOCK_EX);//…usleep(sleepTime);flock(fd, LOCK_UN);}
3. lockf.c 的執行狀況
而如果同時執行 4 個使用 lockf 對同一個檔案上鎖並讀寫,假如程式碼內迴圈寫的順序是:const size_t sleepTime = 100000;for(size_t I=0;i<1000;i++){lockf(fd, F_LOCK, 0); // len=0 means that the section extends from the current file position to infinity, encompassing the present and future end-of-file positions.//…lockf(fd, F_ULOCK, 0);usleep(sleepTime);}或是const size_t sleepTime = 100000;for(size_t I=0;i<1000;i++){usleep(sleepTime);lockf(fd, F_LOCK, 0); // len=0 means that the section extends from the current file position to infinity, encompassing the present and future end-of-file positions.//…lockf(fd, F_ULOCK, 0);}那想像中其他程式應該有辦法在等待的時候趁這個空檔去讀寫檔案,也就是跟 flock 一樣。但是實際跑出來的結果卻是一個程式花 1 倍的時間,另一個程式花 2 倍的時間,另一個程式花 3 倍的時間,另一個程式花 4 倍的時間。看似他們好像排隊做完。在往年的作業中,大部分的解釋為:因為 lockf 的關係,lockf.db會被鎖起來,不能同時對 lockf.db 寫入資料,而第二次 lockf 要先等第一次 lockf 執行完後才能被執行。但是在程式碼中,卻是把等待放在開鎖後,這個時候檔案應該是處於未上鎖的狀態,那怎麼會說「要等前一個解鎖」呢?
我認為 lockf 會有這樣的結果並不如往年作業裡所說的,因為flock跟lockf差異所造成的。純粹是因為程式寫錯了!以去年 408530036 潘巧書的作業為例:(圖片為他的程式碼)在他的 lockf.c 中,分別在 line 24 跟 line 36 上鎖和解鎖,雖然都是上鎖、解鎖 lock_size 個 bytes。但是 file position 在這兩個時間並不一樣,所以上鎖跟解鎖的檔案區域並不一樣。也就是說,會有一段是沒有被解鎖到,就永遠在那邊。而那些沒被解鎖的區段會在什麼時候被解鎖呢?是在檔案被關閉的時候,也就是一個程式跑完後。這有就是為什麼同時執行 4 個程式,會看起來像是「等待一個做完,領一個程式才能開始做」。所以整個過程大概是:4 個程式同時開始執行,但一定還是有一點點時間差,所以只有一個程式第一個成功用 lockf 把檔案鎖起來。然後讀檔 -> 寫檔 -> 解鎖 -> 睡覺(但是解鎖又沒有解乾淨,有一小段還是上著鎖的,就開始睡覺)。在睡覺的這段期間,其他程式嘗試去讀檔案,結果因為那一小段沒解乾淨的區段,因此檔案還是無法讀寫。直到那個程式整個執行完後,那些沒解乾淨的區段才被解鎖。這時候另外三支程式就會有其中一個成功用 lockf 把檔案鎖起來。接著其他兩支程式只能在等他全部做完後才會再去搶第三個…...綜上所述,會有「用 lockf 程式要排隊執行」的錯覺,只是程式寫錯了,沒有解鎖解完全。
比如說,下面這段程式碼(我寫的)。在 lne 40 上鎖前先在 line 39 將 file position 回到檔案起始位置;在 line 50 開鎖前先在 line 49 將 file position 回到檔案起始位置。01: #include <stdio.h>02: #include <stdlib.h>03: #include <string.h>04: #include <sys/file.h>05: #include <unistd.h>06: #define BUFFER_SIZE 409607: #define MAX_COMMAND_LENGTH 15008: #define DEBUG_MODE 009:10: int main() {11: const size_t sleepTime = 100000; // in microsecondj(10^-6 s)12: const size_t iterativeTime = 1000;13: const size_t readDigits = 4;14: // const size_t systemCommandMaxLength = 150;15: const char *fileDir = "lockf.db";16: char systemCommand[3][MAX_COMMAND_LENGTH] = {"sudo mount -oremount,mand /", "chmod g+s ", "chmod g-x "};17: int fileDescriptor = 0;18: off_t offset = 0;19: char buffer[BUFFER_SIZE];20:21: fileDescriptor = open(fileDir, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);22: if (fileDescriptor == -1) {23: perror("Error: Can't not open the file");24: exit(EXIT_FAILURE);25: }26: if (strlen(systemCommand[1]) + strlen(fileDir) + 1 > MAX_COMMAND_LENGTH) { // concat the base command with file directory27: perror("Error: file director too long");28: exit(EXIT_FAILURE);29: } else {30: strcat(systemCommand[1], fileDir);31: strcat(systemCommand[2], fileDir);32: }33: system(systemCommand[0]);34: system(systemCommand[1]);35: system(systemCommand[2]);36:37: for (size_t i = 0; i < iterativeTime; i++) {38: usleep(sleepTime);39: lseek(fileDescriptor, 0, SEEK_SET);40: lockf(fileDescriptor, F_LOCK, 0); // lock the file41: lseek(fileDescriptor, (off_t)-readDigits, SEEK_END); // go back -digitsNum bytes42: read(fileDescriptor, buffer, readDigits); // read the number in the end of the file into the buffer43: sscanf(buffer, "%ld", &offset); // sscanf the data in buffer into offset44: lseek(fileDescriptor, 0, SEEK_END);45: lseek(fileDescriptor, offset, SEEK_CUR);46: offset += 1;47: sprintf(buffer, "%ld", offset);48: write(fileDescriptor, buffer, readDigits);49: lseek(fileDescriptor, 0, SEEK_SET);50: lockf(fileDescriptor, F_ULOCK, 0);51: }52:53: close(fileDescriptor);54: return (EXIT_SUCCESS);55: }56:這樣跑出來的時間就也就會跟 flock 跑出來的結果相似,4 程式執行完成的時間差不多。
這個作業的目的是什麼?期待學生做出怎麼樣的結果?如果是希望展示 flock 和 lockf 的「鎖定效果」,那想像中只要正確解鎖,那同時執行多個相同的程式,結果應該是幾乎一樣的;如果是要體現兩者個強度差異,那像中不應該有「解鎖」這一步,而是在維持鎖定的情況下用不同的方法嘗試去讀寫他,看看哪個方法可以、哪個不行讀寫。
我實在很疑惑,還請老師您解答,謝謝!