文件操作-文件锁

close(关闭文件)

  相关函数 open,fcntl,shutdown,unlink,fclose

  表头文件 #include<unistd.h>

  定义函数 int close(int fd);

  函数说明 当使用完文件后若已不再需要则可使用close()关闭该文件,二close()会让数据写回磁盘,并释放该文件所占用的资源。参数fd为先前由open()或creat()所返回的文件描述词。

  返回值 若文件顺利关闭则返回0,发生错误时返回-1。

  错误代码 EBADF 参数fd 非有效的文件描述词或该文件已关闭。

  附加说明 虽然在进程结束时,系统会自动关闭已打开的文件,但仍建议自行关闭文件,并确实检查返回值。

  范例 参考open()

 

  creat(建立文件)

  相关函数 read,write,fcntl,close,link,stat,umask,unlink,fopen

  表头文件 #include<sys/types.h>

  #include<sys/stat.h>

  #include<fcntl.h>

  定义函数 int creat(const char * pathname, mode_tmode);

  函数说明 参数pathname指向欲建立的文件路径字符串。Creat()相当于使用下列的调用方式调用open()

  open(const char * pathname ,(O_CREAT|O_WRONLY|O_TRUNC));

  错误代码 关于参数mode请参考open()函数。

  返回值 creat()会返回新的文件描述词,若有错误发生则会返回-1,并把错误代码设给errno。

  EEXIST 参数pathname所指的文件已存在。

  EACCESS 参数pathname 所指定的文件不符合所要求测试的权限

  EROFS 欲打开写入权限的文件存在于只读文件系统内

  EFAULT 参数pathname 指针超出可存取的内存空间

  EINVAL 参数mode 不正确。

  ENAMETOOLONG 参数pathname太长。

  ENOTDIR 参数pathname为一目录

  ENOMEM 核心内存不足

  ELOOP 参数pathname有过多符号连接问题。

  EMFILE 已达到进程可同时打开的文件数上限

  ENFILE 已达到系统可同时打开的文件数上限

  附加说明 creat()无法建立特别的装置文件,如果需要请使用mknod()。

  范例 请参考open()。

 

  dup(复制文件描述词)

  相关函数 open,close,fcntl,dup2

  表头文件 #include<unistd.h>

  定义函数 int dup (int oldfd);

  函数说明 dup()用来复制参数oldfd所指的文件描述词,并将它返回。此新的文件描述词和参数oldfd指的是同一个文件,共享所有的锁定、读写位置和各项权限或旗标。例如,当利用lseek()对某个文件描述词作用时,另一个文件描述词的读写位置也会随着改变。不过,文件描述词之间并不共享close-on-exec旗标。

  返回值 当复制成功时,则返回最小及尚未使用的文件描述词。若有错误则返回-1,errno会存放错误代码。错误代码EBADF参数fd非有效的文件描述词,或该文件已关闭。


  dup2(复制文件描述词)

  相关函数 open,close,fcntl,dup

  表头文件 #include<unistd.h>

  定义函数 int dup2(int odlfd,int newfd);

  函数说明 dup2()用来复制参数oldfd所指的文件描述词,并将它拷贝至参数newfd后一块返回。若参数newfd为一已打开的文件描述词,则newfd所指的文件会先被关闭。dup2()所复制的文件描述词,与原来的文件描述词共享各种文件状态,详情可参考dup()。

  返回值 当复制成功时,则返回最小及尚未使用的文件描述词。若有错误则返回-1,errno会存放错误代码。

  附加说明 dup2()相当于调用fcntl(oldfd,F_DUPFD,newfd);请参考fcntl()。

  错误代码 EBADF 参数fd 非有效的文件描述词,或该文件已关闭

 

  fcntl(文件描述词操作)

  相关函数 open,flock

  表头文件 #include<unistd.h>

  #include<fcntl.h>

  定义函数 int fcntl(int fd , int cmd);

  int fcntl(int fd,int cmd,long arg);

  int fcntl(int fd,int cmd,struct flock * lock);

  函数说明 fcntl()用来操作文件描述词的一些特性。参数fd代表欲设置的文件描述词,参数cmd代表欲操作的指令。

  有以下几种情况:

  F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述词,并且复制参数fd的文件描述词。执行成功则返回新复制的文件描述词。请参考dup2()。F_GETFD取得close-on-exec旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。

  F_SETFD 设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC位决定。

  F_GETFL 取得文件描述词状态旗标,此旗标为open()的参数flags。

  F_SETFL 设置文件描述词状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。

  F_GETLK 取得文件锁定的状态。

  F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。

  F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。参数lock指针为flock 结构指针,定义如下

  struct flcok

  {

  short int l_type; /* 锁定的状态*/

  short int l_whence;/*决定l_start位置*/

  off_t l_start; /*锁定区域的开头位置*/

  off_t l_len; /*锁定区域的大小*/

  pid_t l_pid; /*锁定动作的进程*/

  };

  l_type 有三种状态:

  F_RDLCK 建立一个供读取用的锁定

  F_WRLCK 建立一个供写入用的锁定

  F_UNLCK 删除之前建立的锁定

  l_whence 也有三种方式:

  SEEK_SET 以文件开头为锁定的起始位置。

  SEEK_CUR 以目前文件读写位置为锁定的起始位置

  SEEK_END 以文件结尾为锁定的起始位置。

  返回值 成功则返回0,若有错误则返回-1,错误原因存于errno.

 

