【问题标题】:Porting a C code to Windows from POSIX (problem with siginfo)将 C 代码从 POSIX 移植到 Windows(siginfo 问题)
【发布时间】:2021-05-26 16:50:22
【问题描述】:

我正在尝试在 Windows 下本地编译程序“MRCC”(为 Linux 开发)。该程序主要是用 Fortran 编写的,而与系统的接口据我所知是用 C 编写的。除了一个导致问题的 C 源之外,所有源文件都可以使用 mingw64-gnu 编译器成功编译。问题出在 mingw64 中未实现的类型“siginfo_t”。

源文件(signal.c)是:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>


#ifdef __cplusplus
extern "C" {
#endif

#ifdef INT64
void mrccend_(long*);
void dmrccend_(long*);
long ERROR_SIGTERM = -1;
long ERROR_SIGSEGV = -2;
#else
void mrccend_(int*);
void dmrccend_(int*);
int ERROR_SIGTERM = -1;
int ERROR_SIGSEGV = -2;
#endif

void parent_sigterm_(int, siginfo_t *, void *);
void child_sigterm_(int, siginfo_t *, void *);
void child_sigsegv_(int, siginfo_t *, void *);
void sendSignalToChildren(int);

static struct sigaction old_act;

void signalinit_() {
// initialise dmrcc's responses to signals
   struct sigaction act_term;
   memset(&act_term, '\0', sizeof(act_term));
   act_term.sa_sigaction = &parent_sigterm_;
   act_term.sa_flags = SA_SIGINFO;
   sigaction(SIGTERM, &act_term, NULL);
}

void parent_sigterm_(int signum, siginfo_t *siginfo, void *context) {
// initialise response to signal SIGTERM
   pid_t pid_parent;
   char pidchar[10];
   char command[40];

   pid_parent = getpid();
   sprintf(pidchar, "%d", pid_parent);

   printf("\n Program dmrcc recieved SIGTERM\n"); fflush(stdout);

   sendSignalToChildren(SIGTERM);
   sleep(5);
   sendSignalToChildren(SIGKILL);

   printf("\n Program dmrcc terminating\n"); fflush(stdout);
   dmrccend_(&ERROR_SIGTERM);
}

void sendSignalToChildren(int sig) {
   int ownPid = getpid();
   int pid, numPids = 0;
   int *pids;
   FILE *pidfile = fopen("pids", "r");
   if (pidfile == NULL) {
      printf("Error: Could not open pids file\n");
      return;
   }

   // number of running processes other than the current process
   while (fscanf(pidfile, "%d", &pid) != EOF) {
      if (pid != ownPid) {
         numPids++;
      }
   }
   rewind(pidfile);

   // read other process' PIDs
   pids = (int *)malloc(numPids * sizeof(int));
   int i = -1;
   while (fscanf(pidfile, "%d", &pid) != EOF) {
      if (pid != ownPid) {
         pids[++i] = pid;
      }
   }

   // send signal sig to processes
   printf("\n Sending signal %2d to child processes\n", sig); fflush(stdout);
   for (i = 0; i < numPids; i++) {
      kill((pid_t)pids[i], sig);
   }
   
   fclose(pidfile);
   free(pids);
}

void signalinitchild_() {
// initialise child's responses to signals
   struct sigaction act_term;
   memset (&act_term, '\0', sizeof(act_term));
   act_term.sa_sigaction = &child_sigterm_;
   act_term.sa_flags = SA_SIGINFO;
   sigaction(SIGTERM, &act_term, NULL);

   struct sigaction act_segv;
   memset (&act_segv, '\0', sizeof(act_segv));
   act_segv.sa_sigaction = &child_sigsegv_;
   act_segv.sa_flags = SA_SIGINFO;
   sigaction(SIGSEGV, &act_segv, &old_act);
}

void child_sigterm_(int signum, siginfo_t *siginfo, void *context) {
// initialise child's response to signal SIGTERM
   mrccend_(&ERROR_SIGTERM);
}

void child_sigsegv_(int signum, siginfo_t *siginfo, void *context) {
// initialise child's response to signal SIGSEGV
   mrccend_(&ERROR_SIGSEGV);
   if(old_act.sa_flags & SA_SIGINFO)
   {
      (*old_act.sa_sigaction)(signum, siginfo, context);
   } 
   else 
   { 
      (*old_act.sa_handler)(signum);
   }
}

#ifdef __cplusplus
}
#endif

