your programing

신호 처리기에서 printf를 사용하지 않는 방법은 무엇입니까?

lovepro 2020. 10. 12. 08:01
반응형

신호 처리기에서 printf를 사용하지 않는 방법은 무엇입니까?


printf재진입이 아니기 때문에 신호 처리기에서 사용하는 것은 안전하지 않습니다. 하지만 printf이런 식으로 사용하는 예제 코드를 많이 보았습니다 .

그래서 제 질문은 printf신호 처리기에서 사용을 피해야 할 때이며 권장되는 대체품이 있습니까?


일부 플래그 변수를 사용하고, 신호 처리기 내부에서 해당 플래그를 설정하고, printf()정상적인 작동 중에 main () 또는 프로그램의 다른 부분에서 해당 플래그 호출 함수를 기반으로 할 수 있습니다.

printf신호 처리기 내에서 와 같은 모든 함수를 호출하는 것은 안전하지 않습니다 . 유용한 기술은 신호 처리기를 사용하여 a를 설정 한 flag다음 flag주 프로그램에서 확인하고 필요한 경우 메시지를 인쇄하는 것입니다.

아래 예제에서 신호 처리기 ding () alarm_fired은 SIGALRM이 포착되고 주 함수 alarm_fired에서 printf를 조건부로 올바르게 호출하기 위해 검사 될 때 플래그 를 1로 설정 합니다.

static int alarm_fired = 0;
void ding(int sig) // can be called asynchronously
{
  alarm_fired = 1; // set flag
}
int main()
{
    pid_t pid;
    printf("alarm application starting\n");
    pid = fork();
    switch(pid) {
        case -1:
            /* Failure */
            perror("fork failed");
            exit(1);
        case 0:
            /* child */
            sleep(5);
            kill(getppid(), SIGALRM);
            exit(0);
    }
    /* if we get here we are the parent process */
    printf("waiting for alarm to go off\n");
    (void) signal(SIGALRM, ding);
    pause();
    if (alarm_fired)  // check flag to call printf
      printf("Ding!\n");
    printf("done\n");
    exit(0);
}

참조 : Beginning Linux Programming, 4th Edition ,이 책에서는 코드가 정확히 설명되어 있습니다 (원하는 것), 11 장 : 프로세스 및 신호, 484 페이지

또한 비동기 적으로 호출 될 수 있으므로 핸들러 함수를 작성할 때 특별한주의가 필요합니다. 즉, 처리기는 프로그램의 어느 지점에서나 예측할 수없이 호출 될 수 있습니다. 매우 짧은 간격 동안 두 신호가 도착하면 하나의 핸들러가 다른 핸들러 내에서 실행될 수 있습니다. 그리고 선언하는 것이 더 나은 방법으로 간주됩니다 volatile sigatomic_t.이 유형은 항상 원자 적으로 액세스되며 변수에 대한 액세스 중단에 대한 불확실성을 방지합니다. ( 자세한 만료를위한 원자 데이터 액세스 및 신호 처리 읽기 ).

신호 처리기 정의 읽기 : signal()또는 sigaction()함수 로 설정할 수있는 신호 처리기 함수를 작성하는 방법을 배웁니다 . 매뉴얼 페이지
의 인증 된 함수 목록 , 신호 처리기 내에서이 함수를 호출하는 것은 안전합니다.


주요 문제는 신호가 중단 malloc()되거나 유사한 기능 이있는 경우 사용 가능한 목록과 사용 된 목록 또는 기타 유사한 작업간에 메모리 블록을 이동하는 동안 내부 상태가 일시적으로 일치하지 않을 수 있다는 것입니다. 신호 처리기의 코드가를 호출하는 함수를 호출 malloc()하면 메모리 관리가 완전히 망가질 수 있습니다.

C 표준은 신호 처리기에서 수행 할 수있는 작업에 대해 매우 보수적 인 관점을 취합니다.

ISO / IEC 9899 : 2011 §7.14.1.1 signal기능

