flock使用体会

本文最后更新于:2025年11月19日 下午

简介

它的API很简单,如下

1
2
3
#include <sys/file.h>

int flock(int fd, int operation);

operation可指定:

  • LOCK_SH:设置共享(读)锁
  • LOCK_EX:设置独占(写)锁
  • LOCK_UN:解锁

它是建议锁。需要所有使用该文件的人都遵循规矩先flock上锁再使用文件才有效。

它是一个系统调用,在内核fs/locks.c中实现。

实现本质

每个打开的文件都对应有fd—file结构—inode结构三种数据结构。fd和file结构是进程自己独有的,inode是文件系统层面,所有进程共享。
flock在inode上记录了加锁的file结构信息,组成一个链表。通过比较file结构,判断是否需要锁定。
所以它的特性很多和文件相关。关键函数如下:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
static int flock_lock_inode(struct inode *inode, struct file_lock *request)
{
struct file_lock *new_fl = NULL;
struct file_lock *fl;
struct file_lock_context *ctx;
int error = 0;
bool found = false;
LIST_HEAD(dispose);

// 获取inode上的lock信息
ctx = locks_get_lock_context(inode, request->fl_type);
if (!ctx) {
if (request->fl_type != F_UNLCK)
return -ENOMEM;
return (request->fl_flags & FL_EXISTS) ? -ENOENT : 0;
}

if (!(request->fl_flags & FL_ACCESS) && (request->fl_type != F_UNLCK)) {
new_fl = locks_alloc_lock();
if (!new_fl)
return -ENOMEM;
}

percpu_down_read_preempt_disable(&file_rwsem);
spin_lock(&ctx->flc_lock);
if (request->fl_flags & FL_ACCESS)
goto find_conflict;

// 遍历inode上的锁
list_for_each_entry(fl, &ctx->flc_flock, fl_list) {
// file指针比较
if (request->fl_file != fl->fl_file)
continue;
// 锁类型(LOCK_EX,LOCK_SH)比较
if (request->fl_type == fl->fl_type)
goto out;
found = true;
// file结构上找到锁,但是类型不同,直接删除老的
locks_delete_lock_ctx(fl, &dispose);
break;
}

if (request->fl_type == F_UNLCK) {
if ((request->fl_flags & FL_EXISTS) && !found)
error = -ENOENT;
goto out;
}

//file结构上没找到锁也会走这儿
find_conflict:
list_for_each_entry(fl, &ctx->flc_flock, fl_list) {
if (!flock_locks_conflict(request, fl))
continue;
error = -EAGAIN;
if (!(request->fl_flags & FL_SLEEP))
goto out;
// 返回这个就会上锁
error = FILE_LOCK_DEFERRED;
locks_insert_block(fl, request);
goto out;
}
if (request->fl_flags & FL_ACCESS)
goto out;
locks_copy_lock(new_fl, request);
locks_insert_lock_ctx(new_fl, &ctx->flc_flock);
new_fl = NULL;
error = 0;

out:
spin_unlock(&ctx->flc_lock);
percpu_up_read_preempt_enable(&file_rwsem);
if (new_fl)
locks_free_lock(new_fl);
locks_dispose_list(&dispose);
trace_flock_lock_inode(inode, request, error);
return error;
}

注意事项

从上面的原理可知:

  • fork,dup文件描述符后,fd对应的file结构不变,锁定关系不变。
  • 单个进程可使用一个fd多次重复加锁(内核实际只加锁一次),所以只需要解锁一次。
  • 单个进程对同一文件open两个fd(file结构不同),在这两个fd上依次加锁会死锁
  • 关闭文件fd,在该fd上加的锁自动释放

flock命令

有时一个命令或者脚本需要执行很久,有可能多个同时运行,我们需要串行的执行他们,避免出现问题。可以这样

1
flock /tmp/xxxx.lock your_command

比如:

1
flock /tmp/hexo_post.lock hexo g -d

flock使用体会
https://leon0625.github.io/2023/12/29/987d260b4c15/
作者
leon.liu
发布于
2023年12月29日
许可协议