文件锁定

文件锁定

  两个进程之间需要共享数据时可以通过文件来实现,即将共享的数据放于某一文件之中,而进程则分别读写这个文件(该文件被称为 共享文件 )。但是使用文件来共享数据的同时也必须要对文件加以控制,或者说制定一套让进程们合理访问文件的规则,即文件锁定。
  例如,当某一个写进程正在操作文件时,给文件加一把"锁",使得读进程尝试访问该文件时需要先等待这个文件被"解锁"。
  这里的"锁"也可以是一个文件,被称为 锁文件 。要注意,锁文件充当的是一个信号、一把锁,锁的是共享文件,它 本身并不是共享文件

创建锁文件

  创建锁文件有一个要求就是必须保证该锁文件是唯一的,除了应该避免同一目录下与已有的文件重复,还应该避免与其它进程在同一时刻创建同一文件,而方法之一就是通过原子操作来创建一个锁文件。

原子操作指的是无法被打断的一组操作,无论是中断还是调度机制。这样就确保了要创建的文件不会在同一时刻被其它进程创建。

  具体做法可以使用open系统调用并使用O_CREAT和O_EXCL标志:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char **argv)
{
	int fd;
	int save_errno;
	fd = open("/tmp/LOCK", O_RDWR | O_CREAT | O_EXCL, 0444);
	if(fd == -1){
		save_errno = errno;
		printf("Open failed with error %d\n", save_errno);
	} else {
		printf("Open succeeded\n");
	}
	exit(EXIT_SUCCESS);
}

上面的代码主要做了这几件事:

  • 判断要创建的文件是否存在
  • 若存在调用open失败,返回-1(这是由O_EXCL标志造成的)
  • 若不存在创建该文件(记得open是原子操作,创建时不可能有其它进程创建同一文件)

  需要注意的是,open调用以读写的方式打开文件,但创建文件时指出文件的权限是0444(即无论是主、组、其它用户对该文件都是只读)。这并不矛盾,因为一个新文件被创建时所拥有的权限,是要等到该文件被关闭后才生效的。也就是说只要在刚创建完该文件而不关闭它,完全可以对其进行读写操作,一旦关闭该文件(或者结束进程,进程一旦结束其所拥有的文件描述符都会被关闭)0444权限就会立即生效。

关于运行结果,如果第一次运行一般会运行成功,即成功创建了该文件;而再次运行就会报错,因为文件已经存在了。(你可以去相应路径下把它删了)

锁文件如何锁住共享文件:

  创建锁文件的目的是为了让进程之间能够合理进行通信,而锁文件应该经历被创建、打开、关闭、删除这一系列的状态。
  其中创建、打开锁文件相当于给共享文件上锁,成功创建了锁文件的进程才可进一步访问共享文件;而关闭、删除锁文件相当于解锁,只有删除了锁文件其它进程才有机会给共享文件上锁从而进一步访问共享文件。
  基于上诉的过程,前面的代码明显是不符合的,因为锁文件始终没有被删除,可以利用unlink系统调用来修改上面的代码。

锁文件与共享文件的关系

  前面已经说了两者的关系,以及锁文件是如何锁住共享文件的,下面用一张图解释一下这些关系:
文件锁定

  图中文字看不清所以将文字部分再复制一遍:

  • 进入区:
    进入区即进程申请访问资源的代码部分。在这里进入区指的就是进程尝试创建、打开锁文件的代码部分,一旦成功创建锁文件就相当于申请成功,由于此时其它进程无法创建相同的锁文件,即其它进程申请资源失败,那么就相当于锁文件给资源上了锁。这个资源在这里指的就是共享文件。

  • 临界区:
    一旦在前面进入区中,进程创建锁文件成功就会进入到临界区,所谓临界区指的是进程访问资源的代码部分。比如对资源的读写操作便是临界区的部分,在这里就是对共享文件进行读写的部分。一般将只能被一个进程占用的资源称为临界资源,所以访问临界资源的代码就称为临界区。

  • 退出区:
    退出区就是给临界资源(这里就是共享文件)解锁的代码部分。在这里删除锁文件就相当于给共享文件解锁。如果进程不删除锁文件,其它进程就永远不可能成功创建相同的锁文件导致申请不到资源。

  • 剩余区:
    剩余区就是进程访问完临界资源并解锁后的剩余代码部分。