¶5 abort또는 raise함수 를 호출 한 결과가 아닌 다른 신호가 발생하는 경우, 신호 핸들러가 값을 할당하는 것 외에 잠금이없는 원자 객체가 아닌 정적 또는 스레드 저장 기간이있는 객체를 참조하면 동작이 정의되지 않습니다. 로 선언 된 객체로 선언 volatile sig_atomic_t되거나, 신호 핸들러가 abort함수, _Exit함수, quick_exit함수 또는 signal함수를 호출 한 신호에 해당하는 신호 번호와 동일한 첫 번째 인수가 있는 함수를 제외한 표준 라이브러리의 모든 함수를 호출합니다. 매니저. 또한 이러한 signal함수 호출로 인해 결과가 SIG_ERR반환되면의 값 errno은 불확실합니다. 252)

252) 신호가 비동기 신호 처리기에 의해 생성되는 경우 동작은 정의되지 않습니다.

POSIX는 시그널 핸들러에서 할 수있는 일에 대해 훨씬 더 관대합니다.

POSIX 2008 에디션의 신호 개념 은 다음과 같이 말합니다.

프로세스가 다중 스레드이거나 프로세스가 단일 스레드이고 신호 처리기가 다음의 결과가 아닌 다른 실행되는 경우 :

  • 프로세스 호출은 abort(), raise(), kill(), pthread_kill(), 또는 sigqueue()차단되지 않은 신호를 생성하도록

  • 보류중인 신호가 차단 해제되고 차단 해제 된 호출이 반환되기 전에 전달됩니다.

신호 처리기 errno가로 선언 된 객체에 값을 할당하는 것 외에 정적 저장 기간이 아닌 다른 객체 volatile sig_atomic_t를 참조하거나 신호 처리기가에 나열된 함수 중 하나가 아닌이 표준에 정의 된 함수를 호출하는 경우 동작은 정의되지 않습니다. 다음 표.

다음 표는 비동기 신호로부터 안전한 기능 세트를 정의합니다. 따라서 응용 프로그램은 제한없이 신호 포착 함수에서이를 호출 할 수 있습니다.

_Exit()             fexecve()           posix_trace_event() sigprocmask()
_exit()             fork()              pselect()           sigqueue()
fcntl()             pipe()              sigpause()          write()
fdatasync()         poll()              sigpending()

위 표에없는 모든 기능은 신호와 관련하여 안전하지 않은 것으로 간주됩니다. 신호가있는 경우 POSIX.1-2008의이 볼륨에 정의 된 모든 기능은 신호 포착 기능에서 호출되거나 중단 될 때 정의 된대로 동작해야합니다. 단, 신호가 안전하지 않은 기능을 중단하고 신호- catching 함수는 안전하지 않은 함수를 호출하지만 동작은 정의되지 않았습니다.

값을 얻는 errno작업과 값을 할당하는 작업 errno은 비동기 신호 안전이어야합니다.

신호가 스레드에 전달 될 때 해당 신호의 동작이 종료, 중지 또는 계속을 지정하면 전체 프로세스가 각각 종료, 중지 또는 계속됩니다.

그러나 printf()함수 계열은 해당 목록에 특히 없으며 신호 처리기에서 안전하게 호출되지 않을 수 있습니다.

POSIX 2,016 업데이트를 포함하는 안전 기능의 목록을 연장, 특히 발 기능의 다수 <string.h>특히 유용한 추가이다 (또는 특히 초조 감독이었다). 이제 목록은 다음과 같습니다.