flock(锁定文件或解除锁定)

  相关函数 open,fcntl

  表头文件 #include<sys/file.h>

  定义函数 int flock(int fd,int operation);

  函数说明 flock()会依参数operation所指定的方式对参数fd所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文件的某一区域。

  参数 operation有下列四种情况:

  LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。

  LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。

  LOCK_UN 解除文件锁定状态。

  LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH或LOCK_EX 做OR(|)组合。

  单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。

  返回值 返回0表示成功,若有错误则返回-1,错误代码存于errno。

 

  fsync(将缓冲区数据写回磁盘)

  相关函数 sync

  表头文件 #include<unistd.h>

  定义函数 int fsync(int fd);

  函数说明 fsync()负责将参数fd所指的文件数据,由系统缓冲区写回磁盘,以确保数据同步。

  返回值 成功则返回0,失败返回-1,errno为错误代码。


  lseek(移动文件的读写位置)

  相关函数 dup,open,fseek

  表头文件 #include<sys/types.h>

  #include<unistd.h>

  定义函数 off_t lseek(int fildes,off_t offset ,int whence);

  函数说明 每一个已打开的文件都有一个读写位置,当打开文件时通常其读写位置是指向文件开头,若是以附加的方式打开文件(如O_APPEND),则读写位置会指向文件尾。当read()或write()时,读写位置会随之增加,lseek()便是用来控制该文件的读写位置。参数fildes 为已打开的文件描述词,参数offset 为根据参数whence来移动读写位置的位移数。

  参数 whence为下列其中一种:

  SEEK_SET 参数offset即为新的读写位置。

  SEEK_CUR 以目前的读写位置往后增加offset个位移量。

  SEEK_END 将读写位置指向文件尾后再增加offset个位移量。

  当whence 值为SEEK_CUR 或SEEK_END时,参数offet允许负值的出现。

  下列是教特别的使用方式:

  1) 欲将读写位置移到文件开头时:lseek(int fildes,0,SEEK_SET);

  2) 欲将读写位置移到文件尾时:lseek(int fildes,0,SEEK_END);

  3) 想要取得目前文件位置时:lseek(int fildes,0,SEEK_CUR);

  返回值 当调用成功时则返回目前的读写位置,也就是距离文件开头多少个字节。若有错误则返回-1,errno 会存放错误代码。

  附加说明 Linux系统不允许lseek()对tty装置作用,此项动作会令lseek()返回ESPIPE。

  范例 参考本函数说明

 

  mkstemp(建立唯一的临时文件)

  相关函数 mktemp

  表头文件 #include<stdlib.h>

  定义函数 int mkstemp(char * template);

  函数说明 mkstemp()用来建立唯一的临时文件。参数template 所指的文件名称字符串中最后六个字符必须是XXXXXX。Mkstemp()会以可读写模式和0600 权限来打开该文件,如果该文件不存在则会建立该文件。打开该文件后其文件描述词会返回。文件顺利打开后返回可读写的文件描述词。若果文件打开失败则返回NULL,并把错误代码存在errno 中。

  错误代码 EINVAL 参数template 字符串最后六个字符非XXXXXX。EEXIST 无法建立临时文件。

  附加说明 参数template所指的文件名称字符串必须声明为数组,如:

  char template[ ] ="template-XXXXXX";

  千万不可以使用下列的表达方式

