[Linux C编程]Linux文件锁

来源:互联网 发布:2017年1月进出口数据 编辑:程序博客网 时间:2024/05/29 15:30

Linux C 文件锁

Linux系统中,文件上锁的函数有两个flock和fcntl ,前者主要是加建议性锁,后者既可以加建议性锁(默认)也可以加强制性锁,而且它还可以给某一记录进行上锁,即所谓的记录锁。

fcntl文件锁有两种类型:建议性锁 和强制性锁 
  建议性锁是这样规定的:每个使用上锁文件的进程都要检查是否有锁存在,当然还得尊重已有的锁。内核和系统总体上都坚持不使用建议性锁,它们依靠程序员遵守这个规定。
  强制性锁是由内核执行的。当文件被上锁来进行写入操作时,在锁定该文件的进程释放该锁之前,内核会阻止任何对该文件的读或写访问,每次读或写访问都得检查锁是否存在。

系统默认fcntl都是建议性锁,强制性锁是非POSIX标准的。如果要使用强制性锁,要使整个系统可以使用强制性锁,那么得需要重新挂载文件系统,mount使用参数 -0 mand打开强制性锁,或者关闭已加锁文件的组执行权限并且打开该文件的set-GID权限位。

建议性锁只在cooperating processes之间才有用,对cooperating process的理解是最重要的,它指的是会影响其它进程的进程或被别的进程所影响的进程,举两个例子:(1)我们可以同时在两个窗口中运行同一个命令, 对同一个文件进行操作,那么这两个进程就是cooperatingprocesses;(2)cat file| sort,那么cat和sort产生的进程就是使用了pipe的cooperating processes。

使用fcntl文件锁进行I/O操作必须小心:进程在开始任何I/O操作前如何去处理锁,在对文件解锁前如何完成所有的操作,是必须考虑的。如果在设置锁 之前打开文件,或者读取该锁之后关闭文件,另一个进程就可能在上锁/解锁操作和打开/关闭操作之间的几分之一秒内访问该文件。当一个进程对文件加锁后,无论它是否释放所加的锁,只要文件关闭,内核都会自动释放加在文件上的建议性锁(这也是建议性锁和强制性锁的最大区别),所以不要想设置建议性锁来达到永久不让别的进程访问文件的目的(强制性锁才可以);强制性锁则对所有进程起作用。

fcntl常用的有三个参数F_SETLK /F_SETLKW,F_UNLCK和F_GETLK,来分别要求、释放、测试record locks,record locks是对文件一部分而不是整个文件的锁,这种细致的控制使得进程更好地协作以共享文件资源。fcntl能够用于读取锁和写入锁,read lock也叫shared lock(共享锁),因为多个cooperating process能够在文件的同一部分建立读取锁;writelock被称为exclusive lock(排斥锁),因为任何时刻只能有一个cooperating process在文件的某部分上建立写入锁。如果cooperatingprocesses对文件进行操作,那么它们可以同时对文件加read lock,在一个cooperating process加write lock之前,必须释放别的cooperating process加在该文件的read lock和wrtie lock,也就是说,对于文件只能有一个write lock存在,read lock和wrtie lock不能共存。

 

一、 flock用法详解

在多个进程同时操作同一份文件的过程中,很容易导致文件中的数据混乱,需要锁操作来保证数据的完整性,这里介绍的针对文件的锁,称之为“文件锁”-flock。

flock, 建议性锁,不具备强制性。一个进程使用flock将文件锁住,另一个进程可以直接操作正在被锁的文件,修改文件中的数据,原因在于flock只是用于检测文件是否被加锁,针对文件已经被加锁,另一个进程写入数据的情况,内核不会阻止这个进程的写入操作,也就是建议性锁的内核处理策略。

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

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

