IPC之记录锁详解


基本概念:

当两个人同时编辑一个文件时,其后果将如何?在大多数unix系统中,该文件的最后状态取决于写该文件的最后一个进程,

记录锁(record locking)的功能是:当一个进程正在读或者修改文件的某个部分时,使用记录锁可以阻止其他进程修改同一区域。


POSIX.1标准是使用fcntl方法控制记录锁,函数原型如下

FCNTL(2)                   Linux Programmer's Manual                  FCNTL(2)

NAME
       fcntl - manipulate file descriptor

SYNOPSIS
       #include 
       #include 

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

对于记录锁,cmd是F_GETLK, F_SETLK 或 F_SETLKW。第三个参数是一个指向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) */
};

对flock结构说明如下:

1.所希望的锁类型:F_RDLCK(共享读锁),F_WRLCK(独占写锁),F_UNLCK(解锁一个区域)

2.要加锁或解锁区域的起始字节偏移量(l_start和l_whence)

3.要加锁或解锁区域的字节长度(l_len)

4.进程的ID(l_pid)持有的锁能阻塞当前进程(仅由F_GETLK返回)


关于加锁和解锁区域的说明:

1.指定区域起始偏移量的两个元素与lseek函数中的最后两个参数类似。l_whence可选用值是SEEK_SET, SEEK_CUR, SEEK_END

2.锁可以在当前文件尾端处开始或者越过文件尾端处开始,但是不能在文件起始位置之前开始

3.如若l_len为0,则表示锁的范围可以扩展到最大可能偏移量,这意味着不管向文件中追加写了多少数据,它们都可以处于锁的范围内

4.为了对整个文件加锁,可以设置l_start为0,l_whence为SEEK_SET,l_len为0


锁的互斥性:

对于单进程:

如果一个进程对一个文件区间已经有了一把锁,后来该进程又企图在同一个文件区间再加一把锁,那么新锁将替换已有锁。


对于多进程,如图示:

IPC之记录锁详解_第1张图片


加读锁时。该描述符必须是读打开。加写锁时,该描述符必须是写打开。


说明一下fcntl函数的3种命令:

F_GETLK:判断所描述的锁是否会被另外一把锁排斥,如果不存在这种情况,l_type被设置为F_UNLCK

F_SETLK:设置锁,如果锁被排斥,那么fcntl会立即出错返回,此时errno设置为EACCES或EAGIN
F_SETLKW:这个命令是F_SETLK的阻塞版本,如果需要设置的锁被排斥,那么进程会休眠等待锁成功设置。

应该了解,用F_GETLK测试能否建立一把锁,然后用F_SETLK或F_SETLKW企图建立那把锁,这两者不是一个原子操作。因此不能保证在这两次fcntl调用之间不会有另一个进程插入并建立一把相同的锁。


程序1,设置读锁,,gcc flock_read.c -o readflock

#include 
#include 
#include 
#include 
#include 
#include 
#include 

void Perror(const char *s)
{
    perror(s);
    exit(EXIT_FAILURE);
}

/* 检查锁,如果返回0,可以加锁, 否则表示存在写锁或者读锁 */
pid_t lock_test(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)
        Perror("fcntl error");

    /* 这里F_UNLCK并不表示文件不存在锁,表示允许加读or写锁 */
    if (lock.l_type == F_UNLCK)
        return 0;

    return lock.l_pid;
}

/* 设置锁 or 解锁 */
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));
}

int main()
{
    // 创建文件
    int fd = open("./test.temp", O_RDONLY | O_CREAT | O_EXCL, 0777);
    if (fd == -1) {
        printf("file exeit\n");
        fd = open("./test.temp", O_RDONLY, 0777);
    } else {
        printf("create file success\n");
    }

    pid_t pid = getpid();
    printf("the proc pid:%d\n", pid);

    // check read
    pid_t lockpid = lock_test(fd, F_RDLCK, 0, SEEK_SET, 0);
    if (lockpid == 0)
        printf("check read lockable, ok\n");
    else
        printf("check read lockable, can't. have write lock, owner pid:%d\n", lockpid);

    // set read lock
    if (lock_reg(fd, F_SETLK, F_RDLCK, 0, SEEK_SET, 0) < 0)
        printf("set read lock failed\n");
    else
        printf("set read lock success\n");

    sleep(60);
    return 0;
}

程序2,设置写锁,,gcc flock_write.c -o writeflock

#include 
#include 
#include 
#include 
#include 
#include 
#include 

void Perror(const char *s)
{
    perror(s);
    exit(EXIT_FAILURE);
}

/* 检查锁,如果返回0,可以加锁, 否则表示存在写锁或者读锁 */
pid_t lock_test(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)
        Perror("fcntl error");

    /* 这里F_UNLCK并不表示文件不存在锁,表示允许加读or写锁 */
    if (lock.l_type == F_UNLCK)
        return 0;

    return lock.l_pid;
}

/* 设置锁 or 解锁 */
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));
}

int main()
{
    // 创建文件
    int fd = open("./test.temp", O_WRONLY | O_CREAT | O_EXCL, 0777);
    if (fd == -1) {
        printf("file exeit\n");
        fd = open("./test.temp", O_WRONLY, 0777);
    } else {
        printf("create file success\n");
    }

    pid_t pid = getpid();
    printf("the proc pid:%d\n", pid);

    // check write
    pid_t lockpid = lock_test(fd, F_WRLCK, 0, SEEK_SET, 0);
    if (lockpid == 0)
        printf("check write lockable, ok\n");
    else
        printf("check write lockable, can't. have read or write lock, owner pid:%d\n", lockpid);

    // set write lock
    if (lock_reg(fd, F_SETLK, F_WRLCK, 0, SEEK_SET, 0) < 0)
        printf("set write lock failed\n");
    else
        printf("set write lock success\n");

    sleep(60);
    return 0;
}

测试1,进程1设置读锁,进程2再设置读锁:

IPC之记录锁详解_第2张图片


测试2,进程1设置读锁,进程2再设置写锁:

IPC之记录锁详解_第3张图片


测试3,进程1设置写锁,进程2再设置读锁:

IPC之记录锁详解_第4张图片


测试4,进程1设置写锁,进程2再设置写锁:

IPC之记录锁详解_第5张图片


锁的隐含继承和释放:

1.进程终止时,它所建立的锁全部释放。

2.无论一个描述符何时关闭,该进程通过这一描述符引用建立的任何一把锁都会释放。

3.由fork产生的子进程不继承父进程所设置的锁。

4.在执行exec后,新程序可以继承原执行程序的锁。因为执行exec前后还是一个进程。我们 只是改变进程执行的程序,并没有创建新的进程。


建议性锁和强制性锁:

建议性锁:建议锁又称协同锁。对于这种类型的锁 ,内核只是提供加减锁以及检测是否加锁的操作,但是不提供锁的控制与协调工作。也就是说,如果应用程序对某个文件进行操作时,没有检测是否加锁或者无视加锁而直接向文件写入数据,内核是不会加以阻拦控制的。因此,建议锁,不能阻止进程对文件的操作,而只能依赖于大家自觉的去检测是否加锁然后约束自己的行为

强制性锁:内核会检查每一个open、read、write,验证调用进程是否违背了正在访问的文件上的某一把锁。


经过测试:unubtu 14是建议性锁



参考:《unix环境高级编程》·第三版

End;

你可能感兴趣的:(linux之进程操作,linux之IPC,Linux之IPC)