linux中的文件锁(劝告性上锁和强制性上锁)

上午在看UNP卷二这一节的时候及其想睡觉,就草草了事,夜晚没有事情干,就来找找博客看看这两个锁到底是怎么回事吧!

参考文章:https://www.ibm.com/developerworks/cn/linux/l-cn-filelock/index.html

背景知识:在早期的 UNIX 系统,其只支持对整个文件进行加锁,因此无法运行数据库之类的程序,因为此类程序需要实现记录级的加锁。而在 System V Release 3 中,通过 fcntl 提供了记录级的加锁,此后发展成为 POSIX 标准的一部分。

linux中支持的文件所技术只要包括劝告锁(advisory lock) 和强制锁(mandatory lock)这两种。此外,linux中还引入了两种强制锁的变种形式:共享模式强制锁(share-mode mandatory lock)和租借锁(lease)

在linux中,不论进程是在使用劝告锁还是强制锁,它都可以同时使用共享锁和独占锁(读锁和写锁),在同一时刻,可以有多个共享锁在一个文件中,但不能有独占锁;当有一个独占锁的时候,就不能有共享锁和其他独占锁。

劝告锁

有的也称建议性上锁。所谓建议性,我们这样想。就像红灯停绿灯行一样,我们把红绿灯当作一种建议锁,但其只是一种规则,还是会有人闯红的。

那我们的劝告锁也是这样。在上面我们介绍了关于共享锁和独占锁的一般规律,但是劝告锁的话,就是虽然设置了这个规定,但是他还是不能防止在一个文件有共享锁的时候,另一个独占锁还可以往文件写东西。当有一个独占锁的时候,还是可以有一个共享锁来读文件内容。

对于这一种锁来说,内核只提供加锁以及检测文件是否已经加锁的手段,但是内核并不参与锁的控制和协调。

劝告锁是一种协同工作的锁。它对于协作进程(cooperating processes)已经足够了。我们先来解释一下这个cooperating processes

它指的就是具有协作关系的进程:

某些进程为完成同一任务需要分工协作,由于合作的每一个进程都是独立地以不可预知的速度推进,这就需要相互协作的进程在某些协调点上协调各自的工作。当合作进程中的一个到达协调点后,在尚未得到其伙伴进程发来的消息或信号之前应阻塞自己,直到其他合作进程发来协调信号或消息后方被唤醒并继续执行。这种协作进程之间相互等待对方消息或信号的协调关系称为进程同步。

举两个例子:(1)我们可以同时在两个窗口中运行同一个命令,对同一个文件进行操作,那么这两个进程就是cooperating processes;(2)进程间的协作可以是双方不知道对方名字的间接协作 如:cat file| sort,那么cat和sort产生的进程就是使用了pipe的cooperating processes。

那在shell中协作进程是指一个shell命令的前面添加了coproc关键字的情形。

也就是说如果为协作进程的话,就像管道,它本身就会等待上一个的输出,所以说,对于协作进程来说劝告锁就够了。

在网络编程中守护程序的编写是协作进程的一个例子:这些程序访问诸如序列号之类的共享资源,而且都在系统管理员的控制之下。

下面是一个守护进程的程序,保证了其不管任何时候都只有一个副本。

介个程序一定要注意及时刷新缓冲区,另外也提醒我们把带有缓冲区的I/O,和不带缓冲区的I/O混用是一件很不好的编程行为!

//守护进程
#include
#include
#include
#include
#include
#include

#define write_lock(fd,offset,whence,len)\
		lock_reg(fd,F_SETLK,F_WRLCK,offset,whence,len)

#define FILENAME "./2.txt"

/*
守护进程维护一个只有一行的文本
其中含有它的进程ID,它打开一个文件然后请求整个文件的一个写入锁,
如果没有请求成功,则证明有一个副本在运行的

*/

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 argc, char const *argv[])
{
	int fd;
	char line[1000];
	fd = open(FILENAME,O_RDWR | O_CREAT,644);
	if(write_lock(fd,0,SEEK_SET,0) < 0)
	//还要近一步根据错误原因来判断
	{
		if(errno == EACCES || errno == EAGAIN)
			printf("unable to lock %s,is %s already running?\n",
			//这里一定要加回车符刷新一下缓冲区,不然会什么消息都看不到
				FILENAME,argv[0]);
		else
			printf("unable to lock %s",FILENAME);
	}

	//判断完之后这几步不要忘记了
	//即没有一个守护进程的时候,要把含有自己PID的文本写进文件中
	snprintf(line,sizeof(line),"%ld\n",(long) getpid());
	ftruncate(fd,0);
	write(fd,line,strlen(line));
	//sizeof和strlen的区别也是需要注意的

	pause();

}

运行结果如下:
linux中的文件锁(劝告性上锁和强制性上锁)_第1张图片

强制锁

与劝告锁不同,强制锁是一种内核强制采用的文件锁,它是从 System V Release 3 开始引入的。每当有系统调用 open()、read() 以及write() 发生的时候,内核都要检查,以验证其操作不会干扰由某个进程持有的某个锁。

对于通常的阻塞式描述符,与某个强制性锁冲突的write或read讲把调用进程投入睡眠,直到该锁释放为止。对于非阻塞式描述符,将会返回一个EAGAIN错误。

然而,在有些应用中并不适合使用强制锁,所以索引节点结构中的 i_flags 字段中定义了一个标志位MS_MANDLOCK用于有选择地允许或者不允许对一个文件使用强制锁。在 super_block 结构中,也可以将 s_flags 这个标志为设置为1或者0,用以表示整个设备上的文件是否允许使用强制锁。

为对某个特定文件施行强制性上锁,应满足:
(1)组成员执行位必须关掉
(2)SGID位必须打开

$ chmod g+s test.txt
$ chmod g-x test.txt

但是理论上强制锁可以解决非协作进程的问题,但是任何read和write调用都将阻塞进程本身,直到该文件的锁被释放为止,不幸的是,定时问题相当复杂。

共享模式锁

共享模式强制锁可以用于某些私有网络文件系统,如果某个文件被加上了共享模式强制锁,那么其他进程打开该文件的时候不能与该文件的共享模式强制锁所设置的访问模式相冲突。但是由于可移植性不好,因此并不建议使用这种锁。

租借锁

采用强制锁之后,如果一个进程对某个文件拥有写锁,只要它不释放这个锁,就会导致访问该文件的其他进程全部被阻塞或不断失败重试;即使该进程只拥有读锁,也会造成后续更新该文件的进程的阻塞。为了解决这个问题,Linux 中采用了一种新型的租借锁。

当进程尝试打开一个被租借锁保护的文件时,该进程会被阻塞,同时,在一定时间内拥有该文件租借锁的进程会收到一个信号。收到信号之后,拥有该文件租借锁的进程会首先更新文件,从而保证了文件内容的一致性,接着,该进程释放这个租借锁。如果拥有租借锁的进程在一定的时间间隔内没有完成工作,内核就会自动删除这个租借锁或者将该锁进行降级,从而允许被阻塞的进程继续工作。

系统默认的这段间隔时间是 45 秒钟,定义如下:

 int lease_break_time = 45;

这个参数可以通过修改 /proc/sys/fs/lease-break-time 进行调节(当然,/proc/sys/fs/leases-enable 必须为 1 才行)。

你可能感兴趣的:(linux,记录锁)