文件的加锁

今天在讲linux下文件的加锁之前,我要先说一说文件为什么要加锁,因为我相信很多人对文件的锁机制都是很迷惑的。

文件为什么要加锁

因为linux是一个多进用户多任务的操作系统,假如我们对一个文件进行写操作的时候,我们开了好几个进程对这个文件同时进行写操作,这时可能就会发生文件数据的不同步,而且像一些文件,比如数据库,我们必须保证每次进程只能单独的对这个文件进行写操作,为了提供这种机制,linux提供了记录锁机制。

文件的锁的种类

  • 读锁
    如果给一个文件上了读锁,那么这个文件是其他进程只能读不能写的,多个进程在一个字节上是可以有一把共享的读锁的,也就是说,一个文件是可以加多把的读锁
  • 写锁
    如果给一个文件上了写锁,那么同时其他的进程无法读也无法写的,一个字节上只能有一把写锁,不能再加任何其他的锁,这是由于写锁的独占性,但是一个文件可以被分为多个不同的区域,锁机制只能在自己的区域内起作用,也就是说如果把一个文件分为A,B两个部分,那么也是可以加两个写锁或一个写锁一个读锁的

锁的不兼容性

如果一个文件的某一区域上已经有了一把或多把读锁,那么是不能继续加写锁的,同样,如果已经有了一把写锁,那么也不能在继续加读锁,如果某一文件区域已经存在锁机制了,如果还加锁的话,那么旧的锁会被新的锁取代

锁的数据结构

struct flock
{
short_l_type 锁的类型
short_l_whence 锁的起始位置:SEEK_CUR...
off_t_l_start 锁的开始位置
off_t_l_len 锁的偏移量
pid_t_l_pid 锁的属主进程ID
}
//一般为了锁住整个文件,会将whence设置为SEEK_SET,start,len都设置为0

怎么设置一个锁

fcntl函数可以用来给文件加锁,具体用法如下:
int fcntl(int fd, int cmd, struct flock *lock)
第二个参数可以设置的宏:
F_SETLK:用来设置锁,当l_type被设置为F_RDLCK时为读锁,设置为F_WRLCK为写锁,为F_UNLCK时为释放锁。如果锁被其他进程占用,则返回-1。需要注意的是,当设置为读锁时,文件必须以可读当时打开,当设置写锁时,文件必须以可写方式打开,当设置两种所时,文件以可读可写方式打开。

F_SETLKW:用法和F_SETLK用法类似,不同的是当希望设置的锁因为其他锁而存在时被阻止时,该命令会等待相冲突的锁被释放。

F_GETLK:此时第三个参数指向一个希望的设置的锁的类型,如果锁能够被设置,该命令并不真的设置锁,而将lock的l_type的域设置为F_UNLCK,然后返回。如果存在一个或多个锁和要设置的锁的类型冲突,那么它会返回其中任意一种锁的结构。

函数调用成功时返回0,失败时返回-1。单个进程在同一字节上只能设置一种锁,新的锁会替代旧的锁,锁的不兼容规则是针对多个进程的。

附源码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>

void my_err(char *err_string,int line) // 测试错误函数
{
    fprintf(stderr,"line:%d",line);
    perror(err_string);
    exit(1);
}

int test(int fd,struct flock *lock)//测试锁
{
    if(fcntl(fd,F_GETLK,lock)==0)
    {
        if(lock->l_type==F_UNLCK)
        {
            printf("lock can be set in fd\n");

            return 0;
        }
        else
        {
            if(lock->l_type==F_RDLCK)
            {
                printf("can't be set lock,read lock has been set by:%d\n",lock->l_pid);
            }

            if(lock->l_type==F_WRLCK)
            {
                printf("can't be set lock,write lock has benn set by:%d\n",lock->l_pid);
            }

            return -2;
        }
    }
    else
    {
        perror("get incompatible locks fail");
        return -1;
    }
}

int lock_set(int fd,struct flock *lock) // 设置锁
{
    if(fcntl(fd,F_SETLK,lock)==0)
    {
        if(lock->l_type==F_RDLCK)
        {
            printf("set read lock,pid:%d\n",getpid());
        }
        if(lock->l_type==F_WRLCK)
        {
            printf("set write lock,pid:%d\n",getpid());
        }
        if(lock->l_type==F_UNLCK)
        {
            printf("release lock,pid:%d\n",getpid());
        }
    }
    else
    {
        perror("lock operation fail\n");
        return -1;
    }
}

int main()
{
    int fd;
    int ret;
    struct flock lock;
    char read_buf[32];

    if((fd=open("example-65",O_CREAT|O_TRUNC|O_RDWR,S_IRWXU))==-1)
    {
        my_err("open",__LINE__);
    }

    if(write(fd,"test lock",10)!=10)
    {
        my_err("write",__LINE__);
    }

    memset(&lock,0,sizeof(struct flock));//锁的初始化
    lock.l_start=SEEK_SET;
    lock.l_whence=0;
    lock.l_len=0;

    lock.l_type=F_RDLCK;
    if(test(fd,&lock)==0)
    {
        lock.l_type=F_RDLCK;
        lock_set(fd,&lock);
    }

    lseek(fd,0,SEEK_SET);
    if((ret=read(fd,read_buf,10))<0)
    {
        my_err("read",__LINE__);
    }
    read_buf[ret]='\0';
    printf("%s\n",read_buf);

    getchar();

    lock.l_type=F_WRLCK;
    if(test(fd,&lock)==0)
    {
        lock.l_type=F_WRLCK;
        lock_set(fd,&lock);
    }

    //释放锁
    lock.l_type=F_UNLCK;
    lock_set(fd,&lock);
    close(fd);

    return 0;
}

注:linux下的记录锁默认是建议性锁,而不是强制性锁

  • 所谓建议性锁就是假定人们都会遵守某些规则去干一件事。例如,人与车看到红灯都会停,而看到绿灯才会继续走,我们可以称红绿等为建议锁。但这只是一种规则而已,你并不防止某些人强闯红灯。而强制性锁是你想闯红灯也闯不了。

  • 建议性锁,就是一种软弱的锁——必须要在参与所有共享数据操作的所有进程之间都遵守这样神圣的约定的前提,建议性的锁才发挥作用,而劝告性的建议锁对于协作进程来说已经足够了!!但是,有读写权限的进程,不遵守这个约定,就会把这一切都搞杂,约定对于他们来说,不好用。但建议性锁也有他的优势:相对与强制性锁,性能好一些。个人理解。

  • 协作进程(cooperating processes):如果每个进程所使用的函数都以一致的方法处理记录锁,互斥锁等,则这些进程被称为协作进程。

你可能感兴趣的:(linux,操作系统)