char *template = "template-XXXXXX";

  范例 #include<stdlib.h>

  main( )

  {

  int fd;

  char template[ ]="template-XXXXXX";

  fd=mkstemp(template);

  printf("template = %s/n",template);

  close(fd);

  }

  执行 template = template-lgZcbo


open(打开文件)

  相关函数 read,write,fcntl,close,link,stat,umask,unlink,fopen

  表头文件 #include<sys/types.h>

  #include<sys/stat.h>

  #include<fcntl.h>

  定义函数 int open( const char * pathname, int flags);

  int open( const char * pathname,int flags, mode_t mode);

  函数说明 参数pathname 指向欲打开的文件路径字符串。下列是参数flags 所能使用的旗标:

  O_RDONLY 以只读方式打开文件

  O_WRONLY 以只写方式打开文件

  O_RDWR 以可读写方式打开文件。上述三种旗标是互斥的,也就是不可同时使用,但可与下列的旗标利用OR(|)运算符组合。

  O_CREAT 若欲打开的文件不存在则自动建立该文件。

  O_EXCL 如果O_CREAT 也被设置,此指令会去检查文件是否存在。文件若不存在则建立该文件,否则将导致打开文件错误。此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。

  O_NOCTTY 如果欲打开的文件为终端机设备时,则不会将该终端机当成进程控制终端机。

  O_TRUNC 若文件存在并且以可写的方式打开时,此旗标会令文件长度清为0,而原来存于该文件的资料也会消失。

  O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。

  O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。

  O_NDELAY 同O_NONBLOCK。

  O_SYNC 以同步的方式打开文件。

  O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接,则会令打开文件失败。

  O_DIRECTORY 如果参数pathname 所指的文件并非为一目录,则会令打开文件失败。

  此为Linux2.2以后特有的旗标,以避免一些系统安全问题。参数mode 则有下列数种组合,只有在建立新文件时才会生效,此外真正建文件时的权限会受到umask值所影响,因此该文件权限应该为(mode-umaks)。

  S_IRWXU00700 权限,代表该文件所有者具有可读、可写及可执行的权限。

  S_IRUSR 或S_IREAD,00400权限,代表该文件所有者具有可读取的权限。

  S_IWUSR 或S_IWRITE,00200 权限,代表该文件所有者具有可写入的权限。

  S_IXUSR 或S_IEXEC,00100 权限,代表该文件所有者具有可执行的权限。

  S_IRWXG 00070权限,代表该文件用户组具有可读、可写及可执行的权限。

  S_IRGRP 00040 权限,代表该文件用户组具有可读的权限。

  S_IWGRP 00020权限,代表该文件用户组具有可写入的权限。

  S_IXGRP 00010 权限,代表该文件用户组具有可执行的权限。

  S_IRWXO 00007权限,代表其他用户具有可读、可写及可执行的权限。

  S_IROTH 00004 权限,代表其他用户具有可读的权限

  S_IWOTH 00002权限,代表其他用户具有可写入的权限。

  S_IXOTH 00001 权限,代表其他用户具有可执行的权限。

  返回值 若所有欲核查的权限都通过了检查则返回0 值,表示成功,只要有一个权限被禁止则返回-1。

  错误代码 EEXIST 参数pathname 所指的文件已存在,却使用了O_CREAT和O_EXCL旗标。

  EACCESS 参数pathname所指的文件不符合所要求测试的权限。

  EROFS 欲测试写入权限的文件存在于只读文件系统内。

  EFAULT 参数pathname指针超出可存取内存空间。

  EINVAL 参数mode 不正确。

  ENAMETOOLONG 参数pathname太长。

  ENOTDIR 参数pathname不是目录。

  ENOMEM 核心内存不足。

  ELOOP 参数pathname有过多符号连接问题。

  EIO I/O 存取错误。

  附加说明 使用access()作用户认证方面的判断要特别小心,例如在access()后再作open()空文件可能会造成系统安全上的问题。

  范例 #include<unistd.h>

  #include<sys/types.h>

  #include<sys/stat.h>

  #include<fcntl.h>

  main()

  {

  int fd,size;

  char s [ ]="Linux Programmer!/n",buffer[80];

  fd=open("/tmp/temp",O_WRONLY|O_CREAT);

  write(fd,s,sizeof(s));

  close(fd);

  fd=open("/tmp/temp",O_RDONLY);

  size=read(fd,buffer,sizeof(buffer));

  close(fd);

  printf("%s",buffer);

  }

  执行 Linux Programmer!


