(六):文件鎖

當一個系統中存在多個進程同時操作同一個文件時,為了保證數據的正確, 一般會將文件上鎖來避免共享文件產生的競爭狀態。在linux系統下文件上鎖可以使用fcntl函數來實現。 函數fcntl原型如下:

#include <unistd.h>  
#include <fcntl.h>  
int fcntl(int fd, int cmd, ... /* arg */ ); 

函數對所打開的文件描述符fd,根據不同的cmd命令執行不同的操作,針對文件鎖的命令有如下: F_GTELK, F_SETLK, F_SETLKW三種,分別為獲取鎖,設置鎖,同步獲取鎖。該函數在使用文件鎖時第三個參數為一個指向結構體struct flock的指針,該結構體定義大致如下:

struct flock {  
    ...  
    short l_type;    /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */   
    short l_whence;  /* How to interpret l_start:  SEEK_SET, SEEK_CUR, SEEK_END */   
    off_t l_start;   /* Starting offset for lock */  
    off_t l_len;     /* Number of bytes to lock */  
    pid_t l_pid;     /* PID of process blocking our lock (F_GETLK only) */   
    ...  
};  

成員l_whence、l_start、l_len指定了我們期望加鎖的文件範圍;
l_whence可取值:SEEK_SET、SEEK_CUR、SEEK_END,一般設為SEEK_SET。
l_start為文件加鎖開始的偏移處,一般設置為0,為開始處。
l_len為指定加鎖的字節數,如果為0則表示從開始處一直到文件末尾。
l_pid為持有鎖的進程ID號。
l_type為要加鎖的類型,可以取值F_RDLCK(讀鎖)、F_WRLCK(寫鎖)、F_UNLCK (釋放鎖)。

任何數量的進程都可以對同一個文件區域持有一個讀鎖,但只能有一個進程對其持有寫鎖(排它鎖)。單個進程只能對同一文件區域持有一種鎖,如果新的鎖應用在同一片已經使用了所得文件區域,那麼之前存在的鎖被覆蓋。

F_STELK在l_type為F_RDLCK或F_WRLCK時請求一個鎖,在l_type為F_UNLCK是釋放一個鎖,如果此時有另外的進程已經提前和獲取到鎖時,該調用立即返回-1。

F_SETLKW和F_STELK類似,去別在於當之前存在鎖時,該函數調用將阻塞直到鎖釋放為止。如果阻塞期被中斷信號打斷將立即返回。

F_GTELK針對指向struct flock結構體類型鎖去試圖加鎖,如果該鎖可以加上(實際沒有做加鎖的操作),fcntl()函數將結構體中l_type置為F_UNLCK,其餘成員保持不變。如果存在不兼容的鎖阻止該種類型的鎖加鎖,fcntl將已經存在鎖的詳細信息寫入結構體中,其中l_pid為持有該鎖的進程ID。

如果想對文件區域加讀鎖,那麼文件描述應當是以可讀方式打開,類似地,如果將對文件夾寫鎖,文件應當是以可寫的方式打開。

文件鎖釋放有三種途徑,一種是顯示的使用F_UNLCK類型加鎖,第二種是進程終止,第三種是所有針對該文件打開的文件描述全部關閉。

文件鎖不會被通過fork()產生的子進程繼承。

下面是通過包裹fcntl函數實現的加鎖、同步加鎖、解鎖、測試讀寫鎖api:

/**
 * \brief     acquire, release or test for the existence of record locks
 * \details   if a conficling is held on the file, then return -1.
 *
 * \param     fd - file descriptor
 * \param     locktype - lock type: F_RDLCK, F_WRLCK, F_UNLCK.
 *
 * \return    0 is success, < 0 is failed.
 */
static int sln_filelock_set(int fd, short locktype)
{
    struct flock    lock;
    lock.l_type = locktype;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    lock.l_pid = getpid();

    if (fcntl(fd, F_SETLK, &lock) < 0) {
        fprintf(stderr, "fcntl: %s\n", strerror(errno));
        return -1;
    }

    return 0;
}
/**
 * \brief     acquire, release or test for the existence of record locks
 * \details   if a conficling is held on the file, then wait for that
 *            lock to be release
 *
 * \param     fd - file descriptor
 * \param     locktype - lock type: F_RDLCK, F_WRLCK, F_UNLCK.
 *
 * \return    0 is success, < 0 is failed.
 */
