Linux互斥与同步应用(六):文件锁

【版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途】

当一个系统中存在多个进程同时操作同一个文件时,为了保证数据的正确, 一般会将文件上锁来避免共享文件产生的竞争状态。在linux系统下文件上锁可以使用fcntl函数来实现。

函数fcntl原型如下:

       #include <unistd.h>
       #include <fcntl.h>
       int fcntl(int fd, int cmd, ... /* arg */ );

函数对所打开的文件描述符fd,根据不同的cmd命令执行不同的操作,针对文件锁的命令有如下:

F_GTELK, F_SETLK, F_SETLKW三种,分别为获取锁,设置锁,同步获取锁。该函数在使用文件锁时第三个参数为一个指向结构体struct 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) */
               ...
           };

成员l_whence、l_start、l_len指定了我们期望加锁的文件范围;

l_whence可取值:SEEK_SET、SEEK_CUR、SEEK_END,一般设为SEEK_SET。

l_start为文件加锁开始的偏移处,一般设置为0,为开始处。

l_len为指定加锁的字节数,如果为0则表示从开始处一直到文件末尾。

l_pid为持有锁的进程ID号。

l_type为要加锁的类型,可以取值F_RDLCK(读锁)、F_WRLCK(写锁)、F_UNLCK (释放锁)。

任何数量的进程都可以对同一个文件区域持有一个读锁,但只能有一个进程对其持有写锁(排它锁)。单个进程只能对同一文件区域持有一种锁,如果新的锁应用在同一片已经使用了所得文件区域,那么之前存在的锁被覆盖。

F_STELK在l_type为F_RDLCK或F_WRLCK时请求一个锁,在l_type为F_UNLCK是释放一个锁,如果此时有另外的进程已经提前和获取到锁时,该调用立即返回-1。

F_SETLKW和F_STELK类似,去别在于当之前存在锁时,该函数调用将阻塞直到锁释放为止。如果阻塞期被中断信号打断将立即返回。

F_GTELK针对指向struct
flock结构体类型锁去试图加锁,如果该锁可以加上(实际没有做加锁的操作),fcntl()函数将结构体中l_type置为F_UNLCK,其余成员保持不变。如果存在不兼容的锁阻止该种类型的锁加锁,fcntl将已经存在锁的详细信息写入结构体中,其中l_pid为持有该锁的进程ID。

如果想对文件区域加读锁,那么文件描述应当是以可读方式打开,类似地,如果将对文件夹写锁,文件应当是以可写的方式打开。

文件锁释放有三种途径,一种是显示的使用F_UNLCK类型加锁,第二种是进程终止,第三种是所有针对该文件打开的文件描述全部关闭。

文件锁不会被通过fork()产生的子进程继承。

下面是通过包裹fcntl函数实现的加锁、同步加锁、解锁、测试读写锁api:

/**
 * \brief     acquire, release or test for the existence of record locks
 * \details   if a conficling is held on the file, then return -1.
 *
 * \param     fd - file descriptor
 * \param     locktype - lock type: F_RDLCK, F_WRLCK, F_UNLCK.
 *
 * \return    0 is success, < 0 is failed.
 */
