我想开发一个 C 代码,它将同时等待键盘输入和键盘生成的信号,并且能够使其他 I/O(带有管道和文件)处于活动状态。
我有一个最小的信号处理程序,它会增加一个标志(并且当前提供调试):
void Handler (int signo)
{
char msg[80];
ss->nTstp++;
sprintf (msg, "\n%s .. Called Handler %d sig %d ..\n",
TS(), ss->nTstp, signo);
write (STDERR_FILENO, msg, strlen (msg));
}
我这样设置处理程序:
sigemptyset (& ss->sa.sa_mask);
sigaddset (& ss->sa.sa_mask, SIGTSTP);
ss->sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
ss->sa.sa_handler = Handler;
rc = sigaction (SIGTSTP, & ss->sa, NULL);
我的困境是信号被直接确认,但我的终端输入会自动重新启动。在按 Enter 键之前,我无法对处理程序标志执行操作。
如果我不设置 SA_RESTART,我会在终端输入上收到 EINTR,但我相信我也必须在每个其他文件描述符上期待 EINTR(并且必须编写可重新启动的传输)。
有没有办法在特定文件描述符上禁用SA_RESTART,或者有办法保证其他fd不会得到EINTR?
答案1
如果我理解您的需求,您希望能够处理(1)来自键盘的输入,(2)信号,以及(3)潜在的其他事件源。如果这是正确的,那么这可能就是您所追求的开始:
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static int pipe_fds[2];
static void handler(const int signo)
{
#define msg "received SIGTSTP\n"
write(pipe_fds[1], msg, strlen(msg));
#undef msg
}
int main(void)
{
if (pipe(pipe_fds) < 0) {
perror("pipe");
}
struct sigaction sa = {
.sa_handler = handler,
.sa_flags = SA_RESTART | SA_NOCLDSTOP,
};
if (sigemptyset(&sa.sa_mask) < 0) {
perror("sigemptyset");
return 1;
}
if (sigaction(SIGTSTP, &sa, NULL) < 0) {
perror("sigaction");
return 1;
}
struct pollfd fds[] = {
{
.fd = STDIN_FILENO,
.events = POLL_IN,
},
{
.fd = pipe_fds[0],
.events = POLL_IN,
},
};
const int num_fds = 2;
char buffer[1024] = {};
for (;;) {
const int ret = poll(fds, num_fds, -1);
if (ret < 0) {
if (errno == EINTR) {
// Receiving SIGTSTP can cause poll() to return
// -1 with errno = EINTR. Ignore that.
continue;
}
perror("poll");
return 1;
}
for (int i = 0; i < num_fds; ++i) {
if (fds[i].revents & POLL_IN) {
const int count = read(fds[i].fd, buffer, sizeof(buffer) - 1);
buffer[count - 1] = '\0';
printf("Read '%s' from file descriptor %d\n", buffer, fds[i].fd);
}
}
}
return 0;
}
该main()
函数首先创建一个管道;该程序使用管道在信号处理函数(在信号上下文中)和主程序之间进行通信。接下来,它设置信号的信号处理程序SIGTSTP
,就像上面描述的那样。
之后,它创建一个struct pollfd
名为的数组fds
。该数组中的每个条目对应于程序有兴趣监视活动的文件描述符。数组中的第一个条目是标准输入的文件描述符。第二个是上述管道的读取端。如果您想扩展此示例以处理其他事件源(与文件描述符关联的事件),那么这就是执行此操作的位置,只需fds
使用适当的文件描述符向数组添加其他元素即可。
然后它使用 进入事件循环poll
。超时后-1
,poll
将阻塞,直到 (1) 它在注册的文件描述符之一上获得活动或 (2) 信号中断它(例如收到SIGTSTP
)。因此,程序会检查 的返回值poll
,如果它小于 0,则显式检查并忽略EINTR
(被系统调用中断)错误。
如果poll()
由于活动而返回,则revents
关联的字段struct pollfd
将被相应标记。然后它将从关联的文件描述符中读取并打印一条消息。
这是一个示例运行:
$ ./a.out
Hello!
Read 'Hello!' from file descriptor 0
How are you?
Read 'How are you?' from file descriptor 0
^ZRead 'received SIGTSTP' from file descriptor 3
Good
Read 'Good' from file descriptor 0
^C
$
在示例运行中,它Hello!
从How are you?
键盘读取。在这两种情况下,程序都会通过读取我键入的内容并打印响应来做出响应。接下来,我生成SIGTSTP
信号,它读取received SIGTSTP
从管道读取并打印响应。接下来,它Good
从键盘读取并打印响应。最后我用 中断了程序Ctrl-C
,程序终止了。
请注意,有可能read
返回更少的可用字节。为简单起见,我没有检查该条件。根据您想要处理的事件源,您可能需要这样做。