您的位置:首页 > 运维架构 > Linux

IPC之记录锁详解

2016-08-17 01:10 176 查看
基本概念:

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

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

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

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

NAME
fcntl - manipulate file descriptor

SYNOPSIS
#include <unistd.h>
#include <fcntl.h>

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

锁的互斥性:

对于单进程:

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

对于多进程,如图示:



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

说明一下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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

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再设置读锁:







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









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







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



锁的隐含继承和释放:

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

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

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

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

建议性锁和强制性锁:

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

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

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

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

End;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息