static int sln_filelock_set(int fd, short locktype)
{
    struct flock    lock;
    lock.l_type = locktype;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    lock.l_pid = getpid();
    if (fcntl(fd, F_SETLK, &lock) < 0) {
        fprintf(stderr, "fcntl: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}
/**
 * \brief     acquire, release or test for the existence of record locks
 * \details   if a conficling is held on the file, then wait for that
 *            lock to be release
 *
 * \param     fd - file descriptor
 * \param     locktype - lock type: F_RDLCK, F_WRLCK, F_UNLCK.
 *
 * \return    0 is success, < 0 is failed.
 */
int sln_filelock_wait_set(int fd, short locktype)
{
    struct flock    lock;
    lock.l_type = locktype;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    lock.l_pid = getpid();
    if (fcntl(fd, F_SETLKW, &lock) < 0) {
        fprintf(stderr, "fcntl: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}
int sln_file_wrlock(int fd)
{
    return sln_filelock_set(fd, F_WRLCK);
}
int sln_file_rdlock(int fd)
{
    return sln_filelock_set(fd, F_RDLCK);
}
int sln_file_wait_wrlock(int fd)
{
    return sln_filelock_wait_set(fd, F_WRLCK);
}
int sln_file_wait_rdlock(int fd)
{
    return sln_filelock_wait_set(fd, F_RDLCK);
}
int sln_file_unlock(int fd)
{
    return sln_filelock_set(fd, F_UNLCK);
}
/**
 * \brief     test for the existence of record locks on the file
 * \details   none
 *
 * \param     fd - file descriptor
 * \param     locktype - lock type: F_RDLCK, F_WRLCK.
 *
 * \return    0 is success, < 0 is failed.
 */
static int sln_filelock_test(int fd, short locktype)
{
    struct flock    lock;
    lock.l_type = locktype;
    lock.l_whence = 0;
    lock.l_start = 0;
    lock.l_len = 0;
    lock.l_pid = 0;
    if (fcntl(fd, F_GETLK, &lock) < 0) {
        fprintf(stderr, "fcntl: %s\n", strerror(errno));
        return -1;
    }
    if (lock.l_type != F_UNLCK) { //file is locked
        if (F_WRLCK == lock.l_type) {
            printf("write lock hold by process <%d>, lock_type: %d\n", lock.l_pid, lock.l_type);
        } else if (F_RDLCK == lock.l_type) {
            printf("read lock hold by process <%d>, lock_type: %d\n", lock.l_pid, lock.l_type);
        }
        return lock.l_pid; //return the pid of process holding that lock
    } else {
        printf("Unlock, lock type: %d\n", lock.l_type);
        return 0;
    }
}
int sln_file_wrlock_test(int fd)
{
    return sln_filelock_test(fd, F_WRLCK);
}
int sln_file_rdlock_test(int fd)
{
    return sln_filelock_test(fd, F_RDLCK);
}

下面来实现三个进程,三个进程针对同一个文件进行读、写、测试锁操作:

写进程:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "filelock.h"
int main(int argc, const char *argv[])
{
    int             fd;
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <write_str>\n", argv[0]);
        return -1;
    }
    fd = open("filelock.txt", O_RDWR | O_CREAT, 0644);
    if (fd < 0) {
        fprintf(stderr, "open: %s\n", strerror(errno));
        return -1;
    }
    if (sln_file_wait_wrlock(fd) < 0) {
        printf("lock write failed!\n");
        close(fd);
        return -1;
    }
    printf("process <%d> holding write lock ok!\n", getpid());
    write(fd, argv[1], strlen(argv[1]));
    sleep(10);
    sln_file_unlock(fd);
    printf("release lock!\n");
    close(fd);
    return 0;
}

读进程:

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

#include "filelock.h"

int main(int argc, const char *argv[])
{
    int             fd;
    char            buf[1024];

    fd = open("filelock.txt", O_RDONLY | O_CREAT, 0644);
    if (fd < 0) {
        fprintf(stderr, "open: %s\n", strerror(errno));
        return -1;
    }

    if (sln_file_wait_rdlock(fd) < 0) {
        printf("lock read failed!\n");
        close(fd);
        return -1;
    }
    printf("process <%d> holding read lock ok!\n", getpid());
    sleep(10);
    read(fd, buf, sizeof(buf));
    printf("read buf: %s\n", buf);
    sln_file_unlock(fd);
    printf("release lock!\n");

    close(fd);
    return 0;
}

测试读写锁进程:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "filelock.h"
int main(int argc, const char *argv[])
{
    int             fd, pid;
    fd = open("filelock.txt", O_RDWR | O_CREAT, 0644);
    if (fd < 0) {
        fprintf(stderr, "open: %s\n", strerror(errno));
        return -1;
    }
    pid = sln_file_wrlock_test(fd);
    if (pid > 0) {
        printf("write locked!\n");
    } else {
        printf("write unlock!\n");
    }
    pid = sln_file_rdlock_test(fd);
    if (pid > 0) {
        printf("read locked!\n");
    } else {
        printf("read unlock!\n");
    }
    close(fd);
    return 0;
}

编译运行三个程序。

为了测试效果,在进程读和写sleep 10秒钟。

1. 在写锁存在是,读锁是无法持有的。测试:先运行写进程,在运行读进程,读进程将在写进程释放锁时获得锁。

2. 在读锁存在时,写锁是无法持有的。测试:先运行读进程,再运行写进程,写进程将在读进程释放锁时获得锁。

3. 在读锁存在时,另外的读锁可以继续持有。测试:先后运行两个读进程,后运行的读进程可以立即获得读锁。

4.上面的测试也可可以通过测试读写锁进程得到结果。

源码下载:

点击打开链接

时间: 2024-10-25 08:20:40

Linux互斥与同步应用(六):文件锁的相关文章

Linux互斥与同步应用(四):posix信号量的互斥与同步

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途] 在前面讲共享内存的IPC时曾说共享内存本身不具备同步机制,如果要实现同步需要使用信号量等手段来实现之,现在我们就来说说使用posix的信号量来实现posix多进程共享内存的同步.其实信号量也可以使用在同一进程的不同线程之间. 信号量是一个包含非负整数的变量,有两个原子操作,wait和post,也可以说是P操作和V操作,P操作将信号量减一,V操作

Linux互斥与同步应用(一):posix线程及线程间互斥

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途] 有了进程的概念,为何还要使用线程呢? 首先,回忆一下上一个系列我们讲到的IPC,各个进程之间具有独立的内存空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便.而同一个进程下的线程是共享全局内存的,所以一个线程的数据可以在另一个线程中直接使用,及快捷又方便. 其次,在Linux系统下,启动一个新的进程必须分配给它独立的地

Linux互斥与同步应用(二):posix线程同步

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途] 上一节说到线程的互斥锁,互斥锁适合防止访问某个共享变量,这一节我们来看看两个线程如何实现同步.互斥锁也可以实现线程同步,当该值满足某种条件时当前线程继续执行,否则继续轮询,不过这样相当浪费cpu时间.我们需要的是让某个线程进入睡眠,当满足该线程执行条件时,另外线程主动通知它,这就是这一节我们要说的条件变量,它常和互斥锁一起使用. 如同信号量,线

Linux互斥与同步应用(三):posix线程实现单个生产者和单个消费者模型

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途] 在第一节说到了生产者消费者问题,这一节我们来实现这样一个稍作修改的模型: 初始时缓冲区为空,生产者向缓冲区写入数据,消费者在缓冲区为空的情况下睡眠,当生产者写满缓冲区一半之后后通知消费者可以开始消费,生产者开始睡眠,直到消费者消费完缓冲区一半后通知生产者可以开始生产为止,其中生产者和消费者对缓冲区的访问时互斥的. 下面来看一下实现: #incl

Linux互斥与同步应用(五):system V信号量的互斥与同步

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途] system V信号量操作类似于posix信号量,但system V信号量的操作要复杂得多,posix信号量使用步骤为sem_init(sem_open)-->sem_wait(sem_post) --> sem_close详见上一节,system V使用不同的函数. 1. 创建和打开信号量函数:semget(). #include <