read(由已打开的文件读取数据)

  相关函数 readdir,write,fcntl,close,lseek,readlink,fread

  表头文件 #include<unistd.h>

  定义函数 ssize_t read(int fd,void * buf ,size_t count);

  函数说明 read()会把参数fd 所指的文件传送count个字节到buf指针所指的内存中。若参数count为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。

  附加说明 如果顺利read()会返回实际读到的字节数,最好能将返回值与参数count 作比较,若返回的字节数比要求读取的字节数少,则有可能读到了文件尾、从管道(pipe)或终端机读取,或者是read()被信号中断了读取动作。当有错误发生时则返回-1,错误代码存入errno中,而文件读写位置则无法预期。

  错误代码 EINTR 此调用被信号所中断。

  EAGAIN 当使用不可阻断I/O 时(O_NONBLOCK),若无数据可读取则返回此值。

  EBADF 参数fd 非有效的文件描述词,或该文件已关闭。

  范例 参考open()。


  sync(将缓冲区数据写回磁盘)

  相关函数 fsync

  表头文件 #include<unistd.h>

  定义函数 int sync(void)

  函数说明 sync()负责将系统缓冲区数据写回磁盘,以确保数据同步。

  返回值 返回0。


  write(将数据写入已打开的文件内)

  相关函数 open,read,fcntl,close,lseek,sync,fsync,fwrite

  表头文件 #include<unistd.h>

  定义函数 ssize_t write (int fd,const void * buf,size_t count);

  函数说明 write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。当然,文件读写位置也会随之移动。

  返回值 如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。

  错误代码 EINTR 此调用被信号所中断。

  EAGAIN 当使用不可阻断I/O 时(O_NONBLOCK),若无数据可读取则返回此值。

  EADF 参数fd非有效的文件描述词,或该文件已关闭。

  范例 请参考open()。

 


创建锁文件对于资源的排他访问,例如串口,是相当合适的,但是对于访问大的共享文件就是太好了。假如我们拥有一个由一个程序写入的大文件,但是是由许多不同的程序进行持续更新的。当一个程序正在记录一些在较长的时间内所得到的数据,并且正在为其他的一些程序进行处理时就会出现这样的情况。这些正在处理的程序并不会等待日志程序结束--他们是连续运行的--所以他们需要一些合作的方法从而可以提供对于同一个文件的同时访问。

我们可以通过锁住文件的一个区域来到达这种结果,这样只是文件的某一个区域被锁住,但是其他程序可以访问程序的其他部分。这称之为文件段(file-segment),或是文件区域(file-region)。Linux有两种方法可以做到这一点:使用fcntl系统调用与使用lockf调用。我们会主要了解fcntl接口,因为这是最经常用到的接口。lockf是相对较为简单的,并且在Linux上只是fcntl的替换接口用法。然而,fcntl与lockf锁机制不可以同时工作:他们使用不同的底层实现,所以我们不能混用这两种调用;只使用这一种或是另一种。

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

#include <fcntl.h>
int fcntl(int fildes, int command, ...);

