天天看点

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进行验证.