C - Linux FCNTL 文件锁定与超时



标准的 Linuxfcntl调用不提供超时选项。我正在考虑实现带有信号的超时锁定。

以下是阻塞锁的说明:


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,我希望第一个进程永远保持锁定,第二个进程尝试获取锁并在 3 秒后中断,但第二个进程刚刚终止。

谁能告诉我我的代码出了什么问题?

那么我需要用什么样的信号来指示锁 打断?

最明显的信号选择是SIGUSR1SIGUSR2. 提供这些是为了满足用户定义的目的。

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

而且由于我的 进程,我只想打断这个正在为之乱搞的IO线程 文件锁定,其他线程应该不受影响,但信号是 进程级别,我不确定如何处理这种情况。

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

更好的解决方案可能是使用 select():

https://www.gnu.org/software/libc/manual/html_node/Waiting-for-I_002fO.html

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
int
input_timeout (int filedes, unsigned int seconds)
{
fd_set set;
struct timeval timeout;
/* Initialize the file descriptor set. */
FD_ZERO (&set);
FD_SET (filedes, &set);
/* Initialize the timeout data structure. */
timeout.tv_sec = seconds;
timeout.tv_usec = 0;
/* select returns 0 if timeout, 1 if input available, -1 if error. */
return TEMP_FAILURE_RETRY (select (FD_SETSIZE,
&set, NULL, NULL,
&timeout));
}
int
main (void)
{
fprintf (stderr, "select returned %d.n",
input_timeout (STDIN_FILENO, 5));
return 0;
}

我对此遇到了一些困难。终于让它工作了。

// main1.cpp
#include <thread>
#include <chrono>
#include <iostream>
int main(int argc, char *argv[]) {
int fd = open(argv[1],O_RDWR|O_CREAT,S_IRWXU | S_IRWXG | S_IRWXO);
struct flock fd_lock;
fd_lock.l_type = F_WRLCK;    /* read/write (exclusive) fd_lock_lock */
fd_lock.l_whence = SEEK_SET; /* base for seek offsets */
fd_lock.l_start = 0;         /* 1st byte in file */
fd_lock.l_len = 0;           /* 0 here means 'until EOF' */
fd_lock.l_pid = getpid();
std::cout << "locked filen";
fcntl(fd, F_SETLKW, &fd_lock);
std::cout << "file lockedn";
std::this_thread::sleep_for(std::chrono::seconds(100));
}
// main2.cpp
#include <cstring>
#include <chrono>
#include <thread>
#include <iostream>
struct signal_trigger_thread_args { 
int signum;
pthread_t tid;
unsigned int seconds;
};
void alarm_handler(int signum, siginfo_t *x, void *y) {
// std::cout << "Alarm Handler!n";
}
void *trigger_signal_after_time(void *arg) {
struct signal_trigger_thread_args *_arg = (struct signal_trigger_thread_args*)arg; 
std::this_thread::sleep_for(std::chrono::seconds(_arg->seconds));
std::cout << "triggering signal!n";
pthread_kill(_arg->tid,_arg->signum);
return NULL;
}
int fcntl_wait_for(int fd, int cmd, struct flock *_flock, int signum, unsigned int _seconds) {
// Create a thread to trigger the signal.
pthread_t signal_trigger_thread;
struct signal_trigger_thread_args args;
args.signum = signum;
args.tid = pthread_self();
args.seconds = _seconds;
int return_value = pthread_create(&signal_trigger_thread, NULL, &trigger_signal_after_time,(void *)&args);
if ( return_value ) {
std::cout << "pthread creation failedn";
return -2;
}
return_value = fcntl(fd, cmd, _flock);
if ( return_value == 0 ) { return 0; }
if ( return_value = -1 && errno == EINTR ) {
return 1;
}
return -1;
}
int main(int argc, char *argv[]) {
// initialize_signal_handlers();
static struct sigaction _sigact;
memset(&_sigact,0,sizeof(_sigact));
_sigact.sa_sigaction = alarm_handler;
_sigact.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1,&_sigact,NULL);

int fd = open(argv[1],O_RDWR|O_CREAT,S_IRWXU | S_IRWXG | S_IRWXO);
struct flock fd_lock;
fd_lock.l_type = F_WRLCK;    /* read/write (exclusive) fd_lock_lock */
fd_lock.l_whence = SEEK_SET; /* base for seek offsets */
fd_lock.l_start = 0;         /* 1st byte in file */
fd_lock.l_len = 0;           /* 0 here means 'until EOF' */
fd_lock.l_pid = getpid();
std::cout << "waiting for file to be freed for 5 secondsn";
int return_value = fcntl_wait_for(fd, F_SETLKW, &fd_lock, SIGUSR1, 5);
if ( return_value == 1 ) {
std::cout << "fcntl was interrupted!n";
} else if ( return_value == 0 ) {
std::cout << "fcntl obtained lock!n";
} else {
std::cout << "fcntl failed!n";
}
}

最新更新