_Exit()              getppid()            sendmsg()            tcgetpgrp()
_exit()              getsockname()        sendto()             tcsendbreak()
abort()              getsockopt()         setgid()             tcsetattr()
accept()             getuid()             setpgid()            tcsetpgrp()
access()             htonl()              setsid()             time()
aio_error()          htons()              setsockopt()         timer_getoverrun()
aio_return()         kill()               setuid()             timer_gettime()
aio_suspend()        link()               shutdown()           timer_settime()
alarm()              linkat()             sigaction()          times()
bind()               listen()             sigaddset()          umask()
cfgetispeed()        longjmp()            sigdelset()          uname()
cfgetospeed()        lseek()              sigemptyset()        unlink()
cfsetispeed()        lstat()              sigfillset()         unlinkat()
cfsetospeed()        memccpy()            sigismember()        utime()
chdir()              memchr()             siglongjmp()         utimensat()
chmod()              memcmp()             signal()             utimes()
chown()              memcpy()             sigpause()           wait()
clock_gettime()      memmove()            sigpending()         waitpid()
close()              memset()             sigprocmask()        wcpcpy()
connect()            mkdir()              sigqueue()           wcpncpy()
creat()              mkdirat()            sigset()             wcscat()
dup()                mkfifo()             sigsuspend()         wcschr()
dup2()               mkfifoat()           sleep()              wcscmp()
execl()              mknod()              sockatmark()         wcscpy()
execle()             mknodat()            socket()             wcscspn()
execv()              ntohl()              socketpair()         wcslen()
execve()             ntohs()              stat()               wcsncat()
faccessat()          open()               stpcpy()             wcsncmp()
fchdir()             openat()             stpncpy()            wcsncpy()
fchmod()             pause()              strcat()             wcsnlen()
fchmodat()           pipe()               strchr()             wcspbrk()
fchown()             poll()               strcmp()             wcsrchr()
fchownat()           posix_trace_event()  strcpy()             wcsspn()
fcntl()              pselect()            strcspn()            wcsstr()
fdatasync()          pthread_kill()       strlen()             wcstok()
fexecve()            pthread_self()       strncat()            wmemchr()
ffs()                pthread_sigmask()    strncmp()            wmemcmp()
fork()               raise()              strncpy()            wmemcpy()
fstat()              read()               strnlen()            wmemmove()
fstatat()            readlink()           strpbrk()            wmemset()
fsync()              readlinkat()         strrchr()            write()
ftruncate()          recv()               strspn()
futimens()           recvfrom()           strstr()
getegid()            recvmsg()            strtok_r()
geteuid()            rename()             symlink()
getgid()             renameat()           symlinkat()
getgroups()          rmdir()              tcdrain()
getpeername()        select()             tcflow()
getpgrp()            sem_post()           tcflush()
getpid()             send()               tcgetattr()

결과적 write()으로 printf()et al이 제공하는 서식 지원없이 사용 하거나 코드의 적절한 위치에서 (주기적으로) 테스트하는 플래그를 설정하게됩니다. 이 기술은 훌륭하게에서 증명되고 대답 하여 Grijesh 차우 .


표준 C 기능 및 신호 안전

chqrlie 흥미로운 질문에 대해 부분적인 답변 만 제공합니다.

대부분의 문자열 함수 <string.h>또는 문자 클래스 함수의 출처 <ctype.h>와 더 많은 C 표준 라이브러리 함수가 위 목록에없는 이유는 무엇입니까? 구현은 strlen()시그널 핸들러에서 호출 하는 것을 안전하지 않게 만들기 위해 의도적으로 악해야 합니다.

에 많은 기능을 위해 <string.h>, 그 이유는이 선언되지 않은 비동기 신호 안전을보고 열심히하고, 나는 동의 것 strlen()와 함께 대표적인 예이며 strchr(), strstr()등, 등 한편, 다른 기능 strtok(), strcoll()strxfrm()다소 복잡하고 비동기 신호에 안전하지 않을 수 있습니다. 때문에이 strtok()호출 사이에 상태를 유지하고, 신호 처리기 쉽게 사용하는 코드의 일부가 여부를 알 수없는 strtok()엉망이 될 것이다. strcoll()strxfrm()기능은 로케일에 민감한 데이터로 작업하고, 로케일을로드하는 것은 국가 설정의 모든 종류를 포함한다.

에서 기능 (매크로) <ctype.h>모든 로케일에 의존하며, 따라서 같은 문제로 실행 수 strcoll()strxfrm().