函数说明  flock()会依参数operation所指定的方式对参数fd所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文件的某一区域。为了更好的移植性,对于文件的打开与关闭可以选择fopen和fclose的组合,但flock的第一个参数要求的是int类型的文件描述符。这里对fopen返回的FILE类型的文件指针进行转换,转换为int型的文件描述符 (假设open函数返回的文件描述符为fd,而fopen返回的文件指针为*fp,则fd等价于fp->_fileno或fileno(fd)).

  参数  operation有下列四种情况:

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

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

  LOCK_UN 解除文件锁定状态。

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

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

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

说明:

进程使用flock尝试锁文件时,如果文件已经被其他进程锁住,进程会被阻塞直到锁被释放掉,或者在调用flock的时候,采用LOCK_NB参数,在尝试锁住 该文件的时候,发现已经被其他服务锁住,会返回错误,errno错误码为EWOULDBLOCK。即提供两种工作模式:阻塞与非阻塞类型。

服务会阻塞等待直到锁被释放:

flock(lockfd,LOCK_EX)

服务会返回错误发现文件已经被锁住时:

ret  = flock(lockfd,LOCK_EX|LOCK_NB)

同时ret = -1, errno = EWOULDBLOCK

flock锁的释放非常具有特色,即可调用LOCK_UN参数来释放文件锁,也可以通过关闭fd的方式来释放文件锁(flock的第一个参数是fd),意味着flock会随着进程的关闭而被自动释放掉。

flock只要在打开文件后,需要对文件读写之前flock一下就可以了,用完之后再flock一下,前面加锁,后面解锁。示例问题描述如下:

一个进程去打开文件,输入一个整数,然后上一把写锁(LOCK_EX),再输入一个整数将解锁(LOCK_UN),另一个进程打开同样一个文件,直接向文件中写数据,发现锁不起作用,能正常写入(我此时用的是超级用户)。google了一大圈发现flock不提供锁检查,也就是说在用flock之前 需要用户自己去检查一下是否已经上了锁,说明白点就是读写文件之前用一下flock检查一下文件有没有上锁,如果上锁了flock将会阻塞在那里(An attempt to lock the fileusing one of these file descriptors may be denied by a lock that the callingprocess has already placed via another descriptor ),除非用了LOCK_NB。

    注意:关闭文件后,锁将失效!

例一:非阻塞

//lockfile.c

#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <errno.h>int main(){    int fd,i;    char path[]="/home/taoyong/test.txt";    extern int errno;    fd=open(path,O_WRONLY|O_CREAT);    if(fd!=-1)        {        printf("open file %s ./n",path);        printf("please input a numberto lock the file./n");        scanf("%d",&i);        if(flock(fd,LOCK_EX)==0)            {           printf("the file was locked./n");            }        else            {           printf("the file was not locked./n");            }        printf("please input a numberto unlock the file./n");        scanf("%d",&i);        if(flock(fd,LOCK_UN)==0)            {           printf("the file was unlocked./n");            }        else            {           printf("the file was not unlocked./n");            }        close(fd);        }    else        {        printf("cannot open file%s/n",path);       printf("errno:%d/n",errno);       printf("errMsg:%s",strerror(errno));        }}

//testprocess.c

#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <errno.h>#include <sys/file.h>int main(){    int fd,i;    char path[]="/home/taoyong/test.txt";    char s[]="writing.../nwriting....../n";    extern int errno;    fd=open(path,O_WRONLY|O_CREAT|O_APPEND);    if(fd!=-1)            {        printf("open file %s./n",path);           if(flock(fd,LOCK_EX|LOCK_NB)==0)            {           printf("the file was locked by the process./n");                   if(-1!=write(fd,s,sizeof(s)))                   {               printf("write %s to the file %s/n",s,path);                       }               else                      {               printf("cannot write the file %s/n",path);               printf("errno:%d/n",errno);               printf("errMsg:%s/n",strerror(errno));                   }                                    }        else            {           printf("the file was locked by other process.Can't write.../n");               printf("errno:%d:",errno);            }                close(fd);            }        else           {        printf("cannot open file%s/n",path);       printf("errno:%d/n",errno);        printf("errMsg:%s",strerror(errno));            }}