fcntl在文件描述符上进行操作,并且依据command参数可以执行不同的任务。而我们所感兴趣的有关文件锁的三个:

❑ F_GETLK
❑ F_SETLK
❑ F_SETLKW

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

int fcntl(int fildes, int command, struct flock *flock_structure);

flock结构是依赖于实现的,但是他至少包含下面的成员:

❑ short l_type;
❑ short l_whence;
❑ off_t l_start;
❑ off_t l_len;
❑ pid_t l_pid;

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

值 描述
F_RDLCK 共享锁(或读锁)。多个进程可以在文件的相同区域(或重叠)具有一个共享锁。如果任何进程在文件的某一部分具有一个共享锁,其他的进程就不可以在相同的区域获得排他锁。为了获得一个共享锁,文件必须使用读或是读写访问模式打开。
F_UNLCK 解锁;用于清除锁。
F_WRLCK 排他锁(或写锁)。在文件的某一个特定区域只可以有一个进程获得排他锁。一旦有一个进程具有一个这样的锁,其他的进程就不可以在此区域上获得任何锁类型。要获得一个排他锁,文件必须以写或是读写模式打开。

l_whence成员定义了一个文件中的区域--一个连续的字节集合。l_whence的值必须是SEEK_SET,SEEK_CUR,SEEK_END中的一个(定义在unistd.h)中。他们分别对应于开始位置,当前位置以及文件结尾。l_whence定义了相对于l_start的偏移,l_start为区域的第一个字节。通常,这个值为SEEK_SET,所以l_start通常由文件的开始处算起。l_len参数定义了区域中的字节数。

l_pid参数用于报告存放锁的进程。由后面的F_GETLK更详细的描述了这一点。

文件中的第一个字节在任意时刻只能具有一种锁类型,或者是共享锁,或者是排他锁,或者是解锁。

fcntl调用的命令与选项有几种组合,下面我们会依次进行讨论。

F_GETLK命令

第一个命令是F_GETLK。他会获得打开的文件fildes的锁信息。他并不会尝试为文件加锁。这个调用进程传递他希望创建的锁的类型信息,并且使用F_GETLK命令的fcntl调用会返回阻止加锁的信息。

flock结构所使用的值如下表所示:

值 描述
l_type 或者是共享锁F_RDLCK,或者是排他锁F_WRLCK
l_whence SEEK_SET,SEEK_CUR,SEEK_END其中的一个
l_start 感兴趣文件区域的起始字节
l_len 感兴趣的文件区域中的字节数
l_pid 带有锁的进程的标识符

一个进程可以使用F_GETLK来确定一个文件区域的加锁状态。他应该设置flock结构来标识他所需要的锁类型并且定义所感兴趣的文件区域。如果fcntl调用成功则会返回一个非-1的值。如果文件已经具有会阻止所请求执行锁的锁时,他就会使用相应的信息来覆盖flock结构。如果请求锁成功,flock结构则不会发生变化。如果F_GETLK调用不能获取相应的信息,他就会返回-1来标识失败。

如果F_GETLK调用成功(例如,他返回一个非-1的值),调用程序必须检测flock结构的内容以确定他是否被修改。因为l_pid的值会被设置为锁进程的值(如果查找成功),这是一个确定flock结构是否发生变化的合理区域。

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章讨论信号

程序在一个文件上加的所有锁都会在相应的文件描述符关闭时进行原子清除。当程序结束时也会自动执行这些动作。

使用锁进行读写操作

当我们在一个文件区域上使用锁时,使用底层的read与write调用来访问文件中的数据是相当重要的,而不是高层的fread与fwrite。这是必须的,因为fread与fwrite会在库在执行数据的读写缓冲,所以执行一个fread调用来读取一个文件的前100个字节也许(事实上,通常会这样)会读取多于100个字节,并且在库中缓冲其余的数据。如果程序然后使用fread来读取接下来的100个字节,他实际上会读取缓冲在库中的数据,并且不允许底层的read调用由文件中读取更多的数据。

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

