数据管理四个阶段,数据管理四梁八柱

  数据管理四个阶段,数据管理四梁八柱

  锁定区域

  创建一个锁文件非常适合于对资源的独占访问,比如串行端口,但是对于访问大的共享文件来说更好。假设我们有一个由一个程序编写的大文件,但是它被许多不同的程序不断更新。当一个程序正在记录一些长时间获得的数据,并且正在为其他程序进行处理时,就会出现这种情况。这些处理程序不会等待日志程序完成——它们会连续运行——因此它们需要一些协作方法来提供对同一个文件的同时访问。

  我们可以通过锁定文件的一个区域来实现这个结果,这样就只有文件的一个区域被锁定,但是其他程序可以访问程序的其他部分。这称为文件段或文件区域。Linux可以通过两种方式实现:使用fcntl系统调用和使用lockf调用。我们将主要了解fcntl接口,因为它是最常用的接口。Lockf相对简单,它只是fcntl在Linux上的一种替代接口用法。但是,fcntl和lockf锁定机制不能同时工作:它们使用不同的底层实现,所以我们不能混合使用这两种调用;只使用其中之一。

  我们在第3章中介绍了fcntl调用。其定义如下:

  #包含fcntl.h

  int fcntl(int fildes,int command,);

  fcntl对文件描述符进行操作,并可以根据命令参数执行不同的任务。我们感兴趣的是与文件锁相关的三个方面:

  F_GETLK

  F_SETLK

  F_SETLKW

  当我们使用这些命令时,第三个参数必须是指向struct flock的指针,因此实际的原型形式如下:

  int fcntl(int fildes,int command,struct flock * flock _ structure);

  flock结构依赖于实现,但它至少包含以下成员:

  短l _ type

  短l _ where;

  off _ t l _ start

  off _ t l _ len

  pid _ t l _ pid

  l_type成员可以是几个值之一,这些值通常在fcntl.h中定义,如下表所示:

  价值描述

  F_RDLCK共享锁(或读锁)。多个进程可以在文件的同一区域(或重叠区域)拥有一个共享锁。如果任何进程在文件的某个部分有一个共享锁,其他进程就不能在同一区域获得独占锁。为了获得共享锁,必须使用读取或读写访问模式打开文件。

  解锁F _ UNLCK用来开锁。

  F_WRLCK排他锁(或写锁)。只有一个进程可以在文件的特定区域获得排他锁。一旦一个进程拥有这样的锁,其他进程就不能在这个区域获得任何类型的锁。要获得排他锁,文件必须以写或读写模式打开。

  l _ when成员定义了文件中的一个区域——一组连续的字节。l _ when的值必须是seek _ set、seek _ cur和seek _ end中的一个(在unistd.h中定义)。它们对应于文件的开始位置、当前位置和结尾。L _ when定义了从l_start的偏移量,即该区域的第一个字节。通常这个值是SEEK_SET,所以l_start通常从文件的开头开始计算。l_len参数定义了该区域的字节数。

  l_pid参数用于报告存储锁的过程。这在下面的F_GETLK中有更详细的描述。

  文件中的第一个字节在任何时候都只能有一种锁类型,共享锁、排他锁或未锁。

  fcntl调用了几种命令和选项的组合。我们将在下面依次讨论它们。

  F_GETLK命令

  第一个命令是F_GETLK。他将获得打开文件fildes的锁定信息。他不会试图锁定文件。调用进程传递他要创建的锁的类型信息,使用F_GETLK命令的fcntl调用会返回阻塞锁的信息。

  下表显示了群体结构使用的值:

  价值描述

  L_type要么是共享锁F_RDLCK,要么是排他锁F_WRLCK。

  L _ whenseek _ set,SEEK_CUR,SEEK_END。

  L_start感兴趣的文件区的起始字节。

  感兴趣的文件区域中的字节数

  带锁进程的标识符

  进程可以使用F_GETLK来确定文件区域的锁定状态。他应该设置flock结构来标识他需要的锁类型,并定义感兴趣的文件区域。如果fcntl调用成功,它将返回-1以外的值。如果文件已经有一个锁,它将阻止所请求的锁被执行,它将用相应的信息覆盖flock结构。如果锁定请求成功,群体结构将不会改变。如果F_GETLK调用得不到相应的信息,就会返回-1来标识失败。

  如果F_GETLK调用成功(例如,他返回一个非-1的值),调用程序必须检查flock结构的内容,以确定它是否已被修改。因为l_pid的值将被设置为锁进程的值(如果搜索成功),所以这是确定群体结构是否改变的合理区域。

  F_SETLK命令

  此集合将尝试锁定或解锁fildes指向的文件区域。在flock结构中使用的值(不同于F_GETLK中使用的值)如下表所示:

  价值描述

  L_type l_type可以是只读的,也可以是共享的F_RDLCK或F_WRLCK。或只读或共享的f _ rdlck或独占或写f _ wrlck或者解锁F_UNLCK。

  不使用L_pid

  如果锁定成功,fcntl将返回-1以外的值;如果失败,它将返回-1。函数调用将立即返回。

  F_SETLKW命令

  F_SETLKW命令类似于上面的F_SETLK命令,除了如果他不能获得锁,他将等待直到他可以。一旦这个调用开始等待,它将只在可以获得锁或出现信号时返回。我们将在第11章讨论信号。

  当相应的文件描述符关闭时,由程序放置在文件上的所有锁将被原子清除。当程序结束时,这些操作也将自动执行。

  使用锁进行读写。

  当我们在文件区域使用锁时,使用低级别的读写调用来访问文件中的数据,而不是高级别的fread和fwrite,这是非常重要的。这是必要的,因为fread和fwrite会缓冲库中数据的读写,所以执行fread调用读取文件的前100个字节可能(事实上,通常是这样)会读取超过100个字节,缓冲库中剩余的数据。如果程序随后使用fread读取接下来的100个字节,它将实际读取库中缓冲的数据,并且不允许底层读取调用从文件中读取更多数据。

  为了理解为什么这是一个问题,考虑两个程序更新同一个文件。假设这个文件由200字节的全0组成。第一个程序首先运行,并在文件的前100个字节获得写锁。然后,他使用fread读入100个字节。然而,正如我们在前一章中所看到的,fread一次将读取高达BUFSIZ字节的数据,所以实际上他将所有文件读入内存,但只会将前100个字节传递回程序。

  然后第二个程序开始。他获得了程序最后100个字节的写锁。这也会成功,因为第一个程序只锁定了前100个字节。第二个程序在100到199个字节上写2,然后关闭文件,解锁并退出。然后,第一个程序锁定文件的最后100个字节,并调用fread读取它。因为数据被缓冲,所以程序实际上看到的是100个0,而不是文件中实际存在的100个2。当我们使用读写时,就不会有这样的问题了。

  上述锁定可能有些复杂,但使用起来并不像描述的那样困难。

  测试-使用fcntl锁定文件。

  我们先来看看文件锁是如何工作的:lock3.c要测试文件锁,我们需要两个文件:一个用于锁定文件,一个用于测试。第一个程序实现锁功能。

  1程序代码从必要的文件包含和变量声明开始:

  #包括unistd.h

  #包含stdlib.h

  #包含stdio.h

  #包含fcntl.h

  const char * test _ file="/tmp/test _ lock ";

  int main()

  {

  int file _ desc;

  int字节计数;

  char * byte _ to _ write=" A

  结构群体区域_ 1;

  结构群体区域_ 2;

  int res

  2打开一个文件描述符:

  file_desc=open(test_file,O_RDWR O_CREAT,0666);

  如果(!文件_desc) {

  fprintf(stderr,"无法打开%s进行读/写/n ",test _ file);

  退出(退出_失败);

  }

  3在文件中写入一些数据:

  for(byte _ count=0;字节计数100;字节计数){

  (void)写(文件_desc,字节_写入,1);

  }

  四使用共享锁设置区域1,由10到30字节:

  region _ 1.l _ type=F _ RDLCK

  region _ 1.l _ whence=SEEK _ SET

  region _ 1.l _ start=10

  region _ 1.l _ len=20

  5使用排他锁设置区域2,由40到50字节:

  region _ 2.l _ type=F _ WRLCK

  region _ 2.l _ whence=SEEK _ SET

  region _ 2.l _ start=40

  region _ 2.l _ len=10

  6 现在锁住文件:

  printf("Process %d locking file/n ",getpid());

  res=fcntl(文件desc,F_SETLK,区域_ 1);

  if (res==-1) fprintf(stderr,"未能锁定区域1/n”);

  res=fcntl(文件desc,F_SETLK,区域_ 2);

  if (res==-1) fprintf(stderr,"未能锁定区域2/n”);

  七然后等待一会:

  睡眠(60);

  printf("进程%d关闭文件/n ",getpid());

  关闭(文件_ desc);

  退出(退出_成功);

  }

  工作原理

  这个程序首先创建了一个文件,以读写的方式打开,并向其中填充一些数据。然后他设置两个区域:第一个由10到30字节,使用共享锁,而第二个由40到50字节,使用排他锁。然后程序调用记录锁来锁住两个区域,并且在程序关闭文件退出之前等待一会。

  就程序本身而言,程序并没有多大用处。我们需要另一个文件来测试文件锁,锁4.c。

  试验-在文件上测试文件锁

  下面我们来编写一个程序来测试文件上不同区域的锁类型。

  一如平常一样,我们的程序代码以必要的文件包含和变量声明开始:

  #包括unistd.h

  #包含标准库

  #包含标准视频

  #包含fcntl.h

  const char * test _ file="/tmp/test _ lock ";

  #定义尺寸_到_尝试5

  void show _ lock _ info(struct flock * to _ show);

  int main()

  {

  int file _ desc;

  内部资源

  结构群体区域_至_测试

  (同Internationalorganizations)国际组织起始字节;

  2打开一个文件描述符:

  file_desc=open(test_file,O_RDWR O_CREAT,0666);

  如果(!文件_desc) {

  fprintf(stderr,"无法打开%s进行读/写“,test _ file);

  退出(退出_失败);

  }

  for(start _ byte=0;start _ byte start _ byte=SIZE _ TO _ TRY){

  3设置我们希望测试的文件区域:

  region _ to _ test.l _ type=F _ WRLCK

  区域测试。l _ where=SEEK _ SET

  区域测试。l _ start=起始字节;

  区域测试。l _ len=SIZE _ TO _ TRY

  区域测试。l _ PID=-1;

  printf("在从%d到%d/n的区域上测试F_WRLCK ",

  start_byte,start _ byte SIZE _ TO _ TRY);

  四测试文件锁:

  res=fcntl(文件desc,法国,区域_测试);

  if (res==-1) {

  fprintf(stderr," F_GETLK失败/n ");

  退出(退出_失败);

  }

  if (region_to_test.l_pid!=-1) {

  printf("锁定将失败f _ GETLK returned:/n ");

  显示锁定信息(区域_测试);

  }

  否则{

  printf("F_WRLCK - Lock将成功/n ");

  }

  5使用共享锁重复此操作。再次设置我们希望测试的文件区域:

  region _ to _ test.l _ type=F _ RDLCK

  区域测试。l _ where=SEEK _ SET

  区域测试。l _ start=起始字节;

  区域测试。l _ len=SIZE _ TO _ TRY

  区域测试。l _ PID=-1;

  printf("在从%d到%d/n的区域上测试F_RDLCK ",

  start_byte,start _ byte SIZE _ TO _ TRY);

  6 再次测试文件锁:

  res=fcntl(文件desc,法国,区域_测试);

  if (res==-1) {

  fprintf(stderr," F_GETLK失败/n ");

  退出(退出_失败);

  }

  if (region_to_test.l_pid!=-1) {

  printf("锁定将失败f _ GETLK returned:/n ");

  显示锁定信息(区域_测试);

  }

  否则{

  printf("F_RDLCK - Lock将成功/n ");

  }

  }

  关闭(文件_ desc);

  退出(退出_成功);

  }

  void show _ lock _ info(struct flock * to _ show){

  printf("/tl_type %d,",to _ show-l _ type);

  printf(" l _ where % d,",to _ show-l _ where);

  printf("l_start %d,",(int)to _ show-l _ start);

  printf("l_len %d,",(int)to _ show-l _ len);

  printf("l_pid %d/n ",to _ show-l _ PID);

  }

  为了测试文件锁,我们首先需要运行lock3程序;然后我们运行lock4程序来测试锁文件。我们可以使用以下命令让lock3程序在后台运行:

  $ ./lock3

  $ process 1534锁定文件

  返回命令提示符是因为lock3正在后台运行,然后我们使用以下命令运行lock4程序:

  $ ./lock4

  我们的程序输出如下:

  在从0到5的区域上测试F _锁

  F_WRLCK - Lock将会成功

  在从0到5的区域上测试F_RDLOCK

  F_RDLCK - Lock会成功

  .

  在从10到15的区域上测试F _锁

  锁定会失败。F_GETLK返回:

  l_type 0,l _ whence 0,l_start 10,l_len 20,l_pid 1534

  在从10到15的区域上测试F_RDLOCK

  F_RDLCK - Lock会成功

  在从15到20的区域上测试F _锁

  锁定会失败。F_GETLK返回:

  l_type 0,l _ whence 0,l_start 10,l_len 20,l_pid 1534

  在从15到20的区域上测试F_RDLOCK

  F_RDLCK - Lock会成功

  .

  在从25到30的区域上测试F _锁

  锁定会失败。F_GETLK返回:

  l_type 0,l _ whence 0,l_start 10,l_len 20,l_pid 1534

  在从25到30的区域上测试F_RDLOCK

  F_RDLCK - Lock会成功

  .

  在从40到45的区域上测试F _锁

  锁定会失败。F_GETLK返回:

  l_type 1,l _ whence 0,l_start 40,l_len 10,l_pid 1534

  在从40到45的区域上测试F_RDLOCK

  锁定会失败。F_GETLK返回:

  l_type 1,l _ whence 0,l_start 40,l_len 10,l_pid 1534

  .

  在从95到100的区域上测试F_RDLOCK

  F_RDLCK - Lock会成功

  操作原理

  对于文件中的每五个字节,lock4设置一个区域结构来测试文件锁,然后程序用这个区域结构来测试确定是读锁还是写锁。的返回显示了该区域中相对于零字节偏移量的字节数,这将使锁定请求失败。因为返回结构的l_pid部分包含了当前锁定文件的程序的进程id,所以程序将其设置为-1,然后当fcntl调用返回时,程序会检查它是否发生了变化。如果测试区域未被锁定,l_pid将不会改变。

  要理解程序的输出,我们需要看一下包含的fcntl.h文件,从中可以知道l_type的值是1是因为F_WRLCK的值是1,而l_type的值是0是因为F_RDLCK的值是0。所以l_type的值是1,告诉我们锁失败是因为独占写锁,而l_type的值是0,是因为存在读锁。在文件的这个区域中,lock3程序没有被锁定,因此共享锁和排他锁都是成功的。

  从10到30个字节,我们可以看到他可能得到一个共享锁,因为lock3程序在这里有一个共享锁,而不是一个独占锁。在40-50字节区域,两种类型的锁都将失败,因为lock3程序在该区域添加了一个排他锁。

数据管理四个阶段,数据管理四梁八柱