如何"buffer" UNIX 信号
How do you "buffer" UNIX signals
如何以阻塞函数在被调用时解封的方式缓冲UNIX信号?在我们的软件中,我们使用套接字。我们现在想通过信号取消阻止/取消recv()
呼叫。问题是,如果在输入recv()
之前发送信号,那么它就会丢失,recv()
永远不会解锁。
主函数如下所示:
bool signalHandlerSetup;
bool signalSent;
int main(int argc, char* argv[])
{
pthread_t thread;
signalHandlerSetup = false;
signalSent = false;
// Block the SIGUSR1 signal in the main thread
sigset_t signalSet;
sigemptyset(&signalSet);
sigaddset(&signalSet, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &signalSet, NULL);
// Setup the signal handler for all future threads
struct sigaction signalAction;
signalAction.sa_flags = 0;
signalAction.sa_handler = [](int signalNumber) {};
sigaction(SIGUSR1, &signalAction, NULL);
pthread_create(&thread, NULL, secondaryThreadFunction, NULL);
std::cout << "Started thread" << std::endl;
// Wait until the signal handler is setup
while (!signalHandlerSetup);
pthread_kill(thread, SIGUSR1);
signalSent = true;
pthread_join(thread, NULL);
std::cout << "Joined thread" << std::endl;
}
辅助线程只是创建一个套接字并尝试从中读取:
void* secondaryThreadFunction(void *arg)
{
// Setup socket
int internSocket = setupSocket();
// Setup the signal handling
sigset_t signalSet;
sigemptyset(&signalSet);
sigaddset(&signalSet, SIGUSR1);
pthread_sigmask(SIG_UNBLOCK, &signalSet, NULL);
signalHandlerSetup = true;
while (!signalSent);
char buffer;
std::cout << "recv()..." << std::endl;
ssize_t bytesRead = recv(internSocket, static_cast<void*>(&buffer), sizeof(buffer), 0);
std::cout << "recv() canceled" << std::endl;
close(internSocket);
}
如您所见,在输入recv()
函数之前显式发送信号以说明问题。
除了缓冲之外,可能还有其他解决方案,但每种解决方案
都有缺点:- 等到辅助线程被阻塞(导致主线程也阻塞或繁忙循环(
- 不断发送信号(另一个繁忙循环(
编辑: 不应仅仅为了取消阻塞辅助线程而关闭套接字。我们可能希望在解锁后再次在同一套接字上运行。
编辑2: 我已经能够用马丁·詹姆斯和达伦·史密斯提供的解决方案来解决这个问题。我现在正在将select()
函数与事件文件描述符结合使用。作为参考,这里是工作解决方案:
int eventDescriptor;
int main(int argc, char* argv[])
{
pthread_t thread;
// Create the event file descriptor
eventDescriptor = eventfd(
0, // Initial value
0 // Flags
);
if (eventDescriptor == -1)
{
std::cout << "Failed to create event file descriptor" << std::endl;
return -1;
}
pthread_create(&thread, NULL, secondaryThreadFunction, NULL);
std::cout << "Started thread" << std::endl;
// Notify the event descriptor
uint64_t valueToWrite = 1;
if (write(eventDescriptor, &valueToWrite, sizeof(uint64_t)) == -1)
{
std::cout << "Failed to write to event file descriptor" << std::endl;
return -1;
}
pthread_join(thread, NULL);
std::cout << "Joined thread" << std::endl;
close(eventDescriptor);
return 0;
}
这是辅助线程:
void* secondaryThreadFunction(void *arg)
{
// Setup socket
int internSocket = setupSocket();
// Set up the file descriptor set
fd_set readFileDescriptorSet;
FD_ZERO(&readFileDescriptorSet);
FD_SET(internSocket, &readFileDescriptorSet);
FD_SET(eventDescriptor, &readFileDescriptorSet);
char buffer;
std::cout << "select()..." << std::endl;
int fileDescriptorsSet = select(std::max(internSocket, eventDescriptor) + 1, &readFileDescriptorSet, NULL, NULL, NULL);
if (FD_ISSET(eventDescriptor, &readFileDescriptorSet))
{
std::cout << "select() canceled via event" << std::endl;
}
else if (FD_ISSET(internSocket, &readFileDescriptorSet))
{
std::cout << "select() canceled through socket" << std::endl;
ssize_t bytesRead = recv(internSocket, static_cast<void*>(&buffer), sizeof(buffer), 0);
}
close(internSocket);
}
您可以考虑执行以下操作(类似于评论中的MartinJames建议(。
重构你的辅助线程,以便不是直接阻塞recv()
调用,而是用对基于文件的事件循环之一(例如,epoll
或select
(的阻塞调用来替换
它阻止调用将侦听两个文件描述中的事件:
(1( 套接字的文件描述符 (internSocket
(
(2( 由eventfd()
创建的新文件描述符
在 main 函数中,您将通过调用eventfd()
来创建事件文件描述符。 当信号到达时,将值写入事件文件描述符;这将导致被阻止的线程从选择等待中恢复。
基本示例:
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/eventfd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
void write_to_eventfd(int fd) {
/* we must write an 8 byte integet to the eventfd. */
uint64_t u = 1;
if (-1 == write(fd, &u, sizeof(uint64_t)))
perror("write()");
}
int main()
{
/* create event file descriptor */
int efd = eventfd(0, 0);
if (efd == -1)
perror("eventfd()");
/* For example purpose, do an immediate write; this causes select() to
* immediately return (simulate signal arrive before socket read). Later
* perform this in separate thread upon signal arrival. */
write_to_eventfd(efd);
/* Watch stdin (fd 0) to see when it has input. Watch the eventfd for an
* event. Add other socket file descriptors etc. */
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(0, &rfds);
FD_SET(efd, &rfds);
/* Blocking read */
int retval = select(efd+1, &rfds, NULL, NULL, NULL);
if (retval == -1)
perror("select()");
if (FD_ISSET(efd, &rfds))
printf("event!n"); /* next: read the value from efd */
if (FD_ISSET(0, &rfds))
printf("some data on fd(0)n");
return EXIT_SUCCESS;
}
还要考虑其他事件循环。
不要试图"取消阻止recv
"。相反,请使用非阻塞recv
,并使用为此目的而设计的ppoll
来阻止。(也可以使用pselect
(
设置struct pollfd
:
struct pollfd pollfd = {.fd = internSocket, .events = POLLIN};
阻止信号,直到您准备好接收它:
sigset_t signalSet;
sigemptyset(&signalSet);
sigaddset(&signalSet, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &signalSet, NULL);
准备一个信号集,信号畅通无阻:
pthread_sigmask(SIG_SETMASK /* ignored when second parameter is null */, NULL, &signalSet);
sigdelset(&signalSet, SIGUSR1);
呼叫ppoll
以阻止:
int ppoll_result = ppoll(&pollfd, 1, NULL /* no timeout */, &signalSet);
检查ppoll
是否被信号中断,或者您是否获得了任何数据:
if (ppoll_result < 0) {
if (errno == EINTR) {
// interrupted by signal
} else {
// error occurred
}
} else {
assert(ppoll_result == 1); // Should always be true, but it's a good idea to check anyway
// call recv
}
注意:通常,在使用poll
/ppoll
时,我们会检查pollfd.events
以查看导致其唤醒的事件类型,但这不是必需的,因为您只等待一个套接字。使用poll
/ppoll
时,可以一次等待多个套接字。
注意:这不是等待带有poll
信号的唯一方法。您可以使用signalfd
,它将信号"转换"成看起来像套接字的东西。
您可以创建缓冲信号的signalfd
,而无需安装信号处理程序。请参阅man signalfd
中的示例。
您需要使用select
/poll
/epoll
事件循环来等待信号或套接字 fd 准备好读取,然后在非阻塞模式下读取它们直到EAGAIN
,就像这里的其他答案建议的那样。
- Qt VTK交互风格的信号到小部件
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 在UNIX系统中使用DIR查找文件的字节大小
- 当套接字连接断开时检测C/C++Unix
- 如何将点击的信号和插槽添加到qt中的自定义按钮中
- 如何在没有信号的情况下从C++执行QML插槽
- 线程之间的布尔停止信号
- C++:floor unix时间戳到UTC月份
- 如何在C/C++中用FD_set Unix设置套接字文件描述符
- 如何在信号处理程序和普通函数中对全局变量进行互斥读写操作
- 当用户在qtablewidget中输入单元格时,如何获得信号?C++
- 有可能在信号处理程序中设置promise吗
- 代码在我的计算机上运行良好,但是在将其提交给coursera时遇到未知的信号11问题
- 在条件变量中触发错误信号的频率是多少
- 外壳包装器句柄/执行交互式命令管道C++ UNIX
- 如何"buffer" UNIX 信号
- UNIX信号 - 无法设置初始值
- 用unix信号优雅地终止Qt应用程序
- ::write()和::read()没有被声明为错误.Qt5 UNIX信号处理
- 在c++中,我可以使用多线程工具来同步UNIX信号与我的主程序