3.17.2017

檔案鎖機制

這一次,自己想要記錄下來的學習心得,是Linux系統下的檔案鎖的機制。什麼是檔案鎖呢?

我們先假設有一個檔案叫Resource.TXT,它會被兩個程序 (Process 0, Process 1)去存取。可是,如果當這兩個行程,同時要寫資料到Resource.TXT這個檔案,那麼一定會有問題,會發生Race Condition (競走現象)。為了要解決這樣的問題,我們可以先用一個很笨的方法:

1. 當Process 0要去存取Resource.TXT時,先建立一個檔案鎖 (Lock File)
2. 而Process 1要去存取Resource.TXT時,先去判斷檔案鎖是否存在,如果存在,就表示有其它的行程正在存取當中,所以程序先行等待;
3. 當Process 0存取結束時,就把檔案鎖給刪除;
4. 而Process 1再去確認檔案鎖是否存在,若不存在,表示沒有行程在存取,換Process 1自行存取。

很簡單吧!其實,這種避免許多行程去存取某一個資源的競走現象,在作業系統的觀念中還蠻重要的;在更深入的Linux Kernel Space也會用到。

所以,基於以上4點,用幾行簡單的程式碼,具體實踐。
#include "proc.h"

#define SECONDS   30

int main(void)
{
    FILE *fp = (FILE *)NULL;
    char *pStr0 = (char *)NULL;

    creat(LOCK_FILE, S_IROTH);
    fp = fopen(RESOURCE_FILE, "r");
    if (fp == (FILE *)NULL) {
        fprintf(stderr, "Open the Resource File: %s ERROR!! \n", RESOURCE_FILE);
        exit(EXIT_FAILURE);
    }

    pStr0 = (char *)calloc(sizeof(char), 16);
    fread((void *)pStr0, sizeof(char), 16, fp);
    fprintf(stdout, "pStr0: %s", pStr0);
    fprintf(stdout, "Waiting for %d seconds.... \n", SECONDS);
    sleep(SECONDS);
    fprintf(stdout, "Done!! \n");
    free((void *)pStr0);
    fclose(fp);
    remove(LOCK_FILE);

    return 0;
}
首先,我們在Process 0程式一開始執行時,就先用creat()函式來建立一個檔案鎖 (/var/tmp/Resource.lck),

接著,我們開始用fopen(), fread(), fprintf()等函式來讀取Resource.TXT檔案,並且將之顯示出來;

再來,為了讓Process 1有時間去存取和驗證,用sleep()函式讓程序等待30秒之後再結束,

最後,在程序結束前,要記得把檔案鎖給刪除。

再看Process 1的程式碼:
#include "proc.h"

#define SECONDS   3

int main(void)
{
    struct stat stTemp;
    int fd = 1, nRet = 1;
    FILE *fp = (FILE *)NULL;
    char *pStr1 = (char *)NULL;

    bzero((void *)&stTemp, sizeof(struct stat));

    do {
        fd = open(LOCK_FILE, O_RDONLY);
        nRet = fstat(fd, &stTemp);
        fprintf(stdout, "Warning: The file was LOCKed!! Please wait.... \n");
        sleep(SECONDS);
    } while (nRet == 0);
 close(fd);

    pStr1 = (char *)malloc(sizeof(char) * 16);
    fp = fopen(RESOURCE_FILE, "r");
    if (fp == (FILE *)NULL){
        fprintf(stderr, "Open the Resource File: %s ERROR!! \n", RESOURCE_FILE);
        exit(EXIT_FAILURE);
    }

    pStr1 = (char *)malloc(sizeof(char) * 16);
    fread((void *)pStr1, sizeof(char), 16, fp);
    fprintf(stdout, "pStr1: %s", pStr1);
    fclose(fp);
    free((void *)pStr1);

    return 0;
}
程序一開始就先判斷檔案鎖是否存在,這裡判斷的方法是,用低階I/O的open()函式開啟檔案之後,用fstat()函式去判斷檔案鎖是否存在;

因為fstat()函式執行成功會回傳0,所以在while-loop當中,fstat()一直回傳0就表示檔案鎖存在,我們就顯示一小段警告訊息,並且用sleep()函式等待3秒鐘,直到檔案鎖被Process 0給移除之後,才會跳出迴圈。

跳出回圈之後,就可以讀取Resource.TXT了,把內容讀出來。

再把其它的相關檔案程式碼補齊:
proc.h:
#ifndef __PROC_H__
#define __PROC_H__

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>

#define RESOURCE_FILE    "./Resource.TXT"
#define LOCK_FILE        "/var/tmp/Resource.lck"

#endif
Makefile:
GCC := gcc
CFLAGS := -o
OBJS := proc0.o proc1.o
EXEC := proc0.out proc1.out
RM := rm -rf

.PHONY: all
all: $(EXEC)

$(EXEC): $(OBJS)
    @-$(GCC) $(CFLAGS) proc0.out proc0.o
    @-$(GCC) $(CFLAGS) proc1.out proc1.o

$(OBJS): %.o: %.c
    @-$(GCC) -c $< $(CFLAGS) $@

.PHONY: clean cleanall install print tar dist TAGS check test
clean:
    @-$(RM) *.o

cleanall: clean
    @-$(RM) $(EXEC)

install:

print:

tar:

dist:

TAGS:

check: 

test:

Resource.TXT:
Hello!! World!!

執行結果如下圖所示:


這樣的作法,相當的笨拙,且不夠聰明。好在Linux的User Space下還提供了另外一種更好的方式─fcntl()函式 (File Control);這一篇文章只不過是個引言,接下就會記錄如何使用fcntl()函式來避免競走現象。

沒有留言:

張貼留言