posts - 91,comments - 0,views - 12645

信号处理器函数的设计

常见的两种

信号处理器函数设置全局性标志变量并退出。主程序对此标志进行周期性检查,一旦置位随即采取相应动作。

信号处理器函数执行某种类型的清理动作,接着终止进程或者使用非本地跳转将栈解开并将控制返回到主程序中的预定位置。

设计原则

  • 简洁性

    • 短小精悍:信号处理函数应该尽量简短,以避免长时间占用 CPU,防止信号堆积,保持程序响应
    • 避免复杂操作:不要在信号处理函数中做复杂计算或调用可能阻塞的操作,比如文件读写
  • 可重入性

    • 可重入函数:信号处理函数必须能安全地被多次调用,不会导致数据混乱
    • 避免全局变量:如果使用全局变量,确保它们的操作是安全的,或在信号处理期间不会被修改
  • 同步性

    • 尽量避免共享数据:信号处理函数应尽量不与主程序共享数据,避免数据冲突
    • 使用原子操作:如果必须访问共享数据,使用原子操作确保数据的一致性,防止竞争
  • 安全性

    • 避免非安全函数:只调用那些可以安全在信号处理中使用的函数,例如 signal()、write() 等。避免使用如 printf() 和 malloc() 这类不安全的函数
    • 清晰的信号处理逻辑:确保信号处理逻辑简单明了,避免引发额外的信号

非队列化处理

上一节说过了,当信号处理器函数正在执行时,会阻塞同类信号,避免发生重入现象(即重新进入信号处理函数),然后等执行完后,再执行一次(相当于合并同类的信号了),这就发生了信号丢失的现象,所以无法对信号的产生进行计数

可重入函数

要解释可重入函数为何物,首先需要区分单线程程序和多线程程序。典型 UNIX 程序都具有一条执行线程,贯穿程序始终,CPU 围绕单条执行逻辑来处理指令。而对于多线程程序而言,同一进程却存在多条独立、并发的执行逻辑流。

信号的处理会创建出一条独立(不是并发)于主程序的执行线程

如果同一个进程的多条线程可以同时安全地调用某一函数,那么该函数就是可重入的。此处,“安全”意味着,无论其他线程调用该函数的执行状态如何,函数均可产生预期结果。

简单来说,你可以看成是这个函数被打断然后重新进入,仍旧可以产生预期结果,就是不会有啥影响,这个就是可重入函数了

非可重入函数

非可重入函数就是反过来了,它可能产生的原因有以下几点:

  1. 这句话的意思就是数据的一种竞争,全局和静态的就是共享的资源,大家都去修改,没有约定,就会造成混乱

    更新全局变量或静态数据结构的函数可能是不可重入的。(只用到本地变量的函数肯定是可重入的)

    比如说,malloc和free函数维护的一个针对已释放内存块的链表

  2. 这句话的意思是一个函数多次调用会不断地覆盖上次的结果

    还有一些函数库之所以不可重入,是因为它们使用了经静态分配的内存来返回信息。如果信号处理器用到了这类函数,那么将会覆盖主程序中上次调用同一函数所返回的信息(反之亦然)。

    crypt函数的静态缓冲区(存储返回结果)

  3. 这个其实和竞争类似,就是缓冲区还没来得及刷新就被别人拿去修改了

    将静态数据结构用于内部记账的函数也是不可重入的。其中最明显的例子就是 stdio 函数库成员(printf()、scanf()等),它们会为缓冲区 I/O 更新内部数据结构。

    stdio库中对每个文件流维护的缓冲区

异步信号安全函数

可重入的或者不会被信号处理器函数中断的函数

信号处理器函数内部使用全局变量

  • 对errno的使用

      void handler(int sig)
      {
        int savedErrno;
        savedErrno = errno;
        /*code*/
        errno = savedErrno;
      }
    
  • 使用全局变量
    一般信号处理器函数值设置全局标志,然后主程序对全局标志进行检查,执行相应步骤(同时清除标志)

    保证全局变量读写操作的原子性

      volatile sig_atomic_t flag;
    
    • volatile关键字保证处理器函数访问最新的全局标志,而不是访问寄存器中的缓存值,同时避免编译器的优化
    • sig_atomic_t是一种整型数据类型,它保证读写操作的原子性,比如a++(即a=a+1)这种,即读又写,对应的机器指令不止一条,防止中断在其中产生

终止信号处理器函数

  • 使用_exit函数:只会终止进程,不会进行额外的清理工作,exit在调用_exit之前,会刷新stdio的缓冲区
  • kill发送信号终止进程
  • 从信号处理器函数中执行非本地跳转:??????
  • 使用abort函数终止进程:abort函数成功终止进程后,还会刷新stdio流并将其关闭

系统调用的中断和重启

系统调用被中断后,会设置errno(设置为EINTR)表示失败,这时候可以设置是否重启

  • 手动设置
  #define NO_EINTR(stmt) while((stmt) == -1 && errno == EINTR)
  // read的一个重启
  NO_EINTR(cnt = read(fd, buf, BUF_SIZE);
  • SA_RESTART(sigaction函数的标志):对大部分信号都起作用,自动重启
posted on   Dylaris  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示