例二、阻塞式

//File1.c

#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <errno.h>#include <sys/file.h>int main(void){    FILE *fp = NULL;    int i = 20;    if ((fp =fopen("./file_lock.test", "r+b")) == NULL) //打开文件       printf("file open error!\n");    if(flock(fp->_fileno, LOCK_EX) != 0) //给该文件加锁       printf("file lock by others\n");    while(1) //进入循环,加锁时间为20秒,打印倒计时    {          printf("%d\n", i--);       sleep(1);        if (i== 0)           break;    }       fclose(fp); //20秒后退出,关闭文件   flock(fp->_fileno, LOCK_UN); //文件解锁    return 0; }

 //File2.c

#include<stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <errno.h>#include <sys/file.h>int main(void){    FILE *fp = NULL;    int i = 0;        if ((fp =fopen("./file_lock.test", "r+b")) == NULL) //打开文件       printf("file open error!\n");    flock(fp->_fileno,LOCK_EX); //文件加锁    while(1) //进入循环    {          printf("%d\n", i++);       sleep(1);    }       fclose(fp); //关闭文件   flock(fp->_fileno, LOCK_UN); //释放文件锁    return 0; }

首先运行file1.c,紧接着运行file2.c(运行file1.c后20秒内要运行file2.c否则看不到现象)

现象是:file1.c执行起来以后,开始倒计时。此时运行file2.c会阻塞在加锁处。当file1.c运行20秒后关闭文件,并释放文件锁后,file2.c会开始运行。

 

例三、flock其中的一个使用场景为:检测进程是否已经存在;

 

int checkexit(char* pfile){     if (pfile == NULL)          return -1;     int lockfd = open(pfile,O_RDWR);     if (lockfd == -1)          return -2;     int iret =flock(lockfd,LOCK_EX|LOCK_NB)     if (iret == -1)          return -3;     return 0;}


 

二、 fcntl 详细说明

功能描述:根据文件描述词来操作文件的特性。 

文件控制函数
         fcntl -- file control


头文件:

        #include <fcntl.h>; 
         int fcntl(int fd, intcmd); 
         int fcntl(int fd, int cmd,long arg); 
         int fcntl(int fd, int cmd,struct flock *lock); 

[描述]
            Fcntl()
针对(文件)描述符提供控制.参数fd是被参数cmd操作(如下面的描述)的描述符.
            针对cmd的值,fcntl能够接受第三个参数intarg

fcntl函数有5种功能: 
1.复制一个现有的描述符(cmd=F_DUPFD). 
2.获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD). 
3.获得/设置文件状态标记(cmd=F_GETFL或F_SETFL). 
4.获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN). 
5.获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

    cmd值: 
           F_DUPFD            
返回一个如下描述的(文件)描述符:
                           o            最小的大于或等于arg的一个可用的描述符
                           o            与原始操作符一样的某对象的引用
                           o            如果对象是文件(file)的话,返回一个新的描述符,这个描述符与arg共享相同的偏移量(offset)
                           o            相同的访问模式(读,写或读/写)
                           o            相同的文件状态标志(如:两个文件描述符共享相同的状态标志)
                           o            与新的文件描述符结合在一起的close-on-exec标志被设置成交叉式访问execve(2)的系统调用
     
    
           F_GETFD            取得与文件描述符fd联合close-on-exec标志,类似FD_CLOEXEC.如果返回值和FD_CLOEXEC进行与运算结果是0的话,文件保持交叉式访问exec(),否则如果通过exec运行的话,文件将被关闭(arg被忽略)
     
    
           F_SETFD            设置close-on-exec旗标。该旗标以参数arg的FD_CLOEXEC位决定。     
   
            F_GETFL           取得fd的文件状态标志,如同下面的描述一样(arg被忽略)
     
   
           F_SETFL            设置给arg描述符状态标志,可以更改的几个标志是: O_APPEND, O_NONBLOCK,O_SYNC和O_ASYNC。

     
     
           F_GETOWN            取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回成负值(arg被忽略)
     
   
           F_SETOWN            设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明,否则,arg将被认为是进程id

   
            命令字(cmd)F_GETFL和F_SETFL的标志如下面的描述:
           O_NONBLOCK            非阻塞I/O;如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,read或write调用返回-1和EAGAIN错误
           O_APPEND                   强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志
           O_DIRECT                   最小化或去掉reading和writing的缓存影响.系统将企图避免缓存你的读或写的数据.如果不能够避免缓存,那么它将最小化已经被缓存了的数据造成的影响.如果这个标志用的不够好,将大大的降低性能
           O_ASYNC                   当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候

