file lock

作者: 酱油王0901 | 来源:发表于2020-04-18 11:09 被阅读0次

Note: 文中部分内容截取自 File locking in Linux


Table of Contents

Prior knowledge

在了解文件锁之前先简单介绍一下File相关的知识。

在C程序中Files由文件指针(file pointers)或文件描述符(file descriptors)来指定。ISO C的标准I/O库函数( stdio.h中的fopen, fscanf, fprintf, fread, fwrite, fclose等等)用的是文件指针;UNIX 中的I/O函数(unistd.h中的open, read, write, close和ioctl)使用的文件描述符。文件指针和文件描述符提供了用于执行独立于设备的输入输出逻辑标识(logical designations),成为句柄(handle)。其中代表标准输入,标准输出和标准错误的文件指针的符号名(symbolic names)分别为stdin, stdout, stderr。而代表标准输入,标准输出和标准错误的文件描述符的符号名(symbolic names)分别为STDIN_FILENO, STDOUT_FILENO, STDERR_FILENOE

head file standard file representation 标准输入 标准输出 标准错误
stdio.h ISO I/O file pointers stdin stdout stderr
unistd.h UNIX I/O file descriptors STDIN_FILENO STDOUT_FILENO STDERR_FILENOE

File descriptors

open函数将文件或物理设备与程序中使用的逻辑句柄(logical handle)相关联。文件或物理设备通常由字符串来指定。句柄是一个整数,可以将其理解为指向进程特定的文件描述符表(file descriptor table)的索引(index)。进程中每个打开的文件,文件描述符表中都有其对应的条目(entry)。文件描述符表是进程用户区(the process of user area)的一部分。程序只能通过使用文件描述符的函数才能访问它。

例如我们调用open函数打开某文件时,会在文件描述符表中创建一个新的条目指向系统文件表(system file table)中的条目,

myfd = open("/home/my.data", O_RDONLY);

系统文件表(system file table)被系统中的所有进程所共享,每个活动的open都有条目。每个系统文件表条目包含文件偏移量(file offset),访问模式(access mode)的指示(即read, write或read-write),以及指向它的文件描述符表条目的计数。

几个系统文件表条目可能对应相同的物理文件,而这些条目指向内存中索引节点表(in-memeory inode table)的同一个条目。对系统中的每个活动文件,内存中索引节点表都包含对应的条目。

如图所示:



可以查看某个进程的文件描述符

(ENV) [root@ceph-2 ~]# ll /proc/`pgrep prometheus`/fd
total 0
lr-x------ 1 root root 64 Apr 18 11:30 0 -> pipe:[29992]
l-wx------ 1 root root 64 Apr 18 11:30 1 -> pipe:[29993]
lr-x------ 1 root root 64 Apr 18 11:30 10 -> /prometheus/01E4TPBR3PZW9N9Y4WKA94CQMG/chunks/000002
lr-x------ 1 root root 64 Apr 18 11:30 11 -> /prometheus/01E4TPBR3PZW9N9Y4WKA94CQMG/index
lr-x------ 1 root root 64 Apr 18 11:30 12 -> /prometheus/01E5X7WKKNAWVWJP8F675CPJHR/chunks/000001
lr-x------ 1 root root 64 Apr 18 11:30 13 -> /prometheus/01E5X7WKKNAWVWJP8F675CPJHR/chunks/000002
lr-x------ 1 root root 64 Apr 18 11:30 14 -> /prometheus/01E5X7WKKNAWVWJP8F675CPJHR/chunks/000003
lr-x------ 1 root root 64 Apr 18 11:30 15 -> /prometheus/01E5X7WKKNAWVWJP8F675CPJHR/index
lr-x------ 1 root root 64 Apr 18 11:30 16 -> /prometheus/01E5BVP5DV4F05VMP1WC4KSKNN/index
lrwx------ 1 root root 64 Apr 18 11:30 17 -> socket:[182080336]
l-wx------ 1 root root 64 Apr 18 11:30 18 -> /prometheus/wal/00000120
lrwx------ 1 root root 64 Apr 18 11:30 19 -> socket:[182083422]
l-wx------ 1 root root 64 Apr 18 11:30 2 -> pipe:[29994]
lrwx------ 1 root root 64 Apr 18 11:30 20 -> socket:[182084022]
lrwx------ 1 root root 64 Apr 18 11:30 21 -> socket:[182083309]
lrwx------ 1 root root 64 Apr 18 11:30 7 -> socket:[29208]
lr-x------ 1 root root 64 Apr 18 11:30 70 -> /prometheus/01E6318BH2W76TQC1EXZZ9VZYF/chunks/000001
lr-x------ 1 root root 64 Apr 18 11:30 71 -> /prometheus/01E6318BH2W76TQC1EXZZ9VZYF/index
lr-x------ 1 root root 64 Apr 18 17:11 72 -> /prometheus/01E66886MNWDWNVX98CP360ZXM/chunks/000001
lr-x------ 1 root root 64 Apr 18 17:11 73 -> /prometheus/01E66886MNWDWNVX98CP360ZXM/index
lr-x------ 1 root root 64 Apr 18 11:30 76 -> /prometheus/01E65KN1255K3VN6YHSK6A436Q/chunks/000001
lr-x------ 1 root root 64 Apr 18 11:30 77 -> /prometheus/01E65KN1255K3VN6YHSK6A436Q/index