int sln_filelock_wait_set(int fd, short locktype)
{
    struct flock    lock;
    lock.l_type = locktype;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    lock.l_pid = getpid();

    if (fcntl(fd, F_SETLKW, &lock) < 0) {
        fprintf(stderr, "fcntl: %s\n", strerror(errno));
        return -1;
    }

    return 0;
}
int sln_file_wrlock(int fd)
{
    return sln_filelock_set(fd, F_WRLCK);
}
int sln_file_rdlock(int fd)
{
    return sln_filelock_set(fd, F_RDLCK);
}
int sln_file_wait_wrlock(int fd)
{
    return sln_filelock_wait_set(fd, F_WRLCK);
}
int sln_file_wait_rdlock(int fd)
{
    return sln_filelock_wait_set(fd, F_RDLCK);
}
int sln_file_unlock(int fd)
{
    return sln_filelock_set(fd, F_UNLCK);
}
/**
 * \brief     test for the existence of record locks on the file
 * \details   none
 *
 * \param     fd - file descriptor
 * \param     locktype - lock type: F_RDLCK, F_WRLCK.
 *
 * \return    0 is success, < 0 is failed.
 */
static int sln_filelock_test(int fd, short locktype)
{
    struct flock    lock;
    lock.l_type = locktype;
    lock.l_whence = 0;
    lock.l_start = 0;
    lock.l_len = 0;
    lock.l_pid = 0;

    if (fcntl(fd, F_GETLK, &lock) < 0) {
        fprintf(stderr, "fcntl: %s\n", strerror(errno));
        return -1;
    }

    if (lock.l_type != F_UNLCK) { //file is locked
        if (F_WRLCK == lock.l_type) {
            printf("write lock hold by process <%d>, lock_type: %d\n", lock.l_pid,
                   lock.l_type);
        } else if (F_RDLCK == lock.l_type) {
            printf("read lock hold by process <%d>, lock_type: %d\n", lock.l_pid,
                   lock.l_type);
        }

        return lock.l_pid; //return the pid of process holding that lock
    } else {
        printf("Unlock, lock type: %d\n", lock.l_type);
        return 0;
    }
}
int sln_file_wrlock_test(int fd)
{
    return sln_filelock_test(fd, F_WRLCK);
}
int sln_file_rdlock_test(int fd)
{
    return sln_filelock_test(fd, F_RDLCK);
}

下面來實現三個進程,三個進程針對同一個文件進行讀、寫、測試鎖操作: 寫進程:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "filelock.h"
int main(int argc, const char* argv[])
{
    int             fd;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <write_str>\n", argv[0]);
        return -1;
    }

    fd = open("filelock.txt", O_RDWR | O_CREAT, 0644);

    if (fd < 0) {
        fprintf(stderr, "open: %s\n", strerror(errno));
        return -1;
    }

    if (sln_file_wait_wrlock(fd) < 0) {
        printf("lock write failed!\n");
        close(fd);
        return -1;
    }

    printf("process <%d> holding write lock ok!\n", getpid());
    write(fd, argv[1], strlen(argv[1]));
    sleep(10);
    sln_file_unlock(fd);
    printf("release lock!\n");
    close(fd);
    return 0;
}

讀進程:

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

#include "filelock.h"

int main(int argc, const char* argv[])
{
    int             fd;
    char            buf[1024];

    fd = open("filelock.txt", O_RDONLY | O_CREAT, 0644);

    if (fd < 0) {
        fprintf(stderr, "open: %s\n", strerror(errno));
        return -1;
    }

    if (sln_file_wait_rdlock(fd) < 0) {
        printf("lock read failed!\n");
        close(fd);
        return -1;
    }

    printf("process <%d> holding read lock ok!\n", getpid());
    sleep(10);
    read(fd, buf, sizeof(buf));
    printf("read buf: %s\n", buf);
    sln_file_unlock(fd);
    printf("release lock!\n");

    close(fd);
    return 0;
}

