使用带文件描述符的方法可以让你更好地控制和管理 flock 以及与锁相关的行为。当你在子进程或子shell中使用文件描述符时,文件锁可以跨越这些范围,并且只有在你显式地释放它时,锁才会被释放。
让我们看一个例子,更好地理解这一点。
假设我们有一个脚本 script_a.sh:
#!/bin/sh
lock_file="/tmp/script_b.lock"
touch "$lock_file"
# 使用带文件描述符的方法
(
flock -n 9 || { echo "Script B is locked, could not acquire lock."; exit 1; }
# 在子进程中执行脚本b
( ./script_b.sh ) &
) 9>"$lock_file"
在这段代码中,主 shell 脚本使用文件描述符 9 来创建并获取锁。当主脚本退出时,由于 script_b.sh
在子进程运行(使用 &
符号),script_b.sh
可以继承父进程(即主 shell 脚本)的文件描述符。
关键在于,script_b.sh
继承了文件描述符 9,而该描述符具有在退出时继续保持文件锁的特性。这意味着,即使主脚本已经退出,script_b.sh
仍然可以继续持有文件锁,直至其运行结束。
这里的一个重要概念是:锁是与文件描述符绑定的,而文件描述符可以在进程之间传递(例如从父进程传递给子进程)。因此,在这个例子中,锁的生命周期取决于文件描述符 9 何时被关闭。而由于 script_b.sh
继承了该文件描述符并一直保持打开状态,因此它可以在主进程退出后继续持有文件锁。
flock
命令会将锁状态与文件描述符状态联系起来。因此,在文件描述符关闭时,操作系统也会自动清理与该描述符相关的状态,从而释放文件锁。
这种设计有助于防止资源泄漏,确保文件锁在描述符关闭、进程退出或异常终止时被释放。当文件描述符关闭时,操作系统知道不再有进程可以访问该文件或其他资源。因此,释放与文件描述符关联的文件锁是一种自动清理资源的机制,用于确保锁不会因为进程退出或崩溃而永久保持。
相反,如果我们不使用文件描述符,脚本将如下所示:
#!/bin/sh
lock_file="/tmp/script_b.lock"
touch "$lock_file"
# 不使用文件描述符的方法
if flock -n "$lock_file" -c "true"; then
# 在子进程中执行脚本b
( ./script_b.sh ) &
else
echo "Script B is locked, could not acquire lock."
fi
在这种情况下,flock
使用 -c
参数来指定一个命令作为子进程执行。flock
会在获得锁之后执行这个命令,并在该命令运行完成之后自动释放锁。
这行代码将 flock
与文件名一起使用。当使用 -c
参数时,flock
会创建一个临时文件描述符,用于在内部锁定文件。这个临时文件描述符对用户是不可见的。
锁的状态如下:
flock
时,创建一个临时文件描述符,锁定对应的文件。flock
执行子进程(这里是 true
),子进程运行期间锁保持。flock
释放锁。flock
内部创建的。这种锁的状态类似于使用文件描述符的情况,但是更封装且适用于单个命令的执行。当在脚本中有多个命令需要在获得锁后顺序执行时,这种方法可能不太适用。在这种情况下,使用文件描述符将更合适,因为它允许您在脚本中自由地写入多个命令,而无需将它们包装在 -c
参数中。
所以,使用带文件描述符的方法可以让你更好地在子进程或子shell中保持和管理文件锁。
---------------------------------------------------------------------------------------------------------------------------------
在 UNIX 和 Linux 系统中,文件锁通常是与文件描述符绑定的。文件描述符是操作系统分配的唯一标识符,代表打开的文件、设备、网络套接字等资源。文件锁实际上是操作系统内核提供的一种同步和协调机制,管理对共享文件或资源的访问。文件锁依赖于文件描述符,以便操作系统追踪哪个进程手中持有哪个锁。
当一个进程想要锁定一个文件时,它需要首先打开该文件并获得一个文件描述符。然后,这个进程可以使用系统调用(例如 fcntl
或 flock
)操作文件描述符,请求操作系统将锁应用于该文件。操作系统会根据文件描述符的状态,来决定是否授予文件锁。
当文件描述符关闭时,与它关联的文件锁会被自动释放。这是因为文件锁的生命周期与文件描述符有关。这种设计确保了文件锁在进程退出、崩溃或正常关闭文件描述符时自动释放,从而防止了资源泄露和死锁。
在某些情况下,如使用 flock
命令时,您可以看到文件锁与文件名一起使用,而不是与文件描述符一起使用。然而,在这些情况下,flock
内部依然会创建和管理临时的文件描述符,以确保文件锁与文件描述符之间的关联。所以,最终文件锁还是与文件描述符绑定的。
总之,文件锁是与文件描述符紧密相关的,并通过操作系统内核进行管理。使用文件描述符允许操作系统有效地跟踪和控制对共享资源的访问。
父进程和子进程共享文件描述符的情况下,对文件锁的操作实际上是共享的,而不是独立的。这是因为文件锁是与文件描述符关联的,而文件描述符在父子进程之间共享。因此,如果子进程解锁文件描述符,父进程的锁定状态也会受到影响。
这是一个示例,说明当子进程解锁文件描述符时,父进程的锁定状态会受到影响:
#include
#include
#include
#include
#include
#include
int main() {
int fd = open("testfile.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("open failed");
exit(1);
}
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(fd, F_SETLK, &lock) == -1) {
perror("fcntl lock failed");
exit(1);
}
printf("Parent process (%d) locked the file\n", getpid());
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
} else if (pid == 0) {
// 子进程
lock.l_type = F_UNLCK;
if (fcntl(fd, F_SETLK, &lock) == -1) {
perror("Child process fcntl unlock failed");
}
printf("Child process (%d) released the file lock\n", getpid());
} else {
// 父进程
sleep(3);
lock.l_type = F_WRLCK; // 尝试重新对文件上锁
if (fcntl(fd, F_SETLK, &lock) == -1) {
perror("Parent process fcntl lock failed");
} else {
printf("Parent process (%d) locked the file again\n", getpid());
}
lock.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &lock);
close(fd);
}
return 0;
}
运行结果:
父进程锁定文件描述符 fd
,然后创建子进程。子进程解锁文件描述符,父进程在3秒后尝试重新锁定。这时,由于子进程已经解锁,父进程能够成功重新锁定文件。这表明子进程解锁文件描述符时,确实影响了父进程的锁定状态。