标准的linuxfcntl调用不提供超时选项。我正在考虑用信号实现一个超时锁。
以下是阻塞锁的描述:
塞特尔克
此命令应等同于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,我希望第一个进程永远保持锁,第二个进程尝试获取锁并在3秒后中断,但第二个进程刚刚终止。
有人能告诉我我的密码怎么了吗?

最佳答案

所以我需要用什么信号来表示锁是
打断?
最明显的信号选择是SIGUSR1SIGUSR2。这些是为用户定义的目的而提供的。
还有SIGALRM,如果您使用产生这样一个信号的计时器来计时,这是很自然的,而且只要您不将其用于其他目的,即使是以编程方式生成也有一定的意义。
因为有多个线程在我的
进程,我只想中断这个正在阻塞的IO线程
文件锁定,其他线程不应受到影响,但信号是
过程级,我不知道如何处理这种情况。
您可以通过pthread_kill()函数将信号传递到多线程进程中选定的线程。这也适用于多个线程同时等待锁的情况。
对于regularkill(),您还可以让所有线程阻塞所选信号(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>

// glibc does not provide a wrapper function for this syscall:
static pid_t gettid(void) {
    return syscall(SYS_gettid);
}

/**
 * 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_*接口提供POSIX间隔计时器,但指定特定线程以从此类计时器接收信号的规定是Linux特有的。
在Linux上,需要与-lrt链接才能使用timer_*函数。
上面提到Glibc的struct sigevent不符合它自己的文档(至少在相对老的版本2.17中是这样)。文档声称struct sigevent有一个成员,但实际上没有。相反,它有一个包含对应成员的未记录联合,并且它提供了一个宏来修补差异——但是该宏不能在指定的初始值设定项中用作成员指示符。
sigev_notify_thread_id锁以每个进程为基础运行。因此,同一进程的不同线程不能通过这种锁相互排斥。此外,同一进程的不同线程可以修改通过其他线程获得的fcntl锁,而无需任何特殊工作或向任何线程发出任何通知。
为此,可以考虑创建和维护每个线程的静态计时器,而不是在每次调用时创建并销毁一个新计时器。
请注意,如果被任何不终止线程的信号中断,fcntl()将返回fcntl()。因此,您可能希望使用一个信号处理程序来设置每个线程的肯定标志,通过该标志可以验证是否已接收到实际的计时器信号,以便在锁被其他信号中断时重试该锁。
这取决于您确保线程不会因为其他原因接收到所选的信号,或者通过其他方式确认,如果锁定失败,时间实际上已经过期。

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

10-11 15:37