【问题标题】:Code shows different behaviour when optimized优化时代码显示不同的行为
【发布时间】:2020-01-31 21:35:20
【问题描述】:

总结

我尝试编写一个 Monte Carlo 模拟代码,该模拟分叉到最多数量的核心进程。在一定时间后,父级将 SIGUSR1 发送给所有子级,然后这些子级应停止计算将结果发送回父级。

当我在没有任何优化的情况下编译 (clang thread_stop.c) 时,行为符合预期。当我尝试优化代码 (clang -O1 thread_stop.c) 时,信号被捕获,但孩子们并没有停下来。

代码

我将代码缩减为行为相同的最小部分:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>  /* pid_t */
#include <sys/mman.h>   /* mmap */

#define MAX 1           /* Max time to run */

static int a=0; /* int to be changed when signal arrives */

void sig_handler(int signo) {
    if (signo == SIGUSR1){
        a=1;
        printf("signal caught\n");
    }
}

int main(void){

    int * comm;
    pid_t pid;

    /* map to allow child processes access same array */
    comm = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
                    MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    *comm = 0;
    pid=fork();
    if(pid == 0){ /* child process */
        signal(SIGUSR1, sig_handler); /* catch signal */ 

        do {
            /* do things */
        } while(a == 0);

        printf("Child exit(0)\n");
        *comm = 2;
        exit(0); /* exit for child process */
    } /* if(pid == 0) - code below is parent only */

    printf("Started child process, sleeping %d seconds\n", MAX);
    sleep(MAX);
    printf("Send signal to child\n");
    kill(pid, SIGUSR1); /* send SIGUSR1 */
    while(*comm != 2) usleep(10000);
    printf("Child process ended\n");

/* clean up */

    munmap(comm, sizeof(int));
    return 0;
}

系统

clang 在 termux (clang 9.0.1) 和 lubuntu (clang 6.0.0-lubuntu2) 上显示了这一点。

【问题讨论】:

  • 我认为您需要使用volatile sig_atomic_t 而不是static int
  • 还要注意printf 不是signal safe function
  • 如果您的代码正确,优化不会改变结果,但可能会改变获得结果的速度。如果您的代码不正确,优化器可能会根据更改结果的未定义行为做出决定,但就编译器而言没有问题,因为对于未定义行为,任何结果都是有效的。我没有查看您的代码来发现未定义行为的位置,但如果优化改变了结果,则可能存在未定义行为的问题。
  • @M.M — 这不是我说的吗?

标签: c optimization clang fork


【解决方案1】:

在异步调用的信号处理程序中可以执行的操作存在一些限制。在您的代码中,这是因为 kill 是从单独的进程中调用的。

在 ISO C 中,唯一允许的可观察操作是修改 sig_atomic_t 类型的变量。

In POSIX there is a bit more leniency:

如果信号处理程序引用除 errno 以外的任何具有静态存储持续时间的对象,而不是通过将值分配给声明为 volatile sig_atomic_t 的对象,或者如果信号处理程序调用本标准中定义的任何函数,则行为未定义下表中列出的函数之一。

下表定义了一组异步信号安全的函数。因此,应用程序可以不受限制地从信号捕获函数中调用它们。请注意,虽然调用本身没有限制,但对于某些函数,在从信号捕获函数调用该函数后的后续行为有限制(参见 longjmp)。

printf 函数不在表中,因此您的程序在执行信号时会导致未定义的行为(这意味着可能会出现意外结果)。


因此,您需要停止在信号处理程序中调用printf,并将a 更改为具有volatile sig_atomic_t 类型。

内存位置*comm 也存在竞争条件。一个线程读取它,而另一个线程可能同时写入它,没有同步。但是,我无法在 POSIX 文档中找到这样做的后果。

【讨论】:

    【解决方案2】:

    更改为 volatile sig_atomic_t 已治愈。感谢您的快速帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-28
      • 2020-04-09
      • 1970-01-01
      相关资源
      最近更新 更多