linux C语言实现文件锁

来源:互联网 发布:java webp格式转换jpg 编辑:程序博客网 时间:2024/06/05 15:10

flock函数说明

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

表头文件 #include<sys/file.h>
定义函数 int flock(int fd,int operation);
参数 operation有下列四种情况:
LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。
LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。
LOCK_UN 解除文件锁定状态。
LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH或LOCK_EX 做OR(|)组合。
单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。

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

示例代码

test1.c:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <sys/file.h>
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    FILE*f = fopen("temp","w+");
    if(!f)
    {
        printf("error file\n");
        return0;
    }
    if(0 == flock(fileno(f), LOCK_EX))
    {
        printf("lock...\n");
        getchar();
        fclose(f);
        flock(fileno(f), LOCK_UN);
    }
    else
    {
        printf("lock failed\n");
    }
        return0;
}

test2.c

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/file.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
 
int main()
{
   FILE*fp;
   chartext[]="this is a test!";
 
   if((fp =fopen("temp","w+")) == 0)
      printf("can't open file!\n");
   else
   {
      printf("open file success!\n");
      int i = flock(fileno(fp), LOCK_SH | LOCK_NB); // 加锁以判断文件是否已经被加锁了
      printf("%d\n", i);
      flock(fileno(fp), LOCK_UN);
         
      fwrite(text,strlen(text), 1, fp);
   }
 
   fclose(fp);
   return0;
}


-----------------------------------------我是分割线---------------------------------------

fcntl()

fcntl() 函数的功能很多,可以改变已打开的文件的性质,本文中只是介绍其与获取/设置文件锁有关的功能。fcntl() 的函数原型如下所示:

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

其中,参数 fd 表示文件描述符;参数 cmd 指定要进行的锁操作,由于 fcntl() 函数功能比较多,这里先介绍与文件锁相关的三个取值 F_GETLKF_SETLK 以及 F_SETLKW。这三个值均与 flock 结构有关。flock 结构如下所示:


清单 4. flock 结构

struct flock {

             ...

             short l_type;    /* Type of lock: F_RDLCK,

                                 F_WRLCK, F_UNLCK */

             short l_whence;  /* How to interpret l_start:

                                 SEEK_SET, SEEK_CUR, SEEK_END */

             off_t l_start;   /* Starting offset for lock */

             off_t l_len;     /* Number of bytes to lock */

             pid_t l_pid;     /* PID of process blocking our lock

                                 (F_GETLK only) */

             ...

         };

在 flock 结构中,l_type 用来指明创建的是共享锁还是排他锁,其取值有三种:F_RDLCK(共享锁)、F_WRLCK(排他锁)和F_UNLCK(删除之前建立的锁);l_pid 指明了该锁的拥有者;l_whencel_start l_end 这些字段指明了进程需要对文件的哪个区域进行加锁,这个区域是一个连续的字节集合。因此,进程可以对同一个文件的不同部分加不同的锁。l_whence 必须是 SEEK_SETSEEK_CUR 或 SEEK_END 这几个值中的一个,它们分别对应着文件头、当前位置和文件尾。l_whence 定义了相对于 l_start 的偏移量,l_start 是从文件开始计算的。

可以执行的操作包括:

F_GETLK:进程可以通过它来获取通过 fd 打开的那个文件的加锁信息。执行该操作时,lock 指向的结构中就保存了希望对文件加的锁(或者说要查询的锁)。如果确实存在这样一把锁,它阻止 lock 指向的 flock 结构所给出的锁描述符,则把现存的锁的信息写到 lock 指向的 flock 结构中,并将该锁拥有者的 PID 写入 l_pid 字段中,然后返回;否则,就将 lock 指向的 flock 结构中的 l_type 设置为 F_UNLCK,并保持 flock 结构中其他信息不变返回,而不会对该文件真正加锁。

F_SETLK:进程用它来对文件的某个区域进行加锁(l_type的值为 F_RDLCK 或 F_WRLCK)或者删除锁(l_type 的值为F_UNLCK),如果有其他锁阻止该锁被建立,那么 fcntl() 就出错返回

F_SETLKW:与 F_SETLK 类似,唯一不同的是,如果有其他锁阻止该锁被建立,则调用进程进入睡眠状态,等待该锁释放。一旦这个调用开始了等待,就只有在能够进行加锁或者收到信号时才会返回

需要注意的是,F_GETLK 用于测试是否可以加锁,在 F_GETLK 测试可以加锁之后,F_SETLK 和 F_SETLKW 就会企图建立一把锁,但是这两者之间并不是一个原子操作,也就是说,在 F_SETLK 或者 F_SETLKW 还没有成功加锁之前,另外一个进程就有可能已经插进来加上了一把锁。而且,F_SETLKW 有可能导致程序长时间睡眠。还有,程序对某个文件拥有的各种锁会在相应的文件描述符被关闭时自动清除,程序运行结束后,其所加的各种锁也会自动清除。

fcntl() 既可以用于劝告锁,也可以用于强制锁,在默认情况下,它用于劝告锁。如果它用于强制锁,当进程对某个文件进行了读或写这样的系统调用时,系统则会检查该文件的锁的 O_NONBLOCK 标识,该标识是文件状态标识的一种,如果设置文件状态标识的时候设置了 O_NONBLOCK,则该进程会出错返回;否则,该进程被阻塞。cmd 参数的值 F_SETFL 可以用于设置文件状态标识。

此外,系统调用 fcntl() 还可以用于租借锁,此时采用的函数原型如下:

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

