简介

锁定文件的两个接口:

  • flock(), 用来锁定整个文件
  • fcntl(), 用来锁定文件的区域

基本用法是:

  • 给文件加锁
  • 一般的文件读写操作
  • 释放之前的锁

flock()

文件描述符关闭之后, 这个文件锁自动被释放.

flock() 加的锁, 是放在了内核内部的 file description结构体里的, 而不是文件描述符 (file descriptor) 里. 这样一来, 如果使用 dup2(), fcntl() 等方式复制了文件描述符 的话, 这两个文件描述符都指写同一个加了锁的 file description. 在使用 fork() 创建 子进程时, 要特别注意在子进程中应该把锁释放掉.

flock() 有以下不足:

  • 只能锁定整个文件
  • 只是建议锁, 不是强制锁, 即使进程对一个文件加锁失败了, 它仍可以正常去读写.
  • NFS 文件系统对它的支持有限.

fcntl()

可以对整个文件或者部分区域加锁. 基本的调用方式是:

struct flock flockstr;
fcntl(fd, cmd, &flockstr);

以上方法通过修改 flock 结构体来完成加锁和解锁的操作. 该结构体的定义是:

struct flock {
  // 锁类型, F_RDLCK, F_WRLCK, F_UNLCK.
  short l_type;
  // 如何解析 l_start, 可以是 Seek_SET, SEEK_CUR 以及 SEEK_END.
  short l_whence;
  // 文件锁的起点.
  off_t l_start;
  // 要锁定的长度, 如果是0, 表示"到文件结尾.
  off_t l_len;
  // 哪个进程在阻止我们加锁.
  pid_t l_pid;

内核会自动检查两个进程使用 fcntl() 导致的死锁问题, 通过让其中的一个进程加锁失败.

使用文件锁来实现单实例进程.

// Copyright (c) 2018 Xu Shaohua <xushaohua2016@outlook.com>.
// All rights reserved.
// Use of this source is governed by General Public License that can be found
// in the LICENSE file.

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

long createPidFile(const char* pid_file) {
  // Add O_CLOEXEC to release lock in child process after fork().
  int fd = open(pid_file, O_RDWR | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
  if (fd == -1) {
    perror("open()");
    return -1;
  }

  struct flock flockstr;
  flockstr.l_type = F_WRLCK;
  flockstr.l_whence = SEEK_SET;
  flockstr.l_start = 0;
  flockstr.l_len = 0;
  if (fcntl(fd, F_SETLK, &flockstr) == -1) {
    perror("fcntl(F_SETLK)");
    // Get process id by which this pid file is locked.
    if (fcntl(fd, F_GETLK, &flockstr)) {
      perror("fcntl(F_GETLK)");
    }
    return flockstr.l_pid;
  }
  return 0;
}

int main(int argc, char* argv[]) {
  const size_t kBufSize = 255;
  char buf[kBufSize];
  if (snprintf(buf, kBufSize, "/tmp/%s-%d.pid", argv[0], getuid()) == -1) {
    perror("snprintf()");
    exit(EXIT_FAILURE);
  }
  const long ret = createPidFile(buf);
  if (ret == -1) {
    printf("Failed to create pid file");
    exit(EXIT_FAILURE);
  } else if (ret == 0) {
    printf("Create pid file successfully, pid: %ld\n", (long) getpid());
  } else {
    printf("Progress already running with pid: %ld\n", ret);
    exit(EXIT_FAILURE);
  }

  sleep(30);
  printf("Release lock and exit process\n");

  // No need to release write lock, as it will be released by kernel.

  exit(EXIT_SUCCESS);
}