File pointers and buffering

ISO C标准I/O库使用文件指针作为I/O的句柄。文件指针指向进程用户区的被称为FILE结构的数据结构。FILE结构包含一个buffer和文件描述符值。从某种意义上来说文件指针就是句柄的句柄(a handle to a handle)。

注意

  1. 与终端相关的文件是行缓冲(line buffered)的,而不是完全缓冲(fully buffered),标准错误除外,它在默认情况下是不缓冲的。
  2. 每个系统文件表(system file table)条目都包含一个关于指向它的文件描述符表条目的个数的计数值。进程关闭一个文件时,操作系统将计数值减一,只有当计数值为0时,才删除这个条目。
  3. 系统文件表(system file table)位于系统空间,不会被fork复制。当fork创建一个子进程时,子进程继承了父进程环境(environment)和上下文(context)中大部分内容的一份拷贝,其中包括信号状态(signal state),调度参数(scheduling parameters)以及文件描述符表(file descriptor table)。对于父进程在fork子进程之前(prior to fork)打开的文件,父进程和子进程共享相同的文件偏移量(file offset),由于它是存储在系统文件表(system file table)中。如果在fork之后打开文件,则父进程和子进程不共享文件偏移量,此时父进程和子进程的open操作会分别在系统文件表中创建新的条目;但是父进程和子进程仍然共享标准输入,标准输出和标准错误的系统文件表条目。

UNIX file implementation

磁盘格式化将物理硬盘划分为多个区域,每个区域被称为分区(partitions)。每个分区可以有自己的文件系统与之关联。UNIX中所有的全称路径(fully qualified paths)都是从根目录 / 开始。

目录项(directory entries)中包含了一个文件名,和固定长度结构的引用,这个结构被称为inode

Directory entry, inode and data block for a simple file

inode包含文件大小,文件位置,文件所有者,创建时间,最后访问时间,最后修改时间以及权限等等。除了有关文件的描述性信息外,inode中还包含了指向文件前几个数据块(first few data blocks of the file)的指针。

Hard links and Symbolic links

Unix目录有两种类型的链接--链接(link)和符号链接(symbolic link)。链接是一个目录项(directory entry) ,也称为硬链接(hard link)。符号链接又称为软链接(soft link),它是一个存储了字符串的文件(file),当路径名解析中遇到了此字符串就用它来修改路径名。

目录项(directory entry)对应单个链接,但是一个inode可能是好几个链接的目标,即好几个目录项指向同一个inode。每个inode包含对链接到此inode的链接数的计数。

创建和删除硬链接

我们可以通过ln命令或者link函数创建一个新的链接,同时会为这个链接分配一个新的目录项,并增加相应inode的链接计数,链接不再使用额外的磁盘空间。
当执行rm或者调用unlink函数来删除一个文件时,操作系统会删除对应的目录项,并减少inode的链接计数。只有在链接计数值减少到0时才会释放inode及其对应的数据块。

