Linux:多进程访问同一文件,如何用文件锁进行保护, fcntl()详解

时间:2022-04-25 14:44:14
在Linux系统中,进程运行在自己的虚拟内存空间中,如何协调不同虚拟地址空间中的进程访问一个非共享资源,文件加锁是基本进程通信方法之一。   可以使用fcntl()、lockf()、flock()实现文件锁,进而实现进程之间的通信。   1.fcntl() 【函数介绍】 调用形式(执行失败返回-1): int fcntl(int fd,int cmd); int fcntl(int fd,int cmd,long arg); int fcntl(int fd,int cmd,struct flock *lock);   fcntl()的功能不只是给文件加锁,也可以修改打开文件的性质。对于给文件增加非强制文件锁时,参数lock指向的结构体flock定义如下: struct flock{ ... short l_type; //锁类型,可以为:F_RDLOCK/F_WRLOCK/F_UNLOCK,  F_RDLOCK/F_WRLOCK为获得文件锁读/写权限,F_UNLOCK为释放文件锁 short l_whence;  //l_start的起始点,可以为SEEK_SET,SEEK_CUR,SEEK_END off_t l_start;   //锁的起始偏移值 off_t l_len;      //锁定大小 pid_t l_pid; .... };   该函数返回errno,如果为EAGAIN或者EACCES,表示其他进程已经拥有该文件的锁,本次操作被禁止。其他值的含义略去。   【实例】 #include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc,char* argv[])
{
 int fd;
 struct flock lock;
 int count = 0;
 if(argc != 2)
 {
  printf("Usage: %s filename.\n",argv[1]);
  return 1;
 }
 fd = open(argv[1],O_RDWR);
 if(fd<0)
 {
  printf("Open file failed.\n");
  return 1;
 }
 
 lock.l_type = F_WRLCK;  //设置写锁
 lock.l_whence = 0;    //从文件起始位置开始
 lock.l_start = 0;     //偏移值为0
 lock.l_len = 0;       //整篇加锁
 while(fcntl(fd,F_SETLK,&lock)<0)
 {
  if(errno == EAGAIN||errno ==  EACCES)  //被其他进程加锁了
  {
   if(++count<5)
   {
    sleep(1);  //加锁申请最多持续5s
   }
   else
   {
    fcntl(fd,F_GETLK,&lock);  //否则放弃之前打印当前文件被那个进程加了锁,F_GETLK为获得加锁信息
    printf("Pid: %ld process find pid %ld process lock the file %s.\n",
                                           (long)getpid(),(long)lock.l_pid,argv[0]);
    return 1;
   }
  }
  else
  {
   printf("Error: exec function fcntl failed.\n");
   return 1;
  }
  
 }
 printf("Pid: %ld process locked the file.\n",(long)getpid());
 sleep(8);   //占用文件的时间,这里可以是对文件的一些操作
 printf("Pid: %ld process release the file.\n",(long)getpid());
 return 0;
}
【执行结果】
如果在main函数中进程获得文件锁以后sleep8秒(时间大于另一个进程申请锁的最大时间): //release after 8 second.
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$ gcc -o filelock systemcall2.c
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$ ./filelock testlock.dat& ./filelock testlock.dat& ./filelock testlock.dat &
Pid: 6270 process locked the file.
[1] 6270
[2] 6271
[3] 6272
gaolu@gaolu-desktop:~$ Pid: 6271 process find pid 6270 process lock the file ./filelock.
Pid: 6272 process find pid 6270 process lock the file ./filelock.
Pid: 6270 process release the file.
[1]   Done                    ./filelock testlock.dat
[2]-  Exit 1                  ./filelock testlock.dat
[3]+  Exit 1                  ./filelock testlock.dat 
//可见第一个文件占用时间长,后面2个文件无法获得文件锁访问资源
gaolu@gaolu-desktop:~$
修改占用文件(sleep代替)的时间后: //release after 2 second,eg.
gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$ gcc -o filelock systemcall2.c
gaolu@gaolu-desktop:~$ ./filelock testlock.dat& ./filelock testlock.dat& ./filelock testlock.dat &
Pid: 6325 process locked the file.
[1] 6325
[2] 6326
[3] 6327
gaolu@gaolu-desktop:~$ Pid: 6325 process release the file.
Pid: 6326 process locked the file.
Pid: 6326 process release the file.
Pid: 6327 process locked the file.
Pid: 6327 process release the file.
[1]   Done                    ./filelock testlock.dat
[2]-  Done                    ./filelock testlock.dat
[3]+  Done                    ./filelock testlock.dat
//三个文件依次获得文件的访问权限
gaolu@gaolu-desktop:~$
2.lockf() 【函数介绍】 lockf()是fcntl()在文件加锁方面的一个简化调用,可以方便的进行添加、解除、检测文件锁。 调用形式: int lockf(int fd,int cmd,off_t len); cmd为要执行的操作: ------F_LOCK:给文件加互斥锁。如果文件已经被加锁,则进程被阻塞直到另一个进程将锁释放,申请到为止。子进程不会从父进程那里继承锁。可能导致锁的合并。 ------F_TLOCK:功能与F_LOCK基本一致,只是当文件已经被加锁时,调用进程不会阻塞而是直接打印错误信息并返回。 ------F_ULOCK:对指定的文件部分进行锁的移除。可能导致加锁不分拆分。 ------F_TEST:检测是否加锁,返回0表示未加锁或者被当前进程加锁;返回-1表示被其他进程加锁。   【实例】 #include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc,char* argv[])
{
 int fd;
 int lock_result;
 struct flock lock;
 
 if(argc != 2)
 {
  printf("Usage: %s filename.\n",argv[1]);
  return 1;
 }
 
 fd = open(argv[1],O_RDWR);
 if(fd<0)
 {
  printf("Open file failed.\n");
  return 1;
 }
 lock_result = lockf(fd,F_LOCK,0);  //参数使用F_LOCK,则如果已经加锁,则阻塞到前一个进程释放锁为止,参数0表示对整个文件加锁
 if(lock_result<0)
 {
  perror("Exec lockf function failed.\n");
  return 1;
 }
 
 printf("Pid: %ld process locked the file.\n",(long)getpid());
 sleep(1);
 printf("Pid: %ld process release the file.\n",(long)getpid());
 return 0;
}
  【执行结果】 gaolu@gaolu-desktop:~$
gaolu@gaolu-desktop:~$ gcc -o filelock systemcall2.c
gaolu@gaolu-desktop:~$ ./filelock testlock.dat& ./filelock testlock.dat& ./filelock testlock.dat &
Pid: 6488 process locked the file.
[1] 6488
[2] 6489
[3] 6490
gaolu@gaolu-desktop:~$ Pid: 6488 process release the file.
Pid: 6490 process locked the file.  //阻塞到进程6488释放锁的时候申请到
Pid: 6490 process release the file.
Pid: 6489 process locked the file.
Pid: 6489 process release the file.  //阻塞到进程6490释放锁的时候申请到锁
[1]   Done                    ./filelock testlock.dat
[2]-  Done                    ./filelock testlock.dat
[3]+  Done                    ./filelock testlock.dat