为了账号安全,请及时绑定邮箱和手机立即绑定

如何避免在信号处理程序中使用printf?

如何避免在信号处理程序中使用printf?

DIEA 2019-06-03 13:48:45
如何避免在信号处理程序中使用printf?自printf不是可重入的,在信号处理程序中使用它是不安全的。但我见过很多示例代码printf这边请。所以我的问题是:我们什么时候需要避免使用printf在信号处理程序中,是否建议替换?
查看完整描述

3 回答

?
慕桂英546537

TA贡献1848条经验 获得超10个赞

您可以使用一些标志变量,在信号处理程序中设置该标志,并基于该标志调用。printf()在正常运行过程中,在main()或程序的其他部分中起作用。

调用所有函数并不安全,例如printf,在信号处理程序中。一种有用的技术是使用信号处理程序来设置flag然后检查一下flag从主程序中打印一条消息,如果需要的话。

注意,在下面的示例中,信号处理程序ding()设置了一个标志alarm_fired以1作为SIGALRM的捕获和主要功能alarm_fired值进行检查,以便有条件地正确调用printf。

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);}

参考资料:开始Linux编程,第4版,在这本书中,您的代码将被解释(您想要什么),第11章:过程和信号,第484页。

此外,在编写处理程序函数时需要特别小心,因为它们可以异步调用。也就是说,处理程序可以在程序的任意点调用,这是不可预测的。如果两个信号在很短的间隔内到达,一个处理程序可以在另一个处理程序内运行。人们认为更好的做法是宣布volatile sigatomic_t,这种类型总是以原子方式访问,避免了中断对变量访问的不确定性。(原文:原子数据存取和信号处理以获得详细的补偿)。

朗读,阅读定义信号处理程序*学习如何编写可以使用signal()sigaction()职能。
授权职能清单手册页,在信号处理程序中调用此函数是安全的。


查看完整回答
反对 回复 2019-06-03
?
繁星点点滴滴

TA贡献1803条经验 获得超3个赞


主要问题是如果信号中断malloc()或者一些类似的函数,内部状态可能暂时不一致,当它在空闲列表和使用列表之间移动内存块时,或者其他类似的操作。如果信号处理程序中的代码调用一个函数,然后调用malloc(),这可能会完全破坏内存管理。

对于在信号处理程序中可以做什么,C标准采取了非常保守的观点:

ISO/IEC 9899:2011§7.14.1.1signal功能

5如果信号发生时不是由于调用abortraise函数时,如果信号处理程序引用任何具有静态或线程存储持续时间的对象,而该对象不是无锁原子对象,则行为是未定义的,除非将值赋值给声明为volatile sig_atomic_t,或者信号处理程序调用标准库中的除abort函数_Exit函数quick_exit函数,或signal函数的第一个参数等于与导致调用处理程序的信号对应的信号。此外,如果对signal函数的结果是SIG_ERR返回值errno是不确定的。252)

252)如果任何信号是由异步信号处理程序生成的,则该行为是未定义的。

对于在信号处理程序中可以做什么,POSIX要慷慨得多。

信号概念在2008年POSIX版中说:

如果进程是多线程的,或者进程是单线程的,并且执行信号处理程序的结果是:

  • 进程调用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所定义的所有函数在从信号捕捉函数调用或被信号捕获函数中断时都应表现为定义的功能,但有一个例外:当信号中断不安全的函数,而信号捕捉函数调用不安全的函数时,则行为未定义。

获得以下值的操作:errno和将值赋值给errno应该是异步信号安全的。

当信号被传递到线程时,如果该信号的动作指定终止、停止或继续,则整个进程将分别终止、停止或继续。

但是,printf()该列表中明显没有函数族,信号处理程序可能无法安全地调用这些函数。

这个2016年POSIXUPDATE扩展了安全函数列表,特别包括了<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()等,否则您将设置一个标志,在代码中的适当位置(定期)进行测试。此技术在回答通过格里杰什·肖汉.


标准C功能和信号安全

克雷 一个有趣的问题,我只有一个部分的答案:

为什么大多数字符串函数都是从<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(浮点异常)的影响,尽管这几天我只看到一次。整型除以零。类似的不确定性产生于<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>

    -地点依赖性

分析POSIXHeader将是…更困难的是,它们很多,有些函数可能是安全的,但许多不是…但也更简单,因为POSIX说哪些函数是异步信号安全的(不是很多)。请注意,如下所示的标题<pthread.h>有三个安全功能和许多不安全功能。

注:在POSIX环境中,几乎所有对C函数和头的评估都是半信半疑的猜测。这是没有意义的,一个标准机构的明确声明。


查看完整回答
反对 回复 2019-06-03
?
子衿沉夜

TA贡献1828条经验 获得超3个赞

如何避免使用printf在信号处理器里?

  1. 总是避开它,会说:只是不要用printf()在信号处理程序中。

  2. 至少在POSIX一致性系统上,您可以使用write(STDOUT_FILENO, ...)而不是printf()..然而,格式化可能并不容易:使用写或异步安全函数从信号处理程序打印int


查看完整回答
反对 回复 2019-06-03
  • 3 回答
  • 0 关注
  • 1267 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信