天天看點

linux記錄鎖(建議鎖)的幾個疑惑

    在APUE中的第12節中提到了記錄鎖,按照文中提到的示例自己嘗試運作了一遍,最後發現結果不是預期的:

/*************************************************************************
> File Name: file_lock.c
> Author: liuxingen
> Mail: [email protected] 
> Created Time: 2014年07月24日 星期四 21時08分32秒
************************************************************************/

#include "file_lock.h"

int lock_reg(int fd, int cmd, 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;
    lock.l_len = len;

    return fcntl(fd, cmd, &lock);
}


/*
* 測試鎖
* @retval:
*          -1:error
*          0:無鎖
*          >0:鎖程序ID
*/
pid_t test_lock(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;
    lock.l_len = len;

    if(fcntl(fd, F_GETLK, &lock) < 0)
    {
        return (pid_t)-1;
    }

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

    return lock.l_pid;
}
           
/*************************************************************************
	> File Name: file_lock.h
	> Author: liuxingen
	> Mail: [email protected] 
	> Created Time: 2014年08月02日 星期六 21時39分07秒
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

pid_t test_lock(int fd, int type, off_t offset, int whence, off_t len);
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len);

#define read_lock(fd, offset, whence, len) \
        lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, len)

#define readw_lock(fd, offset, whence, len)\
        lock_reg(fd, F_SETLKW, F_RDLCK, offset, whence, len)

#define write_lock(fd, offset, whence, len)\
        lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, len)

#define writew_lock(fd, offset, whence, len)\
        lock_reg(fd, F_SETLKW, F_WRLCK, offset, whence, len)

#define un_lock(fd, offset, whence, len)\
        lock_reg(fd, F_SETLK, F_UNLCK, offset, whence, len)

 #define is_readlock(fd, offset, whence, len)\
            test_lock(fd, F_RDLCK, offset, whence, len)

    #define is_writelock(fd, offset, whence, len)\
            test_lock(fd, F_WRLCK, offset, whence, len)
           
/*************************************************************************
  > File Name: test_lock.c
  > Author: liuxingen
  > Mail: [email protected] 
  > Created Time: 2014年07月24日 星期四 21時08分32秒
 ************************************************************************/


#include "file_lock.h"


int rflag = 0, wflag = 0, tflag = 0;


void file_lock(int fd)
{
    pid_t pid;


    if(wflag && write_lock(fd, 0, SEEK_SET, 1) != -1)      //獨占寫鎖
    {
        fprintf(stderr, "write_lock success.\n");
    }else if(wflag)
    {
        fprintf(stderr, "write_lock error:%s\n", strerror(errno));
    }


    if(rflag && read_lock(fd, 0, SEEK_SET, 1) != -1)       //共享讀鎖
    {
        fprintf(stderr, "read_lock success.\n");
    }else if(rflag)
    {
        fprintf(stderr, "read_lock error:%s\n", strerror(errno));
    }


    if(tflag && (pid = is_writelock(fd, 0, SEEK_SET, 1)) > 0)           //測試寫鎖
    {
        fprintf(stderr, "cannot add write lock(PID %d have add write or read lock on it)\n", pid);
    }else if(tflag && pid == 0)
    {
        fprintf(stderr, "can add write lock\n");
    }


    if(tflag && (pid = is_readlock(fd, 0, SEEK_SET, 1)) > 0)        //測試讀鎖
    {
        fprintf(stderr, "cannot add read lock(PID %d have add write lock on it)\n", pid);
    }else if(pid == 0)
    {
        fprintf(stderr, "can add read lock\n");
    } 
}


int main(int argc, char *argv[])
{
    int fd, opt;
    while((opt = getopt(argc, argv, "rwt")) != -1)
    {
        switch(opt)
        {
            case 'r':
                rflag = 1;      //read lock
                break;
            case 'w':
                wflag = 1;      //write lock
                break;
            case 't':
                tflag = 1;      //test lock
                break;
            default:
                fprintf(stderr, "Usage: %s -r -w -t\n", argv[0]);
                return 1;
        }
    }
    if((fd = open("/tmp/file_lock", O_RDWR | O_CREAT, S_IRWXU)) == -1)
    {
        fprintf(stderr, "creat error:%s\n", strerror(errno));
        return 1;
    }
    file_lock(fd);
    close(fd);
    return 0;
}
           