測試讀寫鎖進程:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "filelock.h"

int main(int argc, const char* argv[])
{
    int             fd, pid;
    fd = open("filelock.txt", O_RDWR | O_CREAT, 0644);

    if (fd < 0) {
        fprintf(stderr, "open: %s\n", strerror(errno));
        return -1;
    }

    pid = sln_file_wrlock_test(fd);

    if (pid > 0) {
        printf("write locked!\n");
    } else {
        printf("write unlock!\n");
    }

    pid = sln_file_rdlock_test(fd);

    if (pid > 0) {
        printf("read locked!\n");
    } else {
        printf("read unlock!\n");
    }

    close(fd);
    return 0;
}

【CSDN 日報】| 2.17-3.17 上榜作者排行出爐 同步博客至 CSDN ,讓更多開發者看到你的文章 看微博技術大咖解析互聯網應用架構實戰 Linux互斥與同步應用(六):文件鎖 標籤: mutexfcntl 2015-01-06 23:43 952人閱讀 評論(0) 收藏 舉報 分類: linux同步與互斥應用系列(6)

版權聲明:本文為博主原創文章,未經博主允許不得轉載。如果您覺得文章對您有用,請點擊文章下面“頂”。 【版權聲明:尊重原創,轉載請保留出處:blog.csdn.net/shallnet 或 .../gentleliu,文章僅供學習交流,請勿用於商業用途】 當一個系統中存在多個進程同時操作同一個文件時,為了保證數據的正確, 一般會將文件上鎖來避免共享文件產生的競爭狀態。在linux系統下文件上鎖可以使用fcntl函數來實現。 函數fcntl原型如下: [cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片

#include <unistd.h>  
#include <fcntl.h>  
int fcntl(int fd, int cmd, ... /* arg */ );  

函數對所打開的文件描述符fd,根據不同的cmd命令執行不同的操作,針對文件鎖的命令有如下: F_GTELK, F_SETLK, F_SETLKW三種,分別為獲取鎖,設置鎖,同步獲取鎖。該函數在使用文件鎖時第三個參數為一個指向結構體struct flock的指針,該結構體定義大致如下: [cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片

struct flock {  
    ...  
    short l_type;    /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */   
    short l_whence;  /* How to interpret l_start:  SEEK_SET, SEEK_CUR, SEEK_END */   
    off_t l_start;   /* Starting offset for lock */  
    off_t l_len;     /* Number of bytes to lock */  
    pid_t l_pid;     /* PID of process blocking our lock (F_GETLK only) */   
    ...  
};  

成員l_whence、l_start、l_len指定了我們期望加鎖的文件範圍; l_whence可取值:SEEK_SET、SEEK_CUR、SEEK_END,一般設為SEEK_SET。 l_start為文件加鎖開始的偏移處,一般設置為0,為開始處。 l_len為指定加鎖的字節數,如果為0則表示從開始處一直到文件末尾。 l_pid為持有鎖的進程ID號。 l_type為要加鎖的類型,可以取值F_RDLCK(讀鎖)、F_WRLCK(寫鎖)、F_UNLCK (釋放鎖)。

任何數量的進程都可以對同一個文件區域持有一個讀鎖,但只能有一個進程對其持有寫鎖(排它鎖)。單個進程只能對同一文件區域持有一種鎖,如果新的鎖應用在同一片已經使用了所得文件區域,那麼之前存在的鎖被覆蓋。

F_STELK在l_type為F_RDLCK或F_WRLCK時請求一個鎖,在l_type為F_UNLCK是釋放一個鎖,如果此時有另外的進程已經提前和獲取到鎖時,該調用立即返回-1。 F_SETLKW和F_STELK類似,去別在於當之前存在鎖時,該函數調用將阻塞直到鎖釋放為止。如果阻塞期被中斷信號打斷將立即返回。 F_GTELK針對指向struct flock結構體類型鎖去試圖加鎖,如果該鎖可以加上(實際沒有做加鎖的操作),fcntl()函數將結構體中l_type置為F_UNLCK,其餘成員保持不變。如果存在不兼容的鎖阻止該種類型的鎖加鎖,fcntl將已經存在鎖的詳細信息寫入結構體中,其中l_pid為持有該鎖的進程ID。

如果想對文件區域加讀鎖,那麼文件描述應當是以可讀方式打開,類似地,如果將對文件夾寫鎖,文件應當是以可寫的方式打開。

文件鎖釋放有三種途徑,一種是顯示的使用F_UNLCK類型加鎖,第二種是進程終止,第三種是所有針對該文件打開的文件描述全部關閉。

文件鎖不會被通過fork()產生的子進程繼承。

下面是通過包裹fcntl函數實現的加鎖、同步加鎖、解鎖、測試讀寫鎖api: [cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片

/** 
 * \brief     acquire, release or test for the existence of record locks 
 * \details   if a conficling is held on the file, then return -1. 
 * 
 * \param     fd - file descriptor 
 * \param     locktype - lock type: F_RDLCK, F_WRLCK, F_UNLCK. 
 * 
 * \return    0 is success, < 0 is failed. 
 */  
static int sln_filelock_set(int fd, short locktype)  
{  
    struct flock    lock;  
    lock.l_type = locktype;  
    lock.l_whence = SEEK_SET;  
    lock.l_start = 0;  
    lock.l_len = 0;  
    lock.l_pid = getpid();  
    if (fcntl(fd, F_SETLK, &lock) < 0) {  
        fprintf(stderr, "fcntl: %s\n", strerror(errno));  
        return -1;  
    }  
    return 0;  
}  
/** 
 * \brief     acquire, release or test for the existence of record locks 
 * \details   if a conficling is held on the file, then wait for that 
 *            lock to be release 
 * 
 * \param     fd - file descriptor 
 * \param     locktype - lock type: F_RDLCK, F_WRLCK, F_UNLCK. 
 * 
 * \return    0 is success, < 0 is failed. 
 */  
int sln_filelock_wait_set(int fd, short locktype)  
{  
    struct flock    lock;  
    lock.l_type = locktype;  
    lock.l_whence = SEEK_SET;  
    lock.l_start = 0;  
    lock.l_len = 0;  
    lock.l_pid = getpid();  
    if (fcntl(fd, F_SETLKW, &lock) < 0) {  
        fprintf(stderr, "fcntl: %s\n", strerror(errno));  
        return -1;  
    }  
    return 0;  
}  
int sln_file_wrlock(int fd)  
{  
    return sln_filelock_set(fd, F_WRLCK);  
}  
int sln_file_rdlock(int fd)  
{  
    return sln_filelock_set(fd, F_RDLCK);  
}  
int sln_file_wait_wrlock(int fd)  
{  
    return sln_filelock_wait_set(fd, F_WRLCK);  
}  
int sln_file_wait_rdlock(int fd)  
{  
    return sln_filelock_wait_set(fd, F_RDLCK);  
}  
int sln_file_unlock(int fd)  
{  
    return sln_filelock_set(fd, F_UNLCK);  
}  
/** 
 * \brief     test for the existence of record locks on the file 
 * \details   none 
 * 
 * \param     fd - file descriptor 
 * \param     locktype - lock type: F_RDLCK, F_WRLCK. 
 * 
 * \return    0 is success, < 0 is failed. 
 */  
static int sln_filelock_test(int fd, short locktype)  
{  
    struct flock    lock;  
    lock.l_type = locktype;  
    lock.l_whence = 0;  
    lock.l_start = 0;  
    lock.l_len = 0;  
    lock.l_pid = 0;  
    if (fcntl(fd, F_GETLK, &lock) < 0) {  
        fprintf(stderr, "fcntl: %s\n", strerror(errno));  
        return -1;  
    }  
    if (lock.l_type != F_UNLCK) { //file is locked  
        if (F_WRLCK == lock.l_type) {  
            printf("write lock hold by process <%d>, lock_type: %d\n", lock.l_pid, lock.l_type);  
        } else if (F_RDLCK == lock.l_type) {  
            printf("read lock hold by process <%d>, lock_type: %d\n", lock.l_pid, lock.l_type);  
        }  
        return lock.l_pid; //return the pid of process holding that lock  
    } else {  
        printf("Unlock, lock type: %d\n", lock.l_type);  
        return 0;  
    }  
}  
int sln_file_wrlock_test(int fd)  
{  
    return sln_filelock_test(fd, F_WRLCK);  
}  
int sln_file_rdlock_test(int fd)  
{  
    return sln_filelock_test(fd, F_RDLCK);  
}  

下面來實現三個進程,三個進程針對同一個文件進行讀、寫、測試鎖操作: 寫進程: [cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片

#include <stdio.h>  
#include <sys/types.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <string.h>  
#include <errno.h>  
#include "filelock.h"  
int main(int argc, const char *argv[])  
{  
    int             fd;  
    if (argc != 2) {  
        fprintf(stderr, "Usage: %s <write_str>\n", argv[0]);  
        return -1;  
    }  
    fd = open("filelock.txt", O_RDWR | O_CREAT, 0644);  
    if (fd < 0) {  
        fprintf(stderr, "open: %s\n", strerror(errno));  
        return -1;  
    }  
    if (sln_file_wait_wrlock(fd) < 0) {  
        printf("lock write failed!\n");  
        close(fd);  
        return -1;  
    }  
    printf("process <%d> holding write lock ok!\n", getpid());  
    write(fd, argv[1], strlen(argv[1]));  
    sleep(10);  
    sln_file_unlock(fd);  
    printf("release lock!\n");  
    close(fd);  
    return 0;  
}  

讀進程: [cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片

#include <stdio.h>  
#include <sys/types.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <string.h>  
#include <errno.h>  
  
#include "filelock.h"  
  
int main(int argc, const char *argv[])  
{  
    int             fd;  
    char            buf[1024];  
  
    fd = open("filelock.txt", O_RDONLY | O_CREAT, 0644);  
    if (fd < 0) {  
        fprintf(stderr, "open: %s\n", strerror(errno));  
        return -1;  
    }  
  
    if (sln_file_wait_rdlock(fd) < 0) {  
        printf("lock read failed!\n");  
        close(fd);  
        return -1;  
    }  
    printf("process <%d> holding read lock ok!\n", getpid());  
    sleep(10);  
    read(fd, buf, sizeof(buf));  
    printf("read buf: %s\n", buf);  
    sln_file_unlock(fd);  
    printf("release lock!\n");  
  
    close(fd);  
    return 0;  
}  

測試讀寫鎖進程: [cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片

#include <stdio.h>  
#include <sys/types.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <string.h>  
#include <errno.h>  
#include "filelock.h"  
int main(int argc, const char *argv[])  
{  
    int             fd, pid;  
    fd = open("filelock.txt", O_RDWR | O_CREAT, 0644);  
    if (fd < 0) {  
        fprintf(stderr, "open: %s\n", strerror(errno));  
        return -1;  
    }  
    pid = sln_file_wrlock_test(fd);  
    if (pid > 0) {  
        printf("write locked!\n");  
    } else {  
        printf("write unlock!\n");  
    }  
    pid = sln_file_rdlock_test(fd);  
    if (pid > 0) {  
        printf("read locked!\n");  
    } else {  
        printf("read unlock!\n");  
    }  
    close(fd);  
    return 0;  
}  

編譯運行三個程序。 為了測試效果,在進程讀和寫sleep 10秒鐘。

1. 在寫鎖存在是,讀鎖是無法持有的。測試:先運行寫進程,在運行讀進程,讀進程將在寫進程釋放鎖時獲得鎖。
2. 在讀鎖存在時,寫鎖是無法持有的。測試:先運行讀進程,再運行寫進程,寫進程將在讀進程釋放鎖時獲得鎖。
3. 在讀鎖存在時,另外的讀鎖可以繼續持有。測試:先後運行兩個讀進程,後運行的讀進程可以立即獲得讀鎖。
4.上面的測試也可可以通過測試讀寫鎖進程得到結果。

http://download.csdn.net/detail/gentleliu/8332973


书籍推荐