나는 하드에서 수학 함수는 이유를 찾을 <math.h>그들이 SIGFPE (부동 소수점 예외)에 의해 영향을받을 수 있기 때문에 유일한 시간에 대해 내가 그 요즘 볼 수 있지만 그것이 아닌 경우입니다, 비동기 시그널에 안전하지 않습니다 정수 0으로 나누기. 유사한 불확실성이 <complex.h>, <fenv.h>에서 발생합니다 <tgmath.h>.

예를 들어의 일부 기능은 <stdlib.h>제외 될 수 있습니다 abs(). 다른 것들은 특히 문제가 있습니다. malloc()가족이 대표적인 예입니다.

POSIX 환경에서 사용되는 표준 C (2011)의 다른 헤더에 대해서도 유사한 평가를 할 수 있습니다. (표준 C는 너무 제한적이므로 순수한 표준 C 환경에서 분석하는 데 관심이 없습니다.) '로케일 종속'으로 표시된 것은 로케일을 조작하는 데 메모리 할당 등이 필요할 수 있으므로 안전하지 않습니다.

  • <assert.h>아마도 안전하지 않을 것입니다.
  • <complex.h>아마도 안전함
  • <ctype.h> - 안전하지 않음
  • <errno.h> — 안전
  • <fenv.h>아마도 안전하지 않을 것입니다.
  • <float.h> — 기능 없음
  • <inttypes.h> — 로케일 구분 함수 (안전하지 않음)
  • <iso646.h> — 기능 없음
  • <limits.h> — 기능 없음
  • <locale.h> — 로케일 구분 함수 (안전하지 않음)
  • <math.h>아마도 안전함
  • <setjmp.h> - 안전하지 않음
  • <signal.h> — 허용됨
  • <stdalign.h> — 기능 없음
  • <stdarg.h> — 기능 없음
  • <stdatomic.h>안전함, 안전하지 않을 수 있음
  • <stdbool.h> — 기능 없음
  • <stddef.h> — 기능 없음
  • <stdint.h> — 기능 없음
  • <stdio.h> - 안전하지 않음
  • <stdlib.h> — 모두 안전하지는 않습니다 (일부는 허용되지만 다른 것은 허용되지 않음).
  • <stdnoreturn.h> — 기능 없음
  • <string.h> — 모두 안전하지 않음
  • <tgmath.h>아마도 안전함
  • <threads.h>아마도 안전하지 않을 것입니다.
  • <time.h>— 로케일 종속 (그러나 time()명시 적으로 허용됨)
  • <uchar.h> — 로케일에 따라 다름
  • <wchar.h> — 로케일에 따라 다름
  • <wctype.h> — 로케일에 따라 다름

POSIX 헤더를 분석하는 것은… 많은 것이 있고 일부 기능은 안전 할 수 있지만 많은 것은 안전하지 않을 수 있다는 점에서 더 어려울 것입니다. 그러나 POSIX가 어떤 기능이 비동기 신호 안전인지 (많은 것은 아님)를 말하므로 더 간단합니다. 같은 헤더 <pthread.h>에는 세 가지 안전한 기능과 많은 안전하지 않은 기능이 있습니다.

NB : POSIX 환경에서 C 기능 및 헤더에 대한 거의 모든 평가는 반 교육을받은 추측입니다. 표준 기관의 결정적인 진술은 의미가 없습니다.


printf신호 처리기에서 사용을 피하는 방법은 무엇입니까?

  1. 항상 피하십시오 printf(). 신호 처리기에 사용하지 마십시오 .

  2. POSIX 준수 시스템에 적어도, 당신이 사용할 수있는 write(STDOUT_FILENO, ...)대신에 printf(). 그러나 형식화가 쉽지 않을 수 있습니다. 쓰기 또는 비동기 안전 함수를 사용하여 신호 처리기에서 int를 인쇄합니다.


