天天看點

檔案鎖的使用淺析

概述

在多數

unix

系統中,當多個程序/線程同時編輯一個檔案時,該檔案的最後狀态取決于最後一個寫該檔案的程序。

對于有些應用程式,如資料庫,各個程序需要保證它正在單獨地寫一個檔案。這時就要用到檔案鎖。

檔案鎖(也叫記錄鎖)的作用是,當一個程序讀寫檔案的某部分時,其他程序就無法修改同一檔案區域。

能夠實作檔案鎖的函數主要有2個:

flock

fcntl

早期的伯克利版本隻支援flock,該函數隻能對整個檔案加鎖,不能對檔案的一部分加鎖。

lockf

是在

fcntl

基礎上構造的函數,它提供了一個簡化的接口。它們允許對檔案中任意位元組區域加鎖,短至一個位元組,長至整個檔案。

fcntl函數

#include <fcntl.h>

int fcntl(int fd, int cmd, .../*struct flock *flockptr*/);
#傳回值:若成功,傳回值依賴于cmd,失敗傳回-1
           

cmd

F_GETLK, F_SETLK, F_SETLKW

中的一個。第三個參數是指向

flock

結構的指針,

flock

結構如下:

struct flock {
short l_type;/* one of F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence;/* SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start;/* offset in bytes, relative to l_whence */
off_t l_end;/* length, in bytes, 0 means lock to EOF */
off_t l_pid;/* returned with F_GETLK */
};
           

其中,

  • 鎖類型:共享讀鎖

    F_RDLCK

    ,獨占性寫鎖

    F_WRLCK

    ,解鎖

    F_UNLCK

  • 加鎖或解鎖區域的起始位元組偏移量(

    l_start, l_whence

  • 區域位元組長度(

    L_len

  • 程序的id持有的鎖能阻塞目前程序,僅由

    F_GETLK

    傳回
  • 鎖可以在檔案尾處開始或者越過尾端開始,但是不能在檔案起始位置之前開始
  • l_len=0

    , 表示鎖的範圍可以擴大到最大可能偏移量,這意味着不管向檔案中追加多少資料,它們都可以處于鎖的範圍内,而且起始位置可以任意
  • 設定

    l_start

    l_whence

    指向檔案的起始位置,并且指定

    l_len=0

    ,以實作對整個檔案加鎖(一般

    l_start=0, l_whence=SEEK_SET

鎖的使用

使用鎖的基本規則:

  • 任意多個程序在一個給定的位元組上可以有一把共享的讀鎖(

    F_RDLCK

    ),但是在一個給定的位元組上隻能有一個程序有一把獨占性寫鎖(

    F_WRLCK

  • 如果在一個給定位元組上已經有一把或多把讀鎖,則不能在該位元組上再加寫鎖,如果在一個位元組上已經有一把獨占性寫鎖,則不能再對它加任何讀鎖
  • 對于單個程序而言,如果程序對某個檔案區域已經有了一把鎖,然後又試圖在相同區域再加一把鎖,則新鎖會替換舊鎖
  • 加讀鎖時,該描述符必須是讀打開,加寫鎖時,該描述符必須是寫打開

fcntl三種cmd的使用:

  • F_GETLK

    :判斷由

    flockptr

    所描述的鎖是否會被另一把鎖所排斥(阻塞),如果存在一把鎖阻止建立由

    flockptr

    所描述的鎖,由該現有鎖的資訊将重寫

    flockptr

    指向的資訊。如果不存在這種情況,則除了将

    l_type

    設定為

    F_UNLCK

    之處,

    flockptr

    所指向結構中的其他資訊保持不變
  • F_SETLK

    :設定由

    flockptr

    所描述的鎖,如果程式試圖獲得一把鎖,而系統阻止程式獲得該鎖,則

    fcntl

    會立即傳回錯誤,

    errno

    設定為

    EACCES或EAGAIN

    。當

    l_type=F_UNLCK

    時,此指令用來清除指定的鎖
  • F_SETLKW:F_SETLK

    的阻塞版本(

    wait

    )。如果程式嘗試獲得的鎖無法被授予,調用程序會進入休眠直到程序獲得鎖或者信号中斷
注意:用F_GETLK 測試能否建立一把鎖,然後用F_SETLK嘗試建立鎖之間并非原子操作,也就是說兩次調用之間有可能另一程序插入并建立了相同的鎖。如果不希望在等待鎖變為可用時産生阻塞,就必須處理由F_SETLK傳回的可能出錯值

下面是測試一把鎖的例子:

#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>

pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
    struct flock lock;

    lock.l_type = type;
    lock.l_start = offset;
    lock.l_whence = whence;
    l   ock.l_len = len;

    if (fcntl(fd, F_GETLK, &lock) < ) {
        printf("fcntl error: %s\n", strerror(errno));
        return ;
    }

    if (lock.l_type == F_UNLCK) {
        return ;
    }

    return lock.l_pid;
}
           

鎖的繼承與釋放

鎖的繼承和釋放有以下三條原則:

  • 鎖與程序和檔案兩者相關聯。即當一個程序終止時,它所建立的所有鎖均釋放,對于描述符而言,無論它何時關閉,程序通過它引用的檔案上的任何一把鎖也都會釋放
  • fork

    産生的子程序不繼承父程序所設定的鎖
  • 執行

    exec

    後,新程式可以繼承原程式的鎖。注意,如果對一個檔案描述符設定了執行時關閉标志,那麼當作為exec的一部分關閉該檔案描述符時,将釋放相應檔案的所有鎖

避免死鎖

如果兩個程序互相等待對方持有并且不釋放的資源時,這兩個程序就會進入死鎖狀态。

如果一個程序已經控制了檔案中的一個加鎖區域,然後它又試圖對另一個程序控制的區域加鎖,那麼它就會進入睡眠,并有可能發生死鎖。

檢測到死鎖時,核心必須選擇一個程序接收錯誤傳回。

總結

在多程序或多線程環境中,當多個應用需要讀寫同一個檔案時,需要考慮對檔案加鎖,以保證對檔案修改的一緻性。

在使用檔案鎖時,應明确應用模式,防止死鎖。

更多關于檔案鎖的使用細節,請參考《UNIX環境進階程式設計》。

繼續閱讀