c - linux fcntl 文件锁定超时

标签 c linux multithreading file-locking

标准linux fcntl调用不提供超时选项。我正在考虑用信号实现超时锁定。

下面是阻塞锁的说明:


F_SETLKW

该命令与 F_SETLK 等效,但如果共享锁或独占锁被其他锁阻塞,则线程应等待,直到请求得到满足。 如果在 fcntl() 等待区域时接收到要捕获的信号,则 fcntl() 将被中断。从信号处理程序返回后,fcntl() 将返回 -1,并带有 errno设置为[EINTR],则不进行锁定操作。


那么我需要使用什么样的信号来指示锁被中断呢?而且由于我的进程中有多个线程在运行,我只想中断这个正在阻塞文件锁的IO线程,其他线程不应该受到影响,但是信号是进程级的,我不知道如何处理这种情况。

添加:

我使用信号编写了一个简单的实现。

int main(int argc, char **argv) {
  std::string lock_path = "a.lck";

  int fd = open(lock_path.c_str(), O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);

  if (argc > 1) {
    signal(SIGALRM, [](int sig) {});
    std::thread([](pthread_t tid, unsigned int seconds) {
      sleep(seconds);
      pthread_kill(tid, SIGALRM);
    }, pthread_self(), 3).detach();
    int ret = file_rwlock(fd, F_SETLKW, F_WRLCK);

    if (ret == -1) std::cout << "FAIL to acquire lock after waiting 3s!" << std::endl;

  } else {
    file_rwlock(fd, F_SETLKW, F_WRLCK);
    while (1);
  }

  return 0;
}

通过运行./main,然后运行./main a,我希望第一个进程永远持有锁,第二个进程尝试获取锁并在之后中断3s,但第二个进程刚刚终止。

谁能告诉我我的代码有什么问题吗?

最佳答案

So what kind of signal I need to use to indicate the lock to be interrupted?

最明显的信号选择是 SIGUSR1SIGUSR2。提供这些是为了服务于用户定义的目的。

还有 SIGALRM,如果您使用产生此类信号的计时器来进行计时,这将是很自然的,并且即使以编程方式生成也是有意义的,只要您不将其用于其他目的。

And since there're multiple threads running in my process, I only want to interrupt this IO thread who is blokcing for the file lock, other threads should not be affected, but signal is process-level, I'm not sure how to handle this situation.

您可以通过 pthread_kill() 函数向多线程进程中选定的线程传递信号。这也适用于多个线程同时等待锁的情况。

使用常规 kill(),您还可以选择让所有线程阻塞所选信号 (sigprocmask()),然后让线程进行锁定尝试立即解锁它。当所选信号传递到进程时,当前未阻塞该信号的线程将接收该信号(如果有可用的此类线程)。

示例实现

这假设已经设置了一个信号处理程序来处理所选信号(它不需要执行任何操作),并且要使用的信号编号可通过符号 LOCK_TIMER_SIGNAL 。它提供所需的超时行为作为 fcntl() 的包装函数,并使用问题中所述的命令 F_SETLKW 。

#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE

#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/syscall.h>

#if (__GLIBC__ < 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 30)
// glibc prior to 2.30 does not provide a wrapper 
// function for this syscall:    
static pid_t gettid(void) {
    return syscall(SYS_gettid);
}
#endif

/**
 * Attempt to acquire an fcntl() lock, with timeout
 *
 * fd: an open file descriptor identifying the file to lock
 * lock_info: a pointer to a struct flock describing the wanted lock operation
 * to_secs: a time_t representing the amount of time to wait before timing out
 */    
int try_lock(int fd, struct flock *lock_info, time_t to_secs) {
    int result;
    timer_t timer;

    result = timer_create(CLOCK_MONOTONIC,
            & (struct sigevent) {
                .sigev_notify = SIGEV_THREAD_ID,
                ._sigev_un = { ._tid = gettid() },
                // note: gettid() conceivably can fail
                .sigev_signo = LOCK_TIMER_SIGNAL },
            &timer);
    // detect and handle errors ...

    result = timer_settime(timer, 0,
            & (struct itimerspec) { .it_value = { .tv_sec = to_secs } },
            NULL);

    result = fcntl(fd, F_SETLKW, lock_info);
    // detect and handle errors (other than EINTR) ...
    // on EINTR, may want to check that the timer in fact expired

    result = timer_delete(timer);
    // detect and handle errors ...

    return result;
}

这符合我的预期。

注释:

  • 信号处置是进程范围的属性,而不是每个线程的属性,因此您需要在整个程序中协调信号的使用。在这种情况下,try_lock 函数本身修改其所选信号的配置是没有用的(而且可能很危险)。
  • timer_* 接口(interface)提供 POSIX 间隔计时器,但指定特定线程从此类计时器接收信号的规定是 Linux 特定的。
  • 在 Linux 上,您需要链接 -lrt 来实现 timer_* 函数。
  • 上述内容围绕 Glibc 的 struct sigevent 不符合其自己的文档(至少在相对较旧的版本 2.17 中)的事实。文档声称 struct sigevent 具有成员 sigev_notify_thread_id ,但实际上没有。相反,它有一个包含相应成员的未记录的 union ,并且它提供了一个宏来修补差异 - 但该宏不能用作指定初始值设定项中的成员指示符。
  • fcntl 锁在每个进程的基础上运行。因此,同一进程的不同线程不能通过这种锁相互排斥。此外,同一进程的不同线程可以修改通过其他线程获得的 fcntl() 锁,而无需任何特殊努力或向任一线程发出任何通知。
  • 您可以考虑为此目的创建和维护一个每线程静态计时器,而不是在每次调用时创建然后销毁一个新计时器。
  • 请注意,如果被任何不终止线程的信号中断,fcntl() 将返回 EINTR。因此,您可能希望使用一个信号处理程序来设置肯定的每线程标志,通过该标志您可以验证是否收到了实际的计时器信号,以便在被不同信号中断时重试锁定。
  • 您需要确保线程不会因其他原因收到所选信号,或者通过其他方式确认在锁定失败并显示 EINTR 时时间实际上已过期.

关于c - linux fcntl 文件锁定超时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57047510/

相关文章:

select 能否被内核模块不间断地阻塞?

java - 将 volatile 关键字与包装类一起使用

c - C (gcc) 中的段错误

c - Linux内核到用户空间的UDP数据包路径

c - 失败注入(inject) :Try to write into protected RAM area

c - 写入后从磁盘读取而不是缓存

c - 使用alarm()和pause()频繁发送信号

linux screen 查看前面几行

multithreading - Go 内存模型发生在之前(具有共享状态的 channel )

java - 使用多线程处理两个 Map