[email protected]:~/station$ ./lock2 -rwt
write_lock success.
read_lock success.
can add write lock
can add read lock
           

    從上面輸出的結果來看F_WRLCK貌似并不是獨占鎖了,因為後面的讀鎖F_RDLCK成功了,更詭異的是我們竟然看到沒有任何程序鎖住檔案.這是為什麼呢?

    對結果百思不得其解,反複嘗試了好幾遍,并且檢查了代碼都木有發現可疑之處,這個時候不得不又重新看書,試圖從中找出點思路.在APUE中關于記錄鎖是這樣描述的"記錄鎖的功能是:一個程序正在讀或者修改檔案的某個部分時,可疑阻止其他程序修改同一檔案區.",當重新讀完這句話的時候我突然明白上面demo的輸出為什麼不符合預期了,因為我是在同一個程序中進行的寫鎖,讀鎖,測試鎖. shit

    下面是一個稍微修改後的demo:

/*************************************************************************
  > File Name: test_lock.c
  > Author: liuxingen
  > Mail: [email protected] 
  > Created Time: 2014年07月24日 星期四 21時08分32秒
 ************************************************************************/


#include "file_lock.h"


int rflag = 0, wflag = 0, tflag = 0;


void file_lock(int fd)
{
    pid_t pid;


    if(wflag && write_lock(fd, 0, SEEK_SET, 1) != -1)      //獨占寫鎖
    {
        fprintf(stderr, "write_lock success.\n");
    }else if(wflag)
    {
        fprintf(stderr, "write_lock error:%s\n", strerror(errno));
    }


    if(rflag && read_lock(fd, 0, SEEK_SET, 1) != -1)       //共享讀鎖
    {
        fprintf(stderr, "read_lock success.\n");
    }else if(rflag)
    {
        fprintf(stderr, "read_lock error:%s\n", strerror(errno));
    }


    if(tflag && (pid = is_writelock(fd, 0, SEEK_SET, 1)) > 0)           //測試寫鎖
    {
        fprintf(stderr, "cannot add write lock(PID %d have add write or read lock on it)\n", pid);
    }else if(tflag && pid == 0)
    {
        fprintf(stderr, "can add write lock\n");
    }


    if(tflag && (pid = is_readlock(fd, 0, SEEK_SET, 1)) > 0)        //測試讀鎖
    {
        fprintf(stderr, "cannot add read lock(PID %d have add write lock on it)\n", pid);
    }else if(pid == 0)
    {
        fprintf(stderr, "can add read lock\n");
    } 
}


int main(int argc, char *argv[])
{
    int fd, opt;
    while((opt = getopt(argc, argv, "rwt")) != -1)
    {
        switch(opt)
        {
            case 'r':
                rflag = 1;      //read lock
                break;
            case 'w':
                wflag = 1;      //write lock
                break;
            case 't':
                tflag = 1;      //test lock
                break;
            default:
                fprintf(stderr, "Usage: %s -r -w -t\n", argv[0]);
                return 1;
        }
    }
    if((fd = open("/tmp/file_lock", O_RDWR | O_CREAT, S_IRWXU)) == -1)
    {
        fprintf(stderr, "creat error:%s\n", strerror(errno));
        return 1;
    }
    file_lock(fd);
    sleep(15);
    close(fd);
    return 0;
}
           

    輸出的結果如下:

[email protected]:~/station$ ./lock2 -w
write_lock success.
同時另外一個程序也進行同樣的操作
[email protected]:~/station$ ./lock2 -rw
write_lock error:Resource temporarily unavailable
read_lock error:Resource temporarily unavailable
           

    上面的結果來看完全符合預期,當一個程序調用F_WRLCK鎖住了檔案區域以後其他的程序就不能擷取到鎖了.

    在APUE中提到"F_GETLK主要用來檢測是否有某個已存在鎖會妨礙将新鎖授予調用程序,如果沒有這樣的鎖,lock所指向的flock結構的l_type成員就會被置成F_UNLCK,否則已存在的鎖的資訊将會寫入lock所指向的flock結構中",我一開始的了解是隻要有程序已經用F_SETLK or F_SETLKW鎖住了檔案區域那麼調用F_GETLK就能擷取到對應的程序PID,其實這個了解是錯誤的.F_GETLK是一個能否獲得對應類型鎖的能力檢測,并不是獲得已經上鎖的程序資訊.

[email protected]:~/station$ ./lock2 -r
read_lock success.
[email protected]:~/station$ ./lock2 -t
cannot add write lock(PID 18900 have add write or read lock on it)
can add read lock
           

    從上面的輸出來看,明明已經有程序F_RDLCK鎖住了檔案區域但是另外一個程序卻可以再次獲得讀鎖.關于記錄鎖的規則可以參考APUE并自己調用demo進行驗證.