在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值,然后按照希望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。

fcntl的返回值 与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD,F_GETFD,F_GETFL以及F_GETOWN。第一个返回新
的文件描述符,第二个返回相应标志,最后一个返回一个正的进程ID或负的进程组ID。

控制fd的例程 如下:

#include 
#include 
#include 
#include 
using namespace std; 

int main(int argc,char* argv[]) 

int fd,var; 
// fd=open("new",O_RDWR); 

if (argc!=2) 

perror("--"); 
cout<<"请输入参数,即文件名!"<


/**
三个存取方式标志(O_RDONLY,O_WRONLY,以及O_RDWR)并不各占1位。(这三种标志的值各是0、1和2,由于历史原因。这三种值互斥—一个文件只能有这三种值之一。)因此首先必须用屏蔽字O_ACCMODE取得存取方式位,然后将结果与这三种值相比较。

****/

switch(var &O_ACCMODE) 

case O_RDONLY : cout<<"Read only.."<

.获得/设置记录锁的功能: (cmd=F_GETLK,F_SETLK或F_SETLKW).

         F_GETLK          通过第三个参数arg(一个指向flock的结构体)取得第一个阻塞lockdescription指向的的锁.取得的信息将覆盖传到fcntl()的flock结构的信息.如果没有发现能够阻止本次锁(flock)生成的锁,这个结构将不被改变,除非锁的类型被设置成F_UNLCK.

     
         F_SETLK          按照指向结构体flock的指针的第三个参数arg所描述的锁的信息设置或者清除一个文件segment锁.F_SETLK被用来实现共享(或读)锁 (F_RDLCK)或独占(写)锁(F_WRLCK),同样可以去掉这两种锁(F_UNLCK).如果共享锁或独占锁不能被设置,fcntl()将立即返回EAGAIN.

     
         F_SETLKW          除了共享锁或独占锁被其他的锁阻塞这种情况外,这个命令和F_SETLK是一样的.如果共享锁或独占锁被其他的锁阻塞,进程将等待直到这个请求能够完成.当fcntl()正在等待文件的某个区域的时候捕捉到一个信号,如果这个信号没有被指定SA_RESTART,fcntl将被中断.

     
          当一个共享锁被set到一个文件的某段的时候,其他的进程可以set共享锁到这个段或这个段的一部分.共享所阻止任何其他进程set独占锁到这段保护区域的任何部分.如果文件描述符没有以读的访问方式打开的话,共享锁的设置请求会失败

    
          独占锁阻止任何其他的进程在这段保护区域任何位置设置共享锁或独占锁.如果文件描述符不是以写的访问方式打开的话,独占锁的请求会失败

结构体flock的指针 

struct flcok 

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

//这三个参数用于分段对文件加锁,若对整个文件加锁,则:l_whence=SEEK_SET,l_start=0,l_len=0;
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 以文件结尾为锁定的起始位置。 

 

对文件锁部分的相关说明及使用方法:

当写文件的时候首先给相应的文件加锁:

1.定义flock结构体

2.加锁指定cmd为: F_SETLK 

3.写数据

4.解锁指定cmd为:F_SETLKW 

代码如下:

