假设一种情况,如果有两个进程同时操作同一个文件,那么最后这个文件的状态会是如何?
在Linux系统中,是允许多个进程同时操作一个文件的,而文件的状态是由最后一个修改进程决定的。但是如果想要保证当前进程的修改是排他的,比如操作一个数据库文件,该怎么办呢?
这时就可以使用记录锁功能保证对文件的操作是排他的,独占的。类UNIX系统中,存在很多种记录锁的实现,本文主要介绍POSIX.1标准的记录锁,POSIX全称为“可移植性的UNIX操作系统接口标准”,该标准是为了方便移植而定义的统一接口,在各种类UNIX系统中都能够兼容,所以重点介绍它。
POSIX.1定义的记录锁接口如下:
#include <fcntl.h>
int fcntl (int fd, int cmd, ...);
实际上是基于fcntl函数实现的,该函数可以根据cmd值的不同,而选择是否接收第三个参数。对于记录锁来说:
需要第三参数:
struct flock {
short l_type;
short l_whence; //SEEK_SET,SEEK_CUR,SEEK_END
off_t l_start;
off_t l_len; //以字节为单位的加锁范围长度,0表示全局到EOF范围
pid_t l_pid; //加锁的进程id,F_GETLK时会返回
};
l_type表示操作类型,可以接受读锁(F_RDLOCK),写锁(F_WRLOCK),解锁(F_UNLOCK)三种值,记录锁是支持文件区域加锁的,l_whence和l_start搭配使用表示该文件锁的范围起始位置,l_len表示加锁长度,l_pid为加锁的进程id。
特别值得注意的是,如果l_whence配置为SEEK_SET,l_start配置为0,l_len也配置为0的话,并不是代表此记录锁加锁区域为0,而是表示对整个文件全局范围加锁,后续新写入的区域也被加锁。
第二个参数cmd:
F_GETLK:判断是否能够获取到锁,如果判断能够获取锁成功,则返回结构体中l_type会被设置为F_UNLOCK,否则返回当前已经持锁的结构体。
F_SETLK:非阻塞获取锁,如果成功则返回0,否则errno返回EACCESS或者EAGAIN。锁的类型由第三个参数传入,可以是读锁也可以是写锁,当然也能用来解锁UNLOCK。
F_SETLKW:阻塞获取锁,功能和SETLK一样,但是是阻塞获取。阻塞状态可以被信号中断。
完整加锁示例:
int f_lock(int fd)
{
int ret = 0;
struct flock fl;
fl.l_type = F_WRLOCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
ret = fcntl(fd, F_SETLK, &fl);
return ret;
}
记录锁的隐含释放规则
1.当获取记录锁的进程终止时,记录锁是自动释放的;
2.当同一个文件对应的多个fd中有任一个fd关闭时,该文件对应的记录锁是自动释放的;
3.fork产生的子进程会继承文件描述符,但是不继承记录锁的;
4.如果对一个文件设置了执行时关闭,那么执行了exec后,父进程会自动释放该文件的记录锁。
建议锁和强制锁
建议锁:
指的是记录锁的控制是由软件决定,要求软件需要按照统一操作规则来操作一个文件,比如对一个文件的读写操作前都先判断记录锁状态是否允许,以此达到锁的目的。建议性锁要求软件控制读写行为,如果一个进程对一个文件加了写锁,而另一个进程不管锁的状态直接写入实际上也是可以成功的,但是这时就达不到锁的目的了。
强制锁:
强制记录锁,就不是简单靠应用程序控制读写行为了,如果设置一个记录锁,那么不需要先判断记录锁,底层读写操作中会自动进行文件记录锁的判断,如果该进程没有获取记录锁,读写是会直接返回失败的。这就是和建议性锁的差异所在。
打开强制性锁的开关,可以通过如下方式:
1.文件属性中的设置组ID位需要置位
2.文件属性中的组执行位需要清除