然后第二个程序启动。他在程序的后100个字节上获得一个写锁。这也会成功,因为第一个程序只锁住了前100个字节。第二个程序在100到199字节上写入2,然后关闭文件,解锁,退出。第一个程序然后锁住文件的后100个字节,并且调用fread来读取。因为数据进行了缓冲,程序实际上看到的是100个0,而不是文件中实际存在的100个2。当我们使用read与write时就不会出这样的问题。

上面所描述的加锁也许会有些复杂,但是使用起来并没有描述的那样困难。

试验--使用fcntl锁住文件

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

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

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
const char *test_file = "/tmp/test_lock";
int main()
{
int file_desc;
int byte_count;
char *byte_to_write = "A";
struct flock region_1;
struct flock region_2;
int res;

2 打开一个文件描述符:

file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
if (!file_desc) {
fprintf(stderr, "Unable to open %s for read/write/n", test_file);
exit(EXIT_FAILURE);
}

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

for(byte_count = 0; byte_count < 100; byte_count++) {
(void)write(file_desc, byte_to_write, 1);
}

4 使用共享锁设置区域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(file_desc, F_SETLK, &region_1);
if (res == -1) fprintf(stderr, "Failed to lock region 1/n");
res = fcntl(file_desc, F_SETLK, &region_2);
if (res == -1) fprintf(stderr, "Failed to lock region 2/n");

7 然后等待一会:

sleep(60);
printf("Process %d closing file/n", getpid());
close(file_desc);
exit(EXIT_SUCCESS);
}

