Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Demonstrate compiler's optimization effects when volatile keyword is not used during signal handling?

In the following code, I have not made the variable quit has volatile sig_atomic_t. I have left it as a plain int.

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

#define UNUSED(x) (void) (x)

int quit;

void sigusr1_handler(int sig)
{
    UNUSED(sig);
    write(1, "handler\n", 8);
    quit = 1;
}

int main()
{
    struct sigaction sa;

    sa.sa_handler = sigusr1_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    quit = 0;
    while (!quit) {
        printf("Working ...\n");
        sleep(1);
    }

    printf("Exiting ...\n");
    return 0;
}

Since the quit variable is not specified as volatile, I was expecting that the compiler's optimizer would optimize the while-loop in the code to:

    while (1) {
        printf("Working ...\n");
        sleep(1);
    }

But I don't see this happening. On one terminal, I run the following.

$ gcc -O3 foo.c && ./a.out 
Working ...
Working ...

On another terminal, I send SIGUSR1 to my program.

$ pkill -USR1 a.out

On the first terminal, the output shows that the program's signal handler is invoked and the while-loop quits.

Working ...
Working ...
handler
Exiting ...
$ 

How can I demonstrate the optimization of the loop due to quit not being volatile?

like image 560
Lone Learner Avatar asked Nov 07 '16 02:11

Lone Learner


1 Answers

It can be difficult to force the compiler to optimize the load of quit out from the while conditional. I was able to do it by removing the body of the while loop:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

#define UNUSED(x) (void) (x)

int quit;

void sigusr1_handler(int sig)
{
    UNUSED(sig);
    write(1, "handler\n", 8);
    quit = 1;
}

int main()
{
    struct sigaction sa;

    sa.sa_handler = sigusr1_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    quit = 0;
    while (!quit) {
        //printf("Working ...\n");
        //sleep(1);
    }

    printf("Exiting ...\n");
    return 0;
}

When compiling for x86-64 with -O3 on gcc version 5.4 this results in an infinite loop which doesn't check the quit variable. Notice the jump-to-self at .L5:

.LC0:
        .string "handler\n"
sigusr1_handler(int):
        sub     rsp, 8
        mov     edx, 8
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, 1
        call    write
        mov     DWORD PTR quit[rip], 1
        add     rsp, 8
        ret
.LC2:
        .string "sigaction"
main:
        sub     rsp, 168
        lea     rdi, [rsp+8]
        mov     QWORD PTR [rsp], OFFSET FLAT:sigusr1_handler(int)
        mov     DWORD PTR [rsp+136], 0
        call    sigemptyset
        xor     edx, edx
        mov     rsi, rsp
        mov     edi, 10
        call    sigaction
        cmp     eax, -1
        je      .L7
        mov     DWORD PTR quit[rip], 0
.L5:
        jmp     .L5
.L7:
        mov     edi, OFFSET FLAT:.LC2
        call    perror
        mov     eax, 1
        add     rsp, 168
        ret
quit:
        .zero   4

Changing the definition to volatile int quit; causes the correct behavior. See the mov, test, and je instructions at .L6:

.LC0:
        .string "handler\n"
sigusr1_handler(int):
        sub     rsp, 8
        mov     edx, 8
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, 1
        call    write
        mov     DWORD PTR quit[rip], 1
        add     rsp, 8
        ret
.LC2:
        .string "sigaction"
.LC3:
        .string "Exiting ..."
main:
        sub     rsp, 168
        lea     rdi, [rsp+8]
        mov     QWORD PTR [rsp], OFFSET FLAT:sigusr1_handler(int)
        mov     DWORD PTR [rsp+136], 0
        call    sigemptyset
        xor     edx, edx
        mov     rsi, rsp
        mov     edi, 10
        call    sigaction
        cmp     eax, -1
        je      .L11
        mov     DWORD PTR quit[rip], 0
.L6:
        mov     eax, DWORD PTR quit[rip]
        test    eax, eax
        je      .L6
        mov     edi, OFFSET FLAT:.LC3
        call    puts
        xor     eax, eax
.L5:
        add     rsp, 168
        ret
.L11:
        mov     edi, OFFSET FLAT:.LC2
        call    perror
        mov     eax, 1
        jmp     .L5
quit:
        .zero   4

You can play with both examples here: without volatile and with volatile.

like image 97
Tim Avatar answered Oct 03 '22 09:10

Tim