#include<unistd.h>#include<fcntl.h>#include<errno.h>#include<stdio.h>#include<stdlib.h>#include<sys/types.h>int fcntlfile(int fd, int cmd, int type, off_t offset, intwhence, off_t len){        struct flock lock;        int n=0;       lock.l_type=type;        lock.l_start=offset;        lock.l_whence=whence;        lock.l_len=len;        if((n=fcntl(fd, cmd, &lock))<0)        {         printf("fcntlerror");               return  -1;        }        return n;}int main (int argc, char **argv){        int fd =open("/home/xmail/ljl/file1",O_WRONLY);        fcntlfile(fd, F_SETLK, F_WRLCK,0, SEEK_SET, 0);//设置为写独占,如果文件不是以写open的则写独占锁设置无效       puts("please input anny key to stop ");        char ch=getchar();        fcntlfile(fd, F_SETLK, F_UNLCK, 0,SEEK_SET, 0);        close(fd);}

当有其他线程写同一文件的时候: 

1.判断文件是否已经被加锁指定cmd为:F_GETLK,获取flock结构体,查看flock.l_type; /* 锁定的状态*/

2.判断是否加锁,加锁就提示,没加锁继续....

2.加锁指定cmd为: F_SETLK 

3.写数据

4.解锁指定cmd为:F_SETLKW 

代码如下:

#include<unistd.h>#include<fcntl.h>#include<errno.h>#include<stdio.h>#include<stdlib.h>#include<sys/types.h>int main(int argc, char**argv){        intfd=open("/home/xmail/ljl/file1", O_WRONLY);        struct flock lock;       //必须给lock赋初值,否组F_GETLK时报错。        lock.l_type=F_WRLCK;//--------------①        lock.l_start=0;        lock.l_whence=SEEK_SET;        lock.l_len=0;        int n=fcntl(fd, F_GETLK, &lock);//获取上一个线程的独占锁信息       if(n<0)        {        printf("fcntl error");        return -1;        }        if(lock.l_type==F_WRLCK)//判断上一个线程的锁信息是否为写独占,这里有个问题就是在①处设置的是写独占,我怎么判断我获取的lock结构体是上个线程的锁的信息,这个不用担心,因为当没有获取到上个线程的lock时,这里的lock.l_type也不是F_WRLCK了,我也不知道为什么,是通过单独执行这个文件的时候,结果跳过了②这个语句得出的结论。        {        puts("allreadylocked");//------------②        }        else{               //lock结构体的初值已经改变,这里必须重新赋值               lock2.l_type=F_WRLCK;               lock2.l_start=0;               lock2.l_whence=SEEK_SET;               lock2.l_len=0;               n=fcntl(fd,F_SETLK,&lock2);                     if((n=fcntl(fd, F_SETLK, &lock))<0)                     {                    printf("fcntl error");                       return  -1;                     }               for(int i=0;i<100;i++)                       {                       int ret =write(fd, "aaaaaaaaaa\n", 11);                       if(ret==-1)                       {                               printf("write file error");                       }                       }               fcntlfile(fd, F_SETLK, F_UNLCK, 0, SEEK_SET, 0);               close(fd);        }}


 

 

三、 flock 和 fcntl 的区别

 

flock和fcntl都有锁的功能,但他们还有一点小小的区别:

1.flock只能加全局锁,fcntl可以加全局锁也可以加局部锁。

2.当一个进程用flock给一个文件加锁时,用另一个进程再给这个文件加锁,它会阻塞或者也可以返回加锁失败(可以自己设置)。

3.当一个进程用fcntl给一个文件加锁时,用另一个进程去读或写文件时必须先获取加锁的信息,然后在给这个文件加锁。

3.当给一个文件加fcntl的独占锁后,再给这个文件加flock的独占锁,其会进入阻塞状态。

4.当给一个文件加flock的独占锁后,用fcntl去获取这个锁信息获取不到,再用fcntl仍然可以给文件加锁。

0 0
原创粉丝点击