Linux 内核的同步机制,第 1 部分 + 第二部分(转)

http://blog.csdn.net/jk198310/article/details/9264721  原文地址: Linux 内核的同步机制,第 1 部分 一. 引言 在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问.尤其是在多处理器系统上,更需要一些同步机制来同步不同处理器上的执行单元对共享的数据的访问.在主流的Linux内核中包含了几乎所有现代的操作系统具有的同步机制,这些同步机制包括:原子操作

Linux多线程与同步

Linux多线程与同步 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 典型的UNIX系统都支持一个进程创建多个线程(thread).在Linux进程基础中提到,Linux以进程为单位组织操作,Linux中的线程也都基于进程.尽管实现方式有异于其它的UNIX系统,但Linux的多线程在逻辑和使用上与真正的多线程并没有差别. 多线程 我们先来看一下什么是多线程.在Linux从程序到进程中,我们看到了一个程序在内存中的表示.这个程

linux 线程的同步 三 (信号量的使用)

信号量.同步这些名词在进程间通信时就已经说过,在这里它们的意思是相同的,只不过是同步的对象不同而已.但是下面介绍的信号量的接口是用于线程的信号量,注意不要跟用于进程间通信的信号量混淆,关于用于进程间通信的信号量的详细介绍可以参阅我的另一篇博文:Linux进程间通信——使用信号量.相似地,线程同步是控制线程执行和访问临界区域的方法. 一.什么是信号量 线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作.如果一个程序中有多个线

Linux并发与同步专题 (2)spinlock

关键词:wfe.FIFO ticket-based.spin_lock/spin_trylock/spin_unlock.spin_lock_irq/spin_lock_bh/spin_lock_irqsave. <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Linux并发与同步专题 (3) 信号量> <Linux并发与同步专题 (4) Mutex互斥量> <Linux并发与同步专题 (