与租借锁相关的 cmd 参数的取值有两种:F_SETLEASE 和 F_GETLEASE。其含义如下所示:

F_SETLEASE:根据下面所描述的 arg 参数指定的值来建立或者删除租约: 

F_RDLCK:设置读租约。当文件被另一个进程以写的方式打开时,拥有该租约的当前进程会收到通知

F_WRLCK:设置写租约。当文件被另一个进程以读或者写的方式打开时,拥有该租约的当前进程会收到通知

F_UNLCK:删除以前建立的租约

F_GETLEASE:表明调用进程拥有文件上哪种类型的锁,这需要通过返回值来确定,返回值有三种:F_RDLCKF_WRLCKF_UNLCK,分别表明调用进程对文件拥有读租借、写租借或者根本没有租借

某个进程可能会对文件执行其他一些系统调用(比如 OPEN() 或者 TRUNCATE()),如果这些系统调用与该文件上由 F_SETLEASE 所设置的租借锁相冲突,内核就会阻塞这个系统调用;同时,内核会给拥有这个租借锁的进程发信号,告知此事。拥有此租借锁的进程会对该信号进行反馈,它可能会删除这个租借锁,也可能会减短这个租借锁的租约,从而可以使得该文件可以被其他进程所访问。如果拥有租借锁的进程不能在给定时间内完成上述操作,那么系统会强制帮它完成。通过 F_SETLEASE 命令将 arg 参数指定为 F_UNLCK 就可以删除这个租借锁。不管对该租借锁减短租约或者干脆删除的操作是进程自愿的还是内核强迫的,只要被阻塞的系统调用还没有被发出该调用的进程解除阻塞,那么系统就会允许这个系统调用执行。即使被阻塞的系统调用因为某些原因被解除阻塞,但是上面对租借锁减短租约或者删除这个过程还是会执行的。

需要注意的是,租借锁也只能对整个文件生效,而无法实现记录级的加锁。


1、协同锁

  协同锁要求参与操作的进程之间协同合作。假设进程“A”获得一个WRITE锁,并开始向文件中写入内容;此时,进程“B”并没有试图获取一个锁,它仍然可以打开文件并向文件中写入内容。在此过程中,进程“B”就是一个非合作进程。如果进程“B”试图获取一个锁,那么整个过程就是一个合作的过程,从而可以保证操作的“序列化”。

  只有当参与操作的进程是协同合作的时候,协同锁才能发挥作用。协同锁有时也被称为“非强制”锁。

  2、强制锁

  强制锁不需要参与操作的进程之间保持协同合作。它利用内核来查检每个打开、读取、写入操作,从而保证在调用这些操作时不违反文件上的锁规则。关于强制锁的更多信息,可以在上找到。

  为了使能Linux中的强制锁功能,你需要在文件系统级别上打开它,同时在单个文件上打开它。其步骤是:

  1、挂载文件系统时使用“-omand”参数。

  2、对于要打开强制锁功能的文件lock_file,必须打开set-group-ID位,关闭group-execute位。(选择此方法的原因是,当你关闭group-execute时,设置set-group-ID就没有实际的意义了)

  Linux文件锁的示例

  为了理解文件锁是如何工作的,我们建立程序文件file_lock.c:

  

  #include <stdio.h>

  #include <fcntl.h>

    

  int main(intargc, char **argv) {

    if (argc > 1) {

      int fd = open(argv[1], O_WRONLY);

      if(fd == -1) {

        printf("Unable to open the file\n");

        exit(1);

      }

      staticstruct flock lock;

    

      lock.l_type = F_WRLCK;

      lock.l_start = 0;

      lock.l_whence = SEEK_SET;

      lock.l_len = 0;

      lock.l_pid = getpid();

    

      int ret = fcntl(fd, F_SETLKW, &lock);

      printf("Return value of fcntl:%d\n",ret);

      if(ret==0) {

        while(1) {

          scanf("%c", NULL);

        }

      }

    }

  }

  用gcc编译此程序:

  

# cc -o file_lock file_lock.c

  使用mount命令带“mand”参数来重新挂载根文件系统,如下所示。这将在文件系统级别使能强制锁功能。

  注意:你必须切换到root用户才能执行下面的命令。

  

# mount -oremount,mand /

  在可执行的(file_lock所在的)目录中创建两个名为“advisory.txt”和“mandatory.txt”的文件。对于“mandatory.txt”使能Set-Group-ID,同时不使能Group-Execute-Bit,如下所示:

  

# touch advisory.txt

  # touch mandatory.txt

  # chmod g+s,g-x mandatory.txt

  测试协同锁:执行示例程序,以“advisory.txt”作为参数。

  

# ./file_lock advisory.txt

  此程序将等待用户的输入。从另一个终端或控制台,尝试输入以下命令行:

  

# ls >>advisory.txt

  在上面的例子中,ls命令会将其输出写入到advisory.txt文件中。即使我们获得了一个写入锁,仍然会有一些进程(非合作)能够往文件里写入数据。这就是所谓的“协同”锁。

  测试强制锁:再次执行示例程序,以“mandatory.txt”作为参数。

  

# ./file_lock mandatory.txt

  从另一个终端或控制台,尝试输入以下命令行:

  

# ls >>mandatory.txt

  在上面的例子中,ls命令在将其输出写入到mandatory.txt文件之前,会等待文件锁被删除。虽然它仍然是一个非合作进程,但强制锁起了作用。




0 0
原创粉丝点击