디버깅 목적으로, 실제로는 async-signal-safe목록의 함수 만 호출하는지 확인 하고 신호 컨텍스트 내에서 호출되는 안전하지 않은 각 함수에 대해 경고 메시지를 출력 하는 도구를 작성했습니다 . 신호 컨텍스트에서 비동기 안전이 아닌 함수를 호출하려는 문제를 해결하지는 못하지만 적어도 실수로 수행 한 경우를 찾는 데 도움이됩니다.

소스 코드는 GitHub에 있습니다 . 오버로딩 signal/sigaction한 다음 일시적 PLT으로 안전하지 않은 기능 항목을 가로 채서 작동합니다. 이로 인해 안전하지 않은 함수에 대한 호출이 래퍼로 리디렉션됩니다.


선택 루프있는 프로그램에서 특히 유용한 한 가지 기술은 신호 수신시 파이프에 단일 바이트를 기록한 다음 선택 루프에서 신호를 처리하는 것입니다. 이 줄을 따라가는 것 (오류 처리 및 기타 세부 사항은 간결함을 위해 생략 됨) :

static int sigPipe[2];

static void gotSig ( int num ) { write(sigPipe[1], "!", 1); }

int main ( void ) {
    pipe(sigPipe);
    /* use sigaction to point signal(s) at gotSig() */

    FD_SET(sigPipe[0], &readFDs);

    for (;;) {
        n = select(nFDs, &readFDs, ...);
        if (FD_ISSET(sigPipe[0], &readFDs)) {
            read(sigPipe[0], ch, 1);
            /* do something about the signal here */
        }
        /* ... the rest of your select loop */
    }
}

어떤 신호 인지 신경 쓰면 파이프 아래의 바이트가 신호 번호가 될 수 있습니다.