工作原理

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

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

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

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

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

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
const char *test_file = "/tmp/test_lock";
#define SIZE_TO_TRY 5
void show_lock_info(struct flock *to_show);
int main()
{
int file_desc;
int res;
struct flock region_to_test;
int start_byte;

2 打开一个文件描述符:

file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
if (!file_desc) {
fprintf(stderr, "Unable to open %s for read/write", test_file);
exit(EXIT_FAILURE);
}
for (start_byte = 0; start_byte < 99; start_byte += SIZE_TO_TRY) {

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

region_to_test.l_type = F_WRLCK;
region_to_test.l_whence = SEEK_SET;
region_to_test.l_start = start_byte;
region_to_test.l_len = SIZE_TO_TRY;
region_to_test.l_pid = -1;
printf("Testing F_WRLCK on region from %d to %d/n",
start_byte, start_byte + SIZE_TO_TRY);

4 测试文件锁:

res = fcntl(file_desc, F_GETLK, &region_to_test);
if (res == -1) {
fprintf(stderr, "F_GETLK failed/n");
exit(EXIT_FAILURE);
}
if (region_to_test.l_pid != -1) {
printf("Lock would fail. F_GETLK returned:/n");
show_lock_info(&region_to_test);
}
else {
printf("F_WRLCK - Lock would succeed/n");
}

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

region_to_test.l_type = F_RDLCK;
region_to_test.l_whence = SEEK_SET;
region_to_test.l_start = start_byte;
region_to_test.l_len = SIZE_TO_TRY;
region_to_test.l_pid = -1;
printf("Testing F_RDLCK on region from %d to %d/n",
start_byte, start_byte + SIZE_TO_TRY);

6 再次测试文件锁:

res = fcntl(file_desc, F_GETLK, &region_to_test);
if (res == -1) {
fprintf(stderr, "F_GETLK failed/n");
exit(EXIT_FAILURE);
}
if (region_to_test.l_pid != -1) {
printf("Lock would fail. F_GETLK returned:/n");
show_lock_info(&region_to_test);
}
else {
printf("F_RDLCK - Lock would succeed/n");
}
}
close(file_desc);
exit(EXIT_SUCCESS);
}
void show_lock_info(struct flock *to_show) {
printf("/tl_type %d, ", to_show->l_type);
printf("l_whence %d, ", to_show->l_whence);
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 locking file

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

$ ./lock4

我们得到的程序输出如下所示:

Testing F_WRLOCK on region from 0 to 5
F_WRLCK - Lock would succeed
Testing F_RDLOCK on region from 0 to 5
F_RDLCK - Lock would succeed
...
Testing F_WRLOCK on region from 10 to 15
Lock would fail. F_GETLK returned:
l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 1534
Testing F_RDLOCK on region from 10 to 15
F_RDLCK - Lock would succeed
Testing F_WRLOCK on region from 15 to 20
Lock would fail. F_GETLK returned:
l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 1534
Testing F_RDLOCK on region from 15 to 20
F_RDLCK - Lock would succeed
...
Testing F_WRLOCK on region from 25 to 30
Lock would fail. F_GETLK returned:
l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 1534
Testing F_RDLOCK on region from 25 to 30
F_RDLCK - Lock would succeed
...
Testing F_WRLOCK on region from 40 to 45
Lock would fail. F_GETLK returned:
l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 1534
Testing F_RDLOCK on region from 40 to 45
Lock would fail. F_GETLK returned:
l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 1534
...
Testing F_RDLOCK on region from 95 to 100
F_RDLCK - Lock would succeed

工作原理

对于文件中的每五个字节组,lock4设置一个区域结构来测试文件锁,然后程序使用这个区域结构进行测试以确定其为读锁还是写锁。返回的显示了区域字节数,相对于零字节的偏移量,这会使得锁请求失败。因为返回结构的l_pid部分包含当前使得文件锁住的程序的进程标识,程序将其设置为-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程序在此处区域加了排他锁。

 

有三种不同的文件锁,这三种都是"咨询性"的,也就是说它们依靠程序之间的
合作,所以一个项目中的所有程序封锁政策的一致是非常重要的,当你的程序需
要和第三方软件共享文件时应该格外地小心。

有些程序利用诸如 FIlENAME.lock 的文件锁文件,然后简单地测试此类文件是否存在。这种方法显然不太好,因为当产生文件的进程被杀后,锁文件依然存在,这样文件也许会被永久锁住。UUCP 中把产生文件的进程号PID存入文件,但这样做仍然不保险,因为PID的利用是回收型的。

这里是三个文件锁函数:
flock();
lockf();
fcntl();

flock()是从BSD中衍生出来的,但目前在大多数UNIX系统上都能找到,在单个主
机上flock()简单有效,但它不能在NFS上工作。Perl中也有一个有点让人迷惑的
flock()函数,但却是在perl内部实现的。

fcntl()是唯一的符合POSIX标准的文件锁实现,所以也是唯一可移植的。它也同
时是最强大的文件锁--也是最难用的。在NFS文件系统上,fcntl()请求会被递
交给叫rpc.lockd的守护进程,然后由它负责和主机端的lockd对话,和flock()
不同,fcntl()可以实现记录层上的封锁。

lockf()只是一个简化了的fcntl()文件锁接口。

无论你使用哪一种文件锁,请一定记住在锁生效之前用sync来更新你所有的文件
输入/输出。

[code] lock(fd);
write_to(some_function_of(fd));
flush_output_to(fd); /* 在去锁之前一定要冲洗输出 */
unlock(fd);
do_something_else; /* 也许另外一个进程会更新它 */
lock(fd);
seek(fd, somewhere); /* 因为原来的文件指针已不安全 */
do_something_with(fd);[/code] ...

一些有用的fcntl()封锁方法(为了简洁略去错误处理):


[code] #include <fcntl.h>;
#include <unistd.h>;

read_lock(int fd) /* 整个文件上的一个共享的文件锁 */
{
fcntl(fd, F_SETLKW, file_lock(F_RDLCK, SEEK_SET));
}

write_lock(int fd) /* 整个文件上的一个排外文件锁 */
{
fcntl(fd, F_SETLKW, file_lock(F_WRLCK, SEEK_SET));
}

append_lock(int fd) /* 一个封锁文件结尾的锁,
其他进程可以访问现有内容 */
{
fcntl(fd, F_SETLKW, file_lock(F_WRLCK, SEEK_END));
}[/code]
前面所用的file_lock函数如下:

[code] struct flock* file_lock(short type, short whence)
{
static struct flock ret ;
ret.l_type = type ;
ret.l_start = 0 ;
ret.l_whence = whence ;
ret.l_len = 0 ;
ret.l_pid = getpid() ;
return &ret ;
}[/code]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值