Linux系統編程:fcntl函數與文件鎖

一、fcntl函數

功能:操縱文件描述符,改變已打開的文件的屬性

int fcntl(int fd, int cmd, ... /* arg */ );


cmd的取值可以如下:

複製文件描述符
F_DUPFD (long)


設置/獲取文件描述符標誌
F_GETFD (void)
F_SETFD (long)


設置/獲取文件狀態標誌
F_GETFL (void)
F_SETFL (long)


獲取/設置文件鎖
F_GETLK
F_SETLK,F_SETLKW


F_SETFL:

On Linux  this  command can change only the O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK flags.

示例程序如下:

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

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void set_flag(int, int);
void clr_flag(int, int);

int main(int argc, char *argv[])
{
    char buf[1024] = {0};
    int ret;
    /*
        int flags;
        flags = fcntl(0, F_GETFL, 0);
        if (flags == -1)
            ERR_EXIT("fcntl get flag error");
        ret = fcntl(0, SETFL, flags | O_NONBLOCK); //設置爲非阻塞,但不更改其他狀態
        if (ret == -1)
            ERR_EXIT("fcntl set flag error");
    */
    set_flag(0, O_NONBLOCK);
    ret = read(0, buf, 1024);
    if (ret == -1)
        ERR_EXIT("read error");

    printf("buf=%s\n", buf);
    return 0;
}

void set_flag(int fd, int flags)
{
    int val;
    val = fcntl(fd, F_GETFL, 0);
    if (val == -1)
        ERR_EXIT("fcntl get flag error");
    val |= flags;
    if (fcntl(fd, F_SETFL, val) < 0)
        ERR_EXIT("fcntl set flag error");
}

void clr_flag(int fd, int flags)
{
    int val;
    val = fcntl(fd, F_GETFL, 0);
    if (val == -1)
        ERR_EXIT("fcntl get flag error");
    val &= ~flags;
    if (fcntl(fd, F_SETFL, val) < 0)
        ERR_EXIT("fcntl set flag error");
}

測試如下:


因爲將標準輸入的狀態更改爲非阻塞,則read不會阻塞等待輸入而立即返回錯誤,errno將被置爲EAGAIN,即可以重新嘗試。


二、文件鎖結構體

Unix/Linux下的文件鎖結構爲:struct flock;
#include <fcntl.h> 或 #include <sys/fcntl.h>
typedef struct flock
{
  short  l_type;   /* lock operation type 鎖操作類型: F_RDLCK、F_WRLCK、F_UNLCK */
  short  l_whence; /* lock base indicator  鎖的基本指示器 */
                   /* SEEK_SET、SEEK_CUR、SEEK_END、SEEK_DATA、SEEK_HOLE */
  off_t  l_start;  /* starting offset from base 鎖的起始位置 */
  off_t  l_len;    /* lock length; len == 0 means until end of file */
  int    l_sysid;  /* system ID running process holding lock 持有該鎖的進程的系統ID*/
  pid_t  l_pid;    /* process ID of process holding lock 持有該鎖的進程的進程ID*/
  long   l_pad[4]; /* reserve area */
} flock_t;

文件鎖的類型只有兩種,一種是寫鎖也叫排他鎖,一種是讀鎖也就共享鎖,可以有多個進程各持有一個讀鎖,但只能有一個進程持有寫鎖,只有對文件有對應的讀寫權限才能施加對應的鎖類型。中間三個參數 l_whence,  l_start, l_len 決定了被鎖定的文件範圍。當fcntl 函數的cmd爲F_GETLK時,flock 結構體的 l_pid 參數會返回持有寫鎖的進程id。進程退出或者文件描述符被關閉時,會釋放所有的鎖。

示例程序:

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

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


int main(int argc, char *argv[])
{
    int fd;
    fd = open("test2.txt", O_CREAT | O_RDWR | O_TRUNC, 0664);
    if (fd == -1)
        ERR_EXIT("open error");
    /* 只有對文件有相應的讀寫權限才能施加對應的文件鎖 */
    struct flock lock;
    memset(&lock, 0, sizeof(lock));
    lock.l_type = F_WRLCK; // 排他鎖,即不允許其他進程再對其加任何類型的鎖,但讀鎖(共享鎖)允許
    lock.l_whence = SEEK_SET;
    lock.l_start = 0; //從文件開頭開始鎖定
    lock.l_len = 0; // 文件全部內容鎖住

    if (fcntl(fd, F_SETLK, &lock) == 0)
    {
        /* 若爲F_SETLKW,這時如果鎖已經被其他進程佔用,則此進程會阻塞直到其他進程釋放鎖*/
        printf("lock success\n");
        printf("press any key to unlock\n");
        getchar();
        lock.l_type = F_UNLCK;
        if (fcntl(fd, F_SETLK, &lock) == 0)
            printf("unlock success\n");
        else
            ERR_EXIT("unlock fail");
    }
    else
        ERR_EXIT("lock fail");

    return 0; //進程退出會對所有文件解鎖
}
現在其中一個終端執行:


現在文件已經被鎖住了,而且沒有按下任何按鍵,所以卡在這裏,也還沒解鎖,接着在另一個終端再次執行同個程序:


會立即返回錯誤,因爲我們希望施加的是排他鎖,而現在前面一個進程正在佔用寫鎖還沒釋放,所以嘗試施加鎖失敗,而如果fcntl 函數的cmd 設置爲 F_SETLKW,即帶w的版本,則此進程會一直阻塞直到前面一個進程釋放了鎖。


發佈了51 篇原創文章 · 獲贊 9 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章