two hard links to the same file
(ENV) [root@ceph-2 ~]# ln data data.hard
(ENV) [root@ceph-2 ~]# ls -ali data*
34546123 -rw-r--r-- 2 root root 33 Nov 26 18:01 data
34546123 -rw-r--r-- 2 root root 33 Nov 26 18:01 data.hard

(ENV) [root@ceph-2 ~]# stat data.hard
  File: ‘data.hard’
  Size: 33          Blocks: 8          IO Block: 4096   regular file
Device: 801h/2049d  Inode: 34546123    Links: 2
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2020-04-19 12:25:23.393696438 +0800
Modify: 2020-04-19 12:45:50.005754927 +0800
Change: 2020-04-19 12:45:50.016754927 +0800
 Birth: -

创建和删除软链接

符号链接(symbolic link)是一个包含了另一个文件或目录的文件。我们可以通过ln -s或者调用symlink函数去创建一个符号链接。符号链接会创建新的inode

Ordinary file with a symbolic link to it
(ENV) [root@ceph-2 ~]# ln -s data data.symbolic
(ENV) [root@ceph-2 ~]# ls -li data*
34546123 -rw-r--r-- 2 root root 33 Apr 19 12:45 data
34546123 -rw-r--r-- 2 root root 33 Apr 19 12:45 data.hard
35815582 lrwxrwxrwx 1 root root  4 Apr 19 12:48 data.symbolic -> data
(ENV) [root@ceph-2 ~]# stat data.symbolic
  File: ‘data.symbolic’ -> ‘data’
  Size: 4           Blocks: 0          IO Block: 4096   symbolic link
Device: 801h/2049d  Inode: 35815582    Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2020-04-19 12:48:18.359762001 +0800
Modify: 2020-04-19 12:48:09.348761571 +0800
Change: 2020-04-19 12:48:09.348761571 +0800
 Birth: -

Introduction

File locking is a mutual-exclusion mechanism for files. Linux supports two major kinds of file locks:

  • advisory locks
  • mandatory locks

Advisory locks

Linux中有三种类型的建议锁(advisory locks)

  • BSD locks(flock)
  • POSIX record locks(fcntl, lockf)
  • Open file description locks(fcntl)

除了lockf函数,其他的锁都是读写锁(reader-writer locks),支持互斥和共享模式。

Common features

所有类型的锁都有如下特性:

  1. 所有的锁支持阻塞和非阻塞操作。
  2. 锁只能用在文件上,不能用在目录上。
  3. 如果进程退出或者终止,锁将自动被删除。这样保证如果锁被获取,则占用锁的进程是活的(alive)。

Differing features

-- BSD locks lockf function POSIX record locks Open file description locks
Portability widely available POSIX (XSI) POSIX(base standard) Linux 3.15+
Associated with File Object [i-node, pid] pair [i-node, pid] pair File Object
Applying to byte range no yes yes yes
Support exclusive and shared modes yes no yes yes
Atomic mode switch no - yes yes
Works on NFS (Linux) Linux 2.6.12+ yes yes yes

File descriptors and i-nodes

A file descriptor is an index in the per-process file descriptor table (in the left of the picture). Each file descriptor table entry contains a reference to a file object, stored in the file table (in the middle of the picture). Each file object contains a reference to an i-node, stored in the i-node table (in the right of the picture).

A file descriptor is just a number that is used to refer a file object from the user space. A file objectrepresents an opened file. It contains things likes current read/write offset, non-blocking flag and another non-persistent state. An i-node represents a filesystem object. It contains things like file meta-information (e.g. owner and permissions) and references to data blocks.

File descriptors created by several open() calls for the same file path point to different file objects, but these file objects point to the same i-node. Duplicated file descriptors created by dup2() or fork() point to the same file object.

BSD lock和 Open file description lock是与file object关联, 而POSIX record lock是与 [i-node, pid] pair关联。

BSD locks(flock)

最简单且最常用的文件锁是通过flock来提供。

Features:

  1. POSIX中没有指定,但是在各种Unix系统中被广泛使用。
  2. 总是对整个文件加锁。
  3. file object关联。
  4. 并不保证锁模式(exclusive and shared)之间是原子切换(atomic switch)。
  5. 在Linux 2.6.11版本之前,无法作用于NFS。自从2.6.12版本开始,使用fcntl() POSIX record byte-range locks 来模拟flock()函数对NFS加锁(除非在NFS挂载选项中禁用来模拟)。

