kra*_*mir 1 c linux posix signals
我刚刚使用 GNU 异步 I/O 和信号实现了异步文件读取。我使用带有回调处理程序(SIGUSR1 目标)的信号处理结果:
static
void aioSigHandler(int sig, siginfo_t *si, void *ucontext)
{
struct aioRequest *request = si->si_value.sival_ptr;
int bytes_read = aio_return(request->aiocbp);
printf("I/O completion signal received %d: %.*s\n", bytes_read, bytes_read, request->aiocbp->aio_buf);
// continue reading if whole buffer was filled
if(bytes_read == BUF_SIZE) {
request->aiocbp->aio_offset += bytes_read;
if (aio_read(request->aiocbp) == -1)
errExit("aio_read");
} else {
request->finished = 1;
}
}
Run Code Online (Sandbox Code Playgroud)
我想知道如果有人将 SIGUSR1 发送到我的进程会发生什么。显然它不会siginfo_t *si填充我的结构实例,因此我读取了垃圾,幸运的是程序会立即以段错误结束。在糟糕的情况下,其他一些数据会被破坏,并且不会检测到错误。我该如何防范?
完整来源:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <aio.h>
#include <signal.h>
#include <fcntl.h>
#define BUF_SIZE 4 /* Size of buffers for read operations */
#define IO_SIGNAL SIGUSR1 /* Signal used to notify I/O completion */
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)
#define errMsg(msg) do { perror(msg); } while (0)
static volatile sig_atomic_t gotSIGQUIT = 0;
struct aioRequest {
int finished;
struct aiocb *aiocbp;
};
static void aioSigHandler(int sig, siginfo_t *si, void *ucontext);
static const char * aioStatusToString(int status);
static struct aioRequest * aioReadingStart(const char *filename);
static void quitHandler(int sig);
int
main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
exit(EXIT_FAILURE);
}
struct sigaction sa;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
sa.sa_handler = quitHandler;
if (sigaction(SIGQUIT, &sa, NULL) == -1)
errExit("sigaction");
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sa.sa_sigaction = aioSigHandler;
if (sigaction(IO_SIGNAL, &sa, NULL) == -1)
errExit("sigaction");
struct aioRequest *request = aioReadingStart(argv[1]);
while (1) {
sleep(3); /* Delay between each monitoring step */
if(request->finished) {
break;
}
int status = aio_error(request->aiocbp);
if (status != EINPROGRESS) {
printf("aio_error() for request (descriptor %d): ", request->aiocbp->aio_fildes);
printf("%s\n",aioStatusToString(status));
break;
}
if (gotSIGQUIT) {
/* On receipt of SIGQUIT, attempt to cancel I/O requests,
* and display status returned
* from the cancellation request */
printf("got SIGQUIT; canceling I/O request: ");
int s = aio_cancel(request->aiocbp->aio_fildes, request->aiocbp);
if (s == AIO_CANCELED)
printf("I/O canceled\n");
else if (s == AIO_NOTCANCELED)
printf("I/O not canceled\n");
else if (s == AIO_ALLDONE)
printf("I/O all done\n");
else
errMsg("aio_cancel");
gotSIGQUIT = 0;
}
}
printf("File reading completed\n");
exit(EXIT_SUCCESS);
}
static
void aioSigHandler(int sig, siginfo_t *si, void *ucontext)
{
struct aioRequest *request = si->si_value.sival_ptr;
int bytes_read = aio_return(request->aiocbp);
printf("I/O completion signal received %d: %.*s\n", bytes_read, bytes_read, request->aiocbp->aio_buf);
// continue reading if whole buffer was filled
if(bytes_read == BUF_SIZE) {
request->aiocbp->aio_offset += bytes_read;
if (aio_read(request->aiocbp) == -1)
errExit("aio_read");
} else {
request->finished = 1;
}
}
static
const char * aioStatusToString(int status) {
switch (status) {
case 0:
return "I/O succeeded\n";
case EINPROGRESS:
return "In progress\n";
case ECANCELED:
return "Canceled\n";
default:
errMsg("aio_error");
return 0;
}
}
static
struct aioRequest * aioReadingStart(const char *filename) {
struct aioRequest *request = malloc(sizeof(struct aioRequest));
struct aiocb *aiocbInstance = malloc(sizeof(struct aiocb));
if (request == NULL || aiocbInstance == NULL)
errExit("malloc");
request->finished = 0;
request->aiocbp = aiocbInstance;
request->aiocbp->aio_fildes = open(filename, O_RDONLY);
if (request->aiocbp->aio_fildes == -1)
errExit("open");
printf("opened %s on descriptor %d\n", filename,
request->aiocbp->aio_fildes);
request->aiocbp->aio_buf = malloc(BUF_SIZE);
if (request->aiocbp->aio_buf == NULL)
errExit("malloc");
request->aiocbp->aio_nbytes = BUF_SIZE;
request->aiocbp->aio_reqprio = 0;
request->aiocbp->aio_offset = 0;
request->aiocbp->aio_sigevent.sigev_notify = SIGEV_SIGNAL;
request->aiocbp->aio_sigevent.sigev_signo = IO_SIGNAL;
request->aiocbp->aio_sigevent.sigev_value.sival_ptr = request;
if (aio_read(request->aiocbp) == -1)
errExit("aio_read");
return request;
}
static void
quitHandler(int sig) {
gotSIGQUIT = 1;
}
Run Code Online (Sandbox Code Playgroud)
为了专注于上述问题,我将把我的建议限制在信号处理方面。
考虑使用实时信号(SIGRTMIN+0to SIGRTMAX-0,包括)而不是SIGUSR1。标准信号SIGUSR1如未排队,因此您可能会丢失它们(如果在另一个相同信号触发时您已经有一个待处理),但实时信号已排队,并且更加可靠。有关man 7 signal详细信息,请参阅 中的实时信号部分。
还要考虑errno在信号处理程序的开头保存,并在返回之前恢复它。否则,在某些errno极端情况下,信号传递可能会“损坏” (因为您的信号处理程序会隐式修改它),这很难调试——简单地说,在某些情况下,errno您认为是由于系统调用失败而分配的, 实际上是由您的信号处理程序重置的。
(语言律师可能会指出,访问线程局部变量(errno通常是一个)是非异步信号安全的,至少在理论上是这样。实际上它是安全的,尤其是当线程已访问线程局部变量时在信号之前。有关 glibc 的更多详细信息,请参阅libc-alpha 邮件列表中的此线程。就个人而言,我创建的 pthreads 小于默认堆栈(默认值对于典型的工作线程来说太大了),并确保线程函数读取和写入线程局部变量作为第一件事,在实践中避免任何线程局部非异步信号安全问题。这也适用于主线程。简而言之,如果线程局部变量已知在信号传递之前分配和可用,它们的用途是,在实践中,异步信号安全。最后,异步信号安全函数,例如read()和write()do 在errno 内部修改,没有任何特殊处理,所以如果它们是异步信号安全的,那么恢复errno也必须如此。)
正如man 7 signal手册页中所述,以及 Andrew Henle 在对原始问题的评论中提到的,只有异步信号安全函数可以安全地在信号处理程序中使用。既不是异步信号安全的,aio_read()也不printf()是异步信号安全的。
请注意,read(2)和write(2)是异步信号安全的,并且可以与例如一起使用。匿名套接字对到信息包(描述事件)转移到一个事件处理线程,或打印(调试)信息到标准输出或标准误差(STDOUT_FILENO和STDERR_FILENO描述符,分别地)。
如果您绝对需要使用非异步信号安全函数,请阻塞这些信号,并创建一个sigwaitinfo()用于处理信号的辅助线程。这不一定适用于 Linux 上的线程目标信号,我个人会使用信号处理程序,GCC 原子内置函数(幸运的是,大多数 C 编译器都支持它们)来维护事件队列,例如sem_post()唤醒事件处理线程。这里有几个设计选项,到目前为止,即使是我遇到的古怪问题也总是可以使用相对简单的方法来解决。
如man 2 sigaction手册页所述,您可以检查si->code以找出信号的原因;这将是SI_ASYNCIO对于AIO完井,POLL_IN/ POLL_OUT/ POLL_MSG/ POLL_ERR/ POLL_PRI/POLL_HUP对SIGIO信号,SI_KERNEL对于其他内核发送的信号,依此类推。如果si->code是SI_USER或SI_QUEUE,您可以检查si->pid以找出哪个进程发送了信号。
还建议在设置任何字段之前清除整个struct sigaction通孔memset(&sa, 0, sizeof sa);。(这是因为某些字段可能是也可能不是联合;将整个结构清零可确保未使用字段的“安全”值。)
嗯,我是不是忘记了什么?如果您发现我遗漏了什么,请在评论中告诉我,以便我修复此答案。