자체 비동기 신호 안전 구현 snprintf("%d및 사용write

내가 생각했던 것만 큼 나쁘지 않습니다 . C에서 int를 문자열로 변환하는 방법? 몇 가지 구현이 있습니다.

신호 처리기가 액세스 할 수있는 흥미로운 데이터 유형은 두 가지뿐입니다.

  • sig_atomic_t 글로벌
  • int 신호 인수

이것은 기본적으로 모든 흥미로운 사용 사례를 다룹니다.

strcpy또한 신호 안전 이라는 사실 은 상황을 더욱 개선합니다.

아래의 POSIX 프로그램은 지금까지 SIGINT를 수신 한 횟수를 stdout에 인쇄 Ctrl + C하고, 및 신호 ID로 트리거 할 수 있습니다 .

Ctrl + \(SIGQUIT)로 프로그램을 종료 할 수 있습니다 .

main.c :

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

/* Calculate the minimal buffer size for a given type.
 *
 * Here we overestimate and reserve 8 chars per byte.
 *
 * With this size we could even print a binary string.
 *
 * - +1 for NULL terminator
 * - +1 for '-' sign
 *
 * A tight limit for base 10 can be found at:
 * https://stackoverflow.com/questions/8257714/how-to-convert-an-int-to-string-in-c/32871108#32871108
 *
 * TODO: get tight limits for all bases, possibly by looking into
 * glibc's atoi: https://stackoverflow.com/questions/190229/where-is-the-itoa-function-in-linux/52127877#52127877
 */
#define ITOA_SAFE_STRLEN(type) sizeof(type) * CHAR_BIT + 2

/* async-signal-safe implementation of integer to string conversion.
 *
 * Null terminates the output string.
 *
 * The input buffer size must be large enough to contain the output,
 * the caller must calculate it properly.
 *
 * @param[out] value  Input integer value to convert.
 * @param[out] result Buffer to output to.
 * @param[in]  base   Base to convert to.
 * @return     Pointer to the end of the written string.
 */
char *itoa_safe(intmax_t value, char *result, int base) {
    intmax_t tmp_value;
    char *ptr, *ptr2, tmp_char;
    if (base < 2 || base > 36) {
        return NULL;
    }

    ptr = result;
    do {
        tmp_value = value;
        value /= base;
        *ptr++ = "ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[35 + (tmp_value - value * base)];
    } while (value);
    if (tmp_value < 0)
        *ptr++ = '-';
    ptr2 = result;
    result = ptr;
    *ptr-- = '\0';
    while (ptr2 < ptr) {
        tmp_char = *ptr;
        *ptr--= *ptr2;
        *ptr2++ = tmp_char;
    }
    return result;
}

volatile sig_atomic_t global = 0;

void signal_handler(int sig) {
    char key_str[] = "count, sigid: ";
    /* This is exact:
     * - the null after the first int will contain the space
     * - the null after the second int will contain the newline
     */
    char buf[2 * ITOA_SAFE_STRLEN(sig_atomic_t) + sizeof(key_str)];
    enum { base = 10 };
    char *end;
    end = buf;
    strcpy(end, key_str);
    end += sizeof(key_str);
    end = itoa_safe(global, end, base);
    *end++ = ' ';
    end = itoa_safe(sig, end, base);
    *end++ = '\n';
    write(STDOUT_FILENO, buf, end - buf);
    global += 1;
    signal(sig, signal_handler);
}

int main(int argc, char **argv) {
    /* Unit test itoa_safe. */
    {
        typedef struct {
            intmax_t n;
            int base;
            char out[1024];
        } InOut;
        char result[1024];
        size_t i;
        InOut io;
        InOut ios[] = {
            /* Base 10. */
            {0, 10, "0"},
            {1, 10, "1"},
            {9, 10, "9"},
            {10, 10, "10"},
            {100, 10, "100"},
            {-1, 10, "-1"},
            {-9, 10, "-9"},
            {-10, 10, "-10"},
            {-100, 10, "-100"},

            /* Base 2. */
            {0, 2, "0"},
            {1, 2, "1"},
            {10, 2, "1010"},
            {100, 2, "1100100"},
            {-1, 2, "-1"},
            {-100, 2, "-1100100"},

            /* Base 35. */
            {0, 35, "0"},
            {1, 35, "1"},
            {34, 35, "Y"},
            {35, 35, "10"},
            {100, 35, "2U"},
            {-1, 35, "-1"},
            {-34, 35, "-Y"},
            {-35, 35, "-10"},
            {-100, 35, "-2U"},
        };
        for (i = 0; i < sizeof(ios)/sizeof(ios[0]); ++i) {
            io = ios[i];
            itoa_safe(io.n, result, io.base);
            if (strcmp(result, io.out)) {
                printf("%ju %d %s\n", io.n, io.base, io.out);
                assert(0);
            }
        }
    }

    /* Handle the signals. */
    if (argc > 1 && !strcmp(argv[1], "1")) {
        signal(SIGINT, signal_handler);
        while(1);
    }

    return EXIT_SUCCESS;
}

컴파일 및 실행 :

gcc -std=c99 -Wall -Wextra -o main main.c
./main 1

Ctrl + C를 15 번 누르면 터미널에 다음이 표시됩니다.

^Ccount, sigid: 0 2
^Ccount, sigid: 1 2
^Ccount, sigid: 2 2
^Ccount, sigid: 3 2
^Ccount, sigid: 4 2
^Ccount, sigid: 5 2
^Ccount, sigid: 6 2
^Ccount, sigid: 7 2
^Ccount, sigid: 8 2
^Ccount, sigid: 9 2
^Ccount, sigid: 10 2
^Ccount, sigid: 11 2
^Ccount, sigid: 12 2
^Ccount, sigid: 13 2
^Ccount, sigid: 14 2

2대한 신호 번호는 어디에 있습니까 SIGINT?

Ubuntu 18.04에서 테스트되었습니다. GitHub 업스트림 .


You can use printf in signal handlers if you are using the pthread library. unix/posix specifies that printf is atomic for threads cf Dave Butenhof reply here : https://groups.google.com/forum/#!topic/comp.programming.threads/1-bU71nYgqw Note that in order to get a clearer picture of printf output, you should run your application in a console (on linux use ctl+alt+f1 to start console 1), rather than a pseudo-tty created by the GUI.

참고URL : https://stackoverflow.com/questions/16891019/how-to-avoid-using-printf-in-a-signal-handler

반응형