基本概念:
当两个人同时编辑一个文件时,其后果将如何?在大多数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
锁的互斥性:
对于单进程:
如果一个进程对一个文件区间已经有了一把锁,后来该进程又企图在同一个文件区间再加一把锁,那么新锁将替换已有锁。
对于多进程,如图示:
加读锁时。该描述符必须是读打开。加写锁时,该描述符必须是写打开。
说明一下fcntl函数的3种命令:
F_GETLK:判断所描述的锁是否会被另外一把锁排斥,如果不存在这种情况,l_type被设置为F_UNLCK
应该了解,用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;
}
测试2,进程1设置读锁,进程2再设置写锁:
测试3,进程1设置写锁,进程2再设置读锁:
测试4,进程1设置写锁,进程2再设置写锁:
锁的隐含继承和释放:
1.进程终止时,它所建立的锁全部释放。
2.无论一个描述符何时关闭,该进程通过这一描述符引用建立的任何一把锁都会释放。
3.由fork产生的子进程不继承父进程所设置的锁。
4.在执行exec后,新程序可以继承原执行程序的锁。因为执行exec前后还是一个进程。我们 只是改变进程执行的程序,并没有创建新的进程。
建议性锁和强制性锁:
建议性锁:建议锁又称协同锁。对于这种类型的锁 ,内核只是提供加减锁以及检测是否加锁的操作,但是不提供锁的控制与协调工作。也就是说,如果应用程序对某个文件进行操作时,没有检测是否加锁或者无视加锁而直接向文件写入数据,内核是不会加以阻拦控制的。因此,建议锁,不能阻止进程对文件的操作,而只能依赖于大家自觉的去检测是否加锁然后约束自己的行为
强制性锁:内核会检查每一个open、read、write,验证调用进程是否违背了正在访问的文件上的某一把锁。
经过测试:unubtu 14是建议性锁
参考:《unix环境高级编程》·第三版
End;