POSIX record locks (fcntl)

Posix record锁是与进程关联的锁,通过fcntl提供。

Features:

  1. POSIX指定的
  2. 适用于byte range
  3. [i-node, pid]对关联,而不是文件对象
  4. 保证锁模式(exclusive and shared)之间是原子切换(atomic switch)
  5. 能工作在Linux NFS

lockf function

lockf函数是POSIX record locks的简化版

Features:

  1. POSIX指定的
  2. 适用于byte range
  3. [i-node, pid]对关联,而不是文件对象
  4. 只支持互斥锁
  5. 能工作在Linux NFS

Open file description locks (fcntl)

Open file description locks只针对与Linux,它结合了BSD locksPOSIX record locks的优点。通过fcntl提供。

Features:

  1. Linux-specific,没有在POSIX中指定。
  2. 适用于byte range
  3. file object关联。
  4. 保证锁模式(exclusive and shared)之间是原子切换(atomic switch)
  5. 能工作在Linux NFS
(ENV) [root@ceph-2 ~]# lslocks
COMMAND           PID  TYPE SIZE MODE  M START END PATH
atd              1007 POSIX   5B WRITE 0     0   0 /run/atd.pid
dockerd           996 FLOCK  32K WRITE 0     0   0 /var/lib/docker/volumes/metadata.db
ceph-mon        20226 POSIX   0B WRITE 0     0   0 /var/lib/ceph/mon/ceph-ceph-2/store.db/LOCK
etcd             1927 FLOCK  61M WRITE 0     0   0 /opt/sds/etcd/10.255.101.74.etcd/member/wal/000000000000001c-0000000000bf6bc6.w
java             3687 POSIX   0B WRITE 0     0   0 /usr/share/elasticsearch/data/nodes/0/indices/fsEGojTsQ_-mOId2lvPkzw/0/index/wr
(ENV) [root@ceph-2 ~]# cat /proc/locks
1: POSIX  ADVISORY  WRITE 3687 08:01:203193430 0 EOF
2: POSIX  ADVISORY  WRITE 3687 08:01:35313181 0 EOF
3: POSIX  ADVISORY  WRITE 3687 08:01:303759598 0 EOF
4: POSIX  ADVISORY  WRITE 3687 08:01:169440475 0 EOF
5: POSIX  ADVISORY  WRITE 3687 08:01:270424087 0 EOF
6: POSIX  ADVISORY  WRITE 3687 08:01:237033099 0 EOF
7: POSIX  ADVISORY  WRITE 3687 08:01:102692284 0 EOF
8: POSIX  ADVISORY  WRITE 3687 08:01:5305924 0 EOF
9: POSIX  ADVISORY  WRITE 3687 08:01:169745104 0 EOF
10: POSIX  ADVISORY  WRITE 3687 08:01:270228547 0 EOF
11: POSIX  ADVISORY  WRITE 3687 08:01:237033579 0 EOF
12: POSIX  ADVISORY  WRITE 3687 08:01:304187409 0 EOF
13: FLOCK  ADVISORY  WRITE 15965 00:12:97153870 0 EOF
14: FLOCK  ADVISORY  WRITE 996 08:01:135038043 0 EOF
15: POSIX  ADVISORY  WRITE 408 00:12:14985 0 EOF

Mandatory locks

Linux对强制文件锁提供了有限的支持。只有如下条件满足时强制锁才会被激活。

  1. 分区挂载时有开启mand选项。
  2. 为文件开启set-group-ID bit, 关闭group-execute bit
  3. POXIS record lock被获取到。

注意:

Since mandatory locks are not allowed for directories and are ignored by unlink() and rename() calls, you can’t prevent file deletion or renaming using these locks.

References

File locking in Linux
Unix系统编程
https://golang.org/pkg/syscall/#Flock
https://www.ibm.com/developerworks/cn/linux/l-cn-filelock/
https://www.kancloud.cn/kancloud/understanding-linux-processes/52169
https://linkscue.com/2018/09/07/2018-09-07-golang-flock-example/
golang的文件锁操作

相关文章

网友评论

      本文标题:file lock

      本文链接:https://www.haomeiwen.com/subject/nmfvvhtx.html