1. 程式人生 > >linux檔案鎖筆記

linux檔案鎖筆記

一、概述

應用程式的一個常見的需求是從一個檔案中讀取資料,修改這些資料,然後將這些資料寫回檔案,當同一時刻只有一個程序使用這個檔案,這麼做不會出現問題,但是當多個程序同時更新一個檔案時,就會出現資源競爭的問題。

檔案鎖是一種檔案讀寫機制,在任何特定的時間只允許一個程序訪問一個檔案。利用這種機制能夠使讀寫單個檔案的過程變得更安全。

主要內容

  1. 檔案鎖的分類與原理
  2. 檔案鎖的使用
  3. 結束語

二、檔案鎖的介紹

檔案鎖主要分為兩種:一種是勸告式的,一種是強制式的鎖;但是在預設的情況下,檔案鎖是勸告式的。

勸告式檔案鎖

這種鎖不會介入write和read的操作,是一種協議式的鎖,在對一個檔案進行讀寫的時候,採用如下的過程

graph LR
加鎖-->讀寫操作
讀寫操作-->解鎖

但是勸告式檔案鎖不會從系統層上強制應用程式遵守這個協議,其他的程式可以不檢測檔案的鎖的狀態而直接操作檔案.

強制式檔案鎖

在程式中可以使用chmod()和fchmod()函式對檔案強制加鎖,強制式的鎖會使read/write函式阻塞或失敗.

三、檔案鎖的使用

檔案鎖有多種實現,這裡將的均是勸告鎖,強制鎖在應用中的價值不高,並且存在很大的弊端(容易鎖死).
在使用檔案鎖時,讀寫檔案的介面必須使用read和write,不能使用庫函式的IO操作函式,因為IO操作函式會帶有緩衝,影響加鎖的結果.
(好像標準庫函式的IO操作函式可以選擇沒有緩衝,但是依舊慎重)

flock

  • 功能:為一個已經開啟的檔案 新增或者移除一把勸告鎖(advisory lock)
  • 原型:int flock(int fd, int operation);
  • 引數:

    • fd 檔案id
    • operation 運算元

    operation可以使下列的三個值

  • LOCK_SH 放置一個共享鎖
  • LOCK_EX 放置一個排他鎖
  • LOCK_UN 解鎖

    此外,LOCK_NB可以與前兩個共用,通過’|’,意思是非阻塞的鎖.當使用flock(fd,LOCK_SH|LOCK_NB)時,如果其他程序持有鎖,則不會阻塞,而是返回錯誤,並記錄一個錯誤碼到error.

程序A 程序B LOCK_SH 程序B LOCK_EX
LOCK_SH
LOCK_EX
一個程序在一個檔案上只能擁有一種型別的鎖,每次呼叫flock會改變鎖的模式.鎖會在檔案描述符關閉的時候自動釋放.
  • 不足 :
  • 粒度大,只能鎖整個檔案
  • 勸告鎖,無法真正的鎖定檔案
  • 有些NFS實現不支援flock

fcntl

這種鎖通常稱為”記錄加鎖”,fcntl可以對檔案的任意部分加鎖,而不影響其他部分的使用,記錄加鎖是針對應用程式說的,記錄邊界是應用程式定義的一個位元組範圍

image

  • 原型:int fcntl(int fd, int cmd, … /* arg */ );
  • 引數:
    • fd 檔案描述符
    • cmd :
      • F_SET_LK 設定鎖狀態,失敗返回錯誤
      • F_SET_LKW 設定鎖狀態,失敗阻塞
      • F_GET_LK 獲取鎖狀態
    • arg :struct flock 結構指標

fcntl的鎖操作,通過下面這個結構體進行設定

struct flock
  {
    short int l_type;   /* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK.  */
    short int l_whence; /* Where `l_start' is relative to (like `lseek').  */
#ifndef __USE_FILE_OFFSET64
    __off_t l_start;    /* Offset where the lock begins.  */
    __off_t l_len;  /* Size of the locked area; zero means until EOF.  */
#else
    __off64_t l_start;  /* Offset where the lock begins.  */
    __off64_t l_len;    /* Size of the locked area; zero means until EOF.  */
#endif
    __pid_t l_pid;  /* Process holding the lock.  */
  };

鎖有三種狀態,分別是讀鎖/寫鎖和無鎖.

一個用於設定鎖狀態的函式

int pidf_set_lock(int fd,int lock_type)
{
        struct flock flockstr;

        //default
        if(lock_type!=F_RDLCK&&lock_type!=F_WRLCK&&lock_type!=F_UNLCK)
                return -1;   

        flockstr.l_type         =lock_type;
        flockstr.l_whence        =SEEK_SET;
        flockstr.l_start        =0;
        flockstr.l_len                =0;

        if(fcntl(fd,F_SETLK,&flockstr)==0)
        {
                if(flockstr.l_type==F_RDLCK)
                        pr_pidf_debug("set a F_RDLCK,thd pid = %d; \n",getpid());
                if(flockstr.l_type==F_WRLCK)
                        pr_pidf_debug("set a F_WRLCK,thd pid = %d; \n",getpid());
                if(flockstr.l_type==F_UNLCK)
                        pr_pidf_debug("set a F_UNLCK,thd pid = %d; \n",getpid());
                return flockstr.l_type;
        }        
        return -1;        
}

獲取鎖狀態的函式

int pidf_get_lock(int fd,pid_t *pid)
{
        struct flock flockstr;        
        flockstr.l_type             =F_WRLCK;
        flockstr.l_whence           =SEEK_SET;
        flockstr.l_start            =0;
        flockstr.l_len              =0;
        if(fcntl(fd,F_GETLK,&flockstr)==0)
        {
                if(pid!=NULL)
                     *pid =flockstr.l_pid;
                return flockstr.l_type; 
        }
        perror("fcntl F_GETLK");
        return -1;
}

繼承和釋放

  • fork產生的子程序不會繼承記錄鎖(fcntl),但是會繼承flock
  • 記錄鎖在exec()中會得到保留
  • 記錄鎖同時和一個程序,一個inode關聯,所以程序結束或者程序對檔案的連結關閉,都會導致其所持有的記錄鎖被釋放

四、結束語

參考

《Linux/UNIX系統程式設計手冊(下)》