Windows 原生支持 SIGTERM 和 SIGSEGV。但是,SIGINFO 不是,所以没有定义 siginfo_t 类型。编译器还会在 sigaction 处引发错误。我知道我需要更改代码,以便它使用 Windows API 而不是 POSIX API。但是,我不知道该怎么做。我可以用 void 类型替换 siginfo_t 类型吗?我可以对 sigaction 做些什么?

[我正在使用的来自 mingw64 的 signal.h 标头已粘贴here]

【问题讨论】:

  • 那些信号处理功能是一场等待发生的灾难。 Footnote 188 of the C11 standard:“因此,信号处理程序通常不能调用标准库函数。” POSIX 允许从信号处理程序中调用异步信号安全函数。 Windows 提供了类似的功能列表。 printf()fflush()fopen()fscanf()fclose()free()malloc() 都不是永远可以安全地从我见过的任何系统上的信号处理程序,sleep()sprintf() 通常也是不安全的。
  • @Thomas 是的,我看过那篇文章,但我对 C 的了解还不够,不知道如何将它应用到我的案例中。
  • @AndrewHenle 那么,我该怎么办?代码是包的一部分,所以我不能全部修改。只有非常少量的 C 源代码,将其全部发布是否有帮助?
  • 顺便说一句,SIGINFOsiginfo_t 是不相关的结构。一个人的存在与否并不能决定另一个人的存在与否。

标签: c linux posix porting sigaction


【解决方案1】:

您可以将 Gnulib 添加到您的项目中。

“Gnulib 修复的可移植性问题: 某些平台上缺少 struct sigaction 和 siginfo_t:mingw、MSVC 14。” https://www.gnu.org/software/gnulib/manual/gnulib.html#signal_002eh

如果您在使用 gnulib 编译 mingw 项目时遇到问题,这些链接可能会有所帮助

Cross-compile a gnulib based project to MinGW

https://courses.p2pu.org/en/groups/cross-linux-from-scratch/content/set-up-your-build-environment/

另一种选择是尝试使用cygwin,它带来了更多的 POSIX 兼容性。

【讨论】:

    【解决方案2】:

    重写它以在 Windows 上使用 signal

    (在底层,这就是 Gnulib 为 mingw 平台所做的事情:根据 signal 重新实现 sigaction,使用已定义但实际上是虚拟的 siginfo_t 结构来解决过去的编译问题。)

    虽然标准做法是use sigaction in preference to signal——事实上,我想说现在使用signal 几乎是疏忽——你必须使用你所拥有的,并且你没有一个符合标准的 SA_SIGINFO 实现。您不必担心重新安装处理程序,因为您打算根据您处理的信号终止。

    所以,当你完成后,它看起来像这样:

    static void (*old_segv_handler)(int) = NULL;
    
    void parent_sigterm_(int s) { ... }
    
    void child_sigterm_(int s) { ... }
    
    void child_segv_(int s) {
        mrccend_(...);
        old_segv_handler(s); // FIXME: handle SIG_IGN, SIG_DFL, SIG_ERR
    }    
    
    void signalinit_() {
        signal(SIGTERM, parent_sigterm_);
    }
    void signalinitchild_() {
        signal(SIGTERM, child_sigterm_);
        old_segv_handler = signal(SIGSEGV, child_segv_);
    }
    

    请务必在您的父 SIGTERM 处理程序中解决上述 FIXME 和 remove those unsafe stdio calls

    【讨论】:

    • 感谢您的回答顺便说一句。我无法弄清楚第一行在做什么。您是否将 null 分配给 old_segv_handler 的指针?对不起,我是 C 新手,所以我不明白这一点。你能解释一下那条线在做什么吗?
    • old_segv_handler 是一个指向函数的指针,该函数采用 int 并且不返回任何内容,即基本信号处理函数。
    • 谢谢!你对如何处理程序后半部分使用的kill函数有什么建议吗?它将信号发送到子进程,但 windows 编译器不支持它。有 TerminateProcess() 但它不发送信号,它只是关闭进程。我需要一种向子进程发送信号的方法。
    • 我能再问一次你所说的 fixme 是什么意思吗?如果我理解正确的话,old_segv_handler 采用前一个函数的值,该函数将采用信号 SIGEGV,所以 old_segv_handler 将是 SIG_DFL 或 SIG_IGN 或 SIG_ERR。那么,我将如何处理它们呢?我不能只将信号(int s)传递给函数吗?
    • 它们不是函数,因此不能被调用。你必须自己模拟他们的行为。 SIG_ERR 有点像红鲱鱼——这意味着你对 signal 的调用失败了。当你的程序不能做你想做的事情时,你想让你的程序做什么?