andrew@andrew-Thurley:/dev$ cat /proc/locks
1: POSIX ADVISORY WRITE 8968 08:01:11666907 1073741825 1073741825
2: POSIX ADVISORY READ 2433 08:01:11798469 128 128
...
35: FLOCK ADVISORY WRITE 1436 00:1a:7 0 EOF
51: FLOCK ADVISORY WRITE 1036 00:16:763 0 EOF
使用ps -p PID
查看进程的相关信息
andrew@andrew-Thurley:/dev$ ps -p 8968
PID TTY TIME CMD
8968 ? 00:00:02 chrome
从上面的输出可以看出持有的锁的程序是chrome
,即 google
浏览器
在/dev
我下搜索主设备号为8
次设备号为1
的设备,是/dev/sda1
andrew@andrew-Thurley:/dev$ ls -li /dev/sda1 |awk '$6=8'
351 brw-rw---- 1 root disk 8 1 12月 22 13:13 /dev/sda1
查看设备/dev/sda1
的挂载点,并在该部分文件系统中搜索i-node
节点为11666907
的文件
andrew@andrew-Thurley:/dev$ mount |grep sda1
/dev/sda1 on / type ext4 (rw,relatime,errors=remount-ro,data=ordered)
从上面的输出可以看出,设备/dev/sda1
是挂在在 /
上的
andrew@andrew-Thurley:/dev$ sudo find / -mount -inum 11666907
/home/andrew/.config/google-chrome/Default/History
find -mount
选项是防止find
进入到/
下的子目录进行搜索
最后显示被锁住文件的内容--google
浏览器的历史记录
sudo vim /home/andrew/.config/google-chrome/Default/History
是一个google
浏览器的SQLite
数据库文件
这样就可以看出google
浏览器持有文件sudo vim /home/andrew/.config/google-chrome/Default/History
上的一把锁,而这个文件的内容就是google
用于记录历史记录的数据库文件。
通过/proc/locks
还能够获取被阻塞的锁请求的相关信息,如下图所示
其中锁号后面随即跟着->
的行表示被阻塞的锁的请求
一些程序-特别是很多的daemon
需要确保同一时刻只有一个程序实例在系统中运行。完成这项任务的一个常见的方法是让daemon
在一个标准的目录中创建一个文件并在该文件上放置一把写锁。daemon
在执行期间一直持有这个文件锁并在即将终止前删除这个文件。如果启用了daemon
的另外一个实例,那么它在获取该文件的写锁时就会失败,其结果是它会意识到daemon
的另一个实例肯定在运行,然后终止。
很多的网络服务器采用了另一种常规的做法,即当服务器绑定的众所周知的
socket
端口号已经被使用时就认为该服务器实例已经处于运行在状态了
/var/run
目录通常是存放此类文件的位置,或者也可以在daemon
的配置文件中加上一行,来指定文件的位置。
通常daemon
会将其进程ID
写入该文件,因此这个文件命名时通常将.pid
作为扩展名。这对于那些需要找出daemon
的进程ID
的应用程序来讲是比较有用的。它允许执行额外的健全检查--效果可以像20.5节那样使用kill(pid,0)来检查进程ID
是否存在。
文件create_pid_file.c
展示了,用来创建和锁住一个进程ID
锁文件的代码实现。可以使用下面的方式调用这个函数。
if(createPidFile("mydaemon", "/car/run/mydaemon.pid", 0) == -1)
errExit("createPidFile");
createPidFile
函数的精妙之处在于使用ftruncate()
函数来清除文件中之前存在的所有的字符串。之所以这样做是因为daemon
的上一个实例在删除文件时可能因系统崩溃而失败,在这种情况下,如果新的daemon
实例的进程ID
较小,那么可能就无法完全覆盖之前文件中的内容,从严格意义上来说清除所有的既有字符串不是必需的,但这样做显得更加简洁并且排除产生混淆的可能。
#include
#include
#include "region_locking.h" /* For lockRegion() */
#include "create_pid_file.h" /* Declares createPidFile() and
defines CPF_CLOEXEC */
#include "tlpi_hdr.h"
#define BUF_SIZE 100 /* Large enough to hold maximum PID as string */
#define CPF_CLOEXEC 1
static int
lockReg(int fd, int cmd, int type, int whence, int start, off_t len)
{
struct flock fl;
fl.l_type = type;
fl.l_whence = whence;
fl.l_start = start;
fl.l_len = len;
return fcntl(fd, cmd, &fl);
}
int /* Lock a file region using nonblocking F_SETLK */
lockRegion(int fd, int type, int whence, int start, int len)
{
return lockReg(fd, F_SETLK, type, whence, start, len);
}
int
createPidFile(const char *progName, const char *pidFile, int flags)
{
int fd;
char buf[BUF_SIZE];
fd = open(pidFile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1)
errExit("Could not open PID file %s", pidFile);
//< flags 设置为1的时候使用fcntl设置FD_CLOEXEC
// 对于通过exec()来从起自己的进程比较有用,如果在exec()时文件描述符没有被关闭,那么重新启动的服务器就会认为服务器的另一个示例处于运行状态。
if (flags & CPF_CLOEXEC) {
/* Set the close-on-exec file descriptor flag */
/* Instead of the following steps, we could (on Linux) have opened the
file with O_CLOEXEC flag. However, not all systems support open()
O_CLOEXEC (which was standardized only in SUSv4), so instead we use
fcntl() to set the close-on-exec flag after opening the file */
flags = fcntl(fd, F_GETFD); /* Fetch flags */
if (flags == -1)
errExit("Could not get flags for PID file %s", pidFile);
flags |= FD_CLOEXEC; /* Turn on FD_CLOEXEC */
if (fcntl(fd, F_SETFD, flags) == -1) /* Update flags */
errExit("Could not set flags for PID file %s", pidFile);
}
//设置一把写锁 SEEK_SET-开头 0 到 len=0代表到EOF
//即对整个文件放置一把写锁,并且写锁的范围随着文件的增加而改变
if (lockRegion(fd, F_WRLCK, SEEK_SET, 0, 0) == -1) {
if (errno == EAGAIN || errno == EACCES)
fatal("PID file '%s' is locked; probably "
"'%s' is already running", pidFile, progName);
else
errExit("Unable to lock PID file '%s'", pidFile);
}
//将文件的大小截断为0
if (ftruncate(fd, 0) == -1)
errExit("Could not truncate PID file '%s'", pidFile);
//将PID写如文件
snprintf(buf, BUF_SIZE, "%ld\n", (long) getpid());
if (write(fd, buf, strlen(buf)) != strlen(buf))
fatal("Writing to PID file '%s'", pidFile);
return fd;
}
在较老的不支持文件加锁的UNIX
实现上,可以是会用一些特别的加锁技术。尽管这些技术都已经被fcntl()
记录加锁计数所取代,但这里仍然需要介绍它们,因为一些较早的应用程序中,仍然存在他们的身影。所有的这些技术在性质上都是劝告式的
open(file, O_CREAT | O_EXCL,...)
加上unlink(file)
SUSv3
第三版的单一规范里,要求使用了O_CREAT
和O_EXCL
标记的open()
调用原子执行检查文件的存在性,以及创建文件的两个步骤。这就意味着如果两个进程尝试在创建一个文件时指定这些标记,那么就保证其中只有一个进程能够成功。(另外一个进程从open中收到EEXIST
错误。)这种调用与unlink()
系统调用组合起来就构成一种加锁机制的基础。获取所可通过使用O_CREAT
和O_EXCL
标记打开文件后,立即跟着一个close()
来完成,释放锁可通过使用unlink()
来完成,尽管这项技术能够正常的工作,但它存在一些局限:
如果open()
失败了,即表示其他进程拥有了锁,那么就必须要在某种循环中重试open()
操作,这种循环既可以是持续不断地,也可以是相邻两次尝试时间上加上一定的时间延时。有了fcntl()
之后则可以使用F_SETTLKW
来阻塞直到锁可用为止。
使用open()
和uunlink()
获取和释放锁涉及到的文件系统的操作,这比记录锁要慢得多,
如果一个进程意外的终止并且没有删除锁文件,那么锁就不会被释放。处理这个问题存在特别的技术,包括检查文件的上次修改时间和让锁的持有者将进程ID
写入文件,这样就能够检查进程是否存在,但这些计数中没有一项技术是安全可靠的,与之相反的是,在一个进程终止时记录锁的释放操作是原子的。
如果放置多把锁,那么就无法检车出死锁,如果发生了死锁,那么造成死锁的进程就会永远的保持阻塞。
第二版的NFS
不支持O_EXCL
语义。Linux
2.4NFS
客户端也没有正确地实现O_EXCL
,即使是第三版的NFS
以及以后版本的也没能完成这个任务。
link(file, lockfile)
加上unink()lockfile
link()
系统调用,在新链接已经存在时,会失败的事实可用作一种加锁机制,而解锁功能则还是使用unlink()
来完成。常规的做法是让需要获取锁的进程创建一个临时的文件名,一般来将需要包含进程ID
。要获取锁则需要将这个临时文件链接到某个约定的标准路径上。如果link()
调用成功,那么就获取了锁,如果调用失败EEXIST
,那么就是另一个进程持有了锁,因此必须要在稍后的某个时刻重新尝试获取该锁。这项技术与上面介绍的open(file, O_CREAT|O_EXCL, 0)
技术存在相同的局限。
open(file, O_CREAT|O_TRUNC|O_WRONLY, 0)
当指定O_TRUNC
并且写权限被拒绝时在一个既有文件上调用open()
会失败的事实,可作为一项几所技术的基础,要获取一把锁可以使用下面的代码,来创建新文件。
fd=open(file, O_CREAT|O_TRUNC|O_WRONLY, (mode_t)0);
close(fd);
如果open()
调用成功,那么就获取了锁,如果因EACCES
而失败(即文件存在但是没没有人拥有权限),那么其他进程持有了锁,还需要在后面某个时刻尝试重新获取锁。这项计数与前面介绍的技术存在相同的局限,还需要注意的是不能具备超级用户特权的程序使用这项技术,因为open()
总是会成功,不管文件上设置的权限是什么。
文件加锁使得进程能偶同步对一个文件的访问,Linux
提供了两种文件加锁的系统调用
flock()
system V
衍生出来的fcntl()
尽管这两组系统调用在大多数UNIX
实现上都是可用的,但只有fcntl()
加锁是在SUSv3
中进行了标准化。
flock()
系统调用对整个文件加锁,可放置的锁有两种:一种是共享锁,这种锁与其他进程持有的共享锁是兼容的;另一种互斥锁,这种锁能偶阻止其他进程放置这两种锁。
fcntl()
系统调用将一个文件的任意区域上放置一把锁(“记录锁”),这个区域可以是单个字节也可以是整个文件。可放置的锁有两种:读锁和写锁,他们之间的兼容性语义与flock()
放置的共享锁和互斥锁之间的兼容语义类似。如果一个阻塞式(F_SETLKW
)锁请求将会导致死锁,那么内核将让其中一个受影响的进程的fcntl()
失败(返回EDADLK
错误)。
除非系统中的flock()
是使用fcntl()
实现的,否则使用flock()
和fcntl()
放置的锁之间是相互不可见的,通过flcok()
和fcntl()
放置的锁在fork()
中的继承语义和在文件描述符被关闭时的释放语义是不同的。
Linux
特有的/proc/locks
文件给出了系统中所有进程当期持有的文件锁。