10.6 可重入函数

当一个信号捕获到并开始被进程处理的时候,进程正常执行的指令序列将被信号处理函数临时中断,进程立即转到信号处理函数中开始执行,如果信号处理函数返回(而不是调用exit或者是longjmp等),然后在进入信号处理函数之前进程正在执行的指令序列将会接着执行,但是在信号处理函数中,我们无法获知在信号被捕获的时候进程正在执行那一段代码,如果进程正在使用函数malloc在其堆上分配额外的内存的过程中会发生什么呢?或者是进程正在调用一个函数的过程中,比如说getpwnam将会发生什么呢?函数getpwnam会将其结果存储在一个静态区域,这可能会导致一个混乱,因为malloc通常会保存期已经分配的区域到一个链表中,并且信号被捕获的时候,进程可能正在对这一列表进行修改,在函数getpwnam函数的情况下,返回给正常调用进程的结果可能会被信号处理函数重写。

The Single UNIX Specification详细列出了在信号处理函数中调用保证安全的函数,这些函数是可重入的,并且在Single UNIX Specification中被称为async-signal safe,除了可重入函数以外的函数,如果在调用过程中被检测出可能造成不一致性情况的出现的话,那么信号处理函数中对这些函数的调用就会被阻塞,图10.4列出了这些async-signal safe函数,许多函数不是可重入的原因是:

  1. 函数使用了静态的数据结构;
  2. 函数调用了malloc或者free函数;
  3. 它们是标准IO函数库的一部分。

    许多标准IO库的实现都以一种不可重入的方式使用了全局数据结构,注意,在我们的许多例子中,虽然我们在信号处理函数中调用了printf函数,但是我们却不能保证这能够产生我们期望的效果,因为信号处理函数可能会中断主程序中的printf函数的调用。

注意即是是图10.4中列出的信号处理函数,对于每一个线程而言都只有一个errno变量,因此我们可能会不经意之间对其进行了不期望的修改。假设main中刚刚设置完成errno的值,一个中断处理函数就被调用了,如果中断处理函数调用了read函数,该调用可能会改变errno的数值,从而覆盖掉刚刚在main函数中所赋的数值,因此,一个通用的规则是:当我们在信号处理函数中调用图10.4中列出的函数的时候,我们应该存储和恢复errno,注意到一个经常被捕获到的信号是SIGCHLD,其信号处理函数通常会调用一个wait函数,而所有的wait函数都将会改变errno的数值。

注意,函数longjmp以及siglongjmp函数并没有出现在图10.4中,因为当主程序正在以一种不可重入的方式更新数据结构的时候,信号可能出现,如果我们在信号处理函数中调用siglongjmp而不是返回的话,就可能导致数据结构只更新了一半,剩下的部分永远不能再正确地更新了。因此如果函数正在处理一些像更新数据结构之类的事情的时候,同时信号处理函数可能造成sigsetjmp函数的调用的话,应用程序就应该在更新数据结构的时候阻塞信号。

Example

图10.5的例子中,在信号处理函数中每秒钟调用一次不可重入函数getpwnam:

  1. #include "apue.h"
  2. #include <pwd.h>
  3. static void my_alarm(int signo)
  4. {
  5. struct passwd *rootptr;
  6. printf("in signal handler\n");
  7. #if 1
  8. if((rootptr = getpwnam("root")) == NULL)
  9. {
  10. err_sys("getpwnam(root) error");
  11. }
  12. #endif
  13. printf("getpwnam function execute finish!\n");
  14. alarm(1);
  15. }
  16. int main(void)
  17. {
  18. struct passwd *ptr;
  19. signal(SIGALRM, my_alarm);
  20. alarm(1);
  21. while(1)
  22. {
  23. #if 1
  24. if((ptr = getpwnam("os")) == NULL)
  25. {
  26. err_sys("getpwnam error");
  27. }
  28. if(strcmp(ptr->pw_name, "os") != 0)
  29. {
  30. printf("return value correpted!, pw_name = %s\n", ptr->pw_name);
  31. }
  32. #endif
  33. }
  34. }
  35. "10_5.c" 76 lines, 633 characters

编译上述程序并在Debian上进行测试,当上述代码中的连个都是#if 1的时候,运行效果如下所示:

  1. [email protected]:~/UnixProgram/Chapter10$ ./10_5.exe
  2. in signal handler
  3. ^C
  4. [email protected]:~/UnixProgram/Chapter10$

可见程序在信号处理函数内调用函数getpwnam的时候被阻塞了,程序运行很长时间都没有看到输出getpwnam function execute finish!。

对上述程序略作修改以后可以看到,只要将其中一个或者两个#if 1修改为#if 0,程序就可以按照与其的运行了:

  1. [email protected]:~/UnixProgram/Chapter10$ ./10_5.exe
  2. in signal handler
  3. getpwnam function execute finish!
  4. in signal handler
  5. getpwnam function execute finish!
  6. in signal handler
  7. getpwnam function execute finish!
  8. in signal handler
  9. getpwnam function execute finish!
  10. in signal handler
  11. getpwnam function execute finish!
  12. ^C
  13. [email protected]:~/UnixProgram/Chapter10$

APUE.3E对于上述程序的解释如下所述:

当上述程序运行的时候,结果数随机的,通常情况下,程序可能被信号SIGSEGV(无效内存引用)中断,对于core文件的检查显示main函数已经调用了函数getpwnam,但是当getpwnam调用函数free的时候,定时信号处理函数将其中断,然后在信号处理函数中调用了getpwnam,该函数会再次调用函数free,通常,该程序可以在被信号ISGSEGV错误中断之前运行几秒钟。

可见在一个信号处理函数中调用一个不可重入的函数,其结果将是不可预测的.

来自为知笔记(Wiz)

时间: 2024-12-13 19:55:23

10.6 可重入函数的相关文章

线程安全与可重入函数的区别与联系

一. 线程安全 前面提到过线程的同步与互斥,也就是当两个线程同时访问到同一个临界资源的时候,如果对临界资源的操作不是原子的就会产生冲突,使得结果并不如最终预期的那样,比如如下的程序: #include <stdio.h> #include <pthread.h> int g_val = 0; void* fun(void *arg) {     int i = 0;     while(i++ < 500)     {            int tmp = g_val;

2信号处理之:信号产生原因,进程处理信号行为,信号集处理函数,PCB的信号集,sigprocmask()和sigpending(),信号捕捉设定,sigaction,C标准库信号处理函数,可重入函数,

 1信号产生原因 2.进程处理信号行为 manpage里信号3中处理方式: SIG_IGN SIG_DFL                                            默认Term动作 a signal handling function 进程处理信号 A默认处理动作 term   中断 core    core(调试的时候产生) gcc –g file.c ulimit –c 1024 gdb a.out core ign      忽略 stop     停止

可重入函数的总结

搞错了,下面说的是线程安全 概念 可重入函数这个概念是针对多进程,多线程编程中产生的.指的是一个函数被并发调用时,任意一个调用不会影响到它的另一个调用. 那么什么样的函数又不是可重入的呢?举几个反例,说明可重入函数要规避的东西.下面的1,2两点都是在多线程中出现的问题,进程在fork后静态变量和全局变量是有各自的拷贝,不会出现这样的情况. 函数中使用静态变量 1 void foo(void) 2 { 3 int n = 10000000; 4 static int i = 0; 5 6 i++;

Use Reentrant Functions for Safer Signal Handling(译:使用可重入函数进行更安全的信号处理)

Use Reentrant Functions for Safer Signal Handling 使用可重入函数进行更安全的信号处理 How and when to employ reentrancy to keep your code bug free 何时及如何利用可重入性避免代码缺陷 Dipak Jha (mailto:[email protected]?subject=Use reentrant functions for safer signal handling&[email pr

信号处理函数误用不可重入函数导致的进程死锁情况

记一次进程死锁的情况: 某天突然发现进程不再运行处理且有没有崩溃产生core文件: 使用gdb -p pid查看堆栈信息如下: 1 #0 0x000000376faf83ae in __lll_lock_wait_private () from /lib64/libc.so.6 2 #1 0x000000376fa7d35b in _L_lock_10288 () from /lib64/libc.so.6 3 #2 0x000000376fa7ab83 in malloc () from /l

线程安全和可重入函数

一.线程安全 1.线程安全函数:C语言中局部变量是在栈中分配的,任何未使用静态数据或其他共享资源的函数都是线程安全的. (1)对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量.局部静态变量.分配于堆的变量都是共享的,即是非线程安全的. (2) 在对这些共享变量进行访 问时,如果要保证线程安全,则必须通过加锁的方式. 2.线程安全的:                   如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的.                  

可重入函数与不可重入函数

参考:http://blog.csdn.net/wenhui_/article/details/6889013 重入:重新进入 区别:多个任务能否同时调用一个函数,例如操作系统在进程调度过程中,或者单片机.处理器等的中断的时候会发生重入的现象 满足下面条件之一的多数是不可重入函数:(1)使用了静态数据结构:(2)调用了malloc或free:(3)调用了标准I/O函数,比如printf: 标准io库很多实现都以不可重入的方式使用全局数据结构:(4)进行了浮点运算.许多的处理器/编译器中,浮点一般

可重入函数与线程安全的区别和联系

1.可重入函数 可重入函数即表示可以被多个执行流重复进入,意味着只使用自己栈上的变量,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰. 一个不可重入函数的例子: int global; int fun( int a ) { int temp; global = a; temp = gloabl*2; return temp; } global是一个全局变量,若进程a运行这段代码传入的参数是2,预期的结果是4:进程b也运行这段代码,传入的参数是3,由于操作系统的进程调

可重入函数与线程安全

线程安全: 假如在一个函数中它是这么写的,在一个全局链表上存放数据,在单线程模式下,我们先new一个新的节点然后让head->next指向这个节点,这种场景在多线程场景下会是这样的过程,线程一new了一个节点,然后cpu转去执行线程二,线程二new一个节点后head->next指向线程二,然后执行线程一,线程一的head->next也指向它刚刚new出来的节点,这就导致一个head指向了两个节点,这也就是线程安全的问题. 导致线程安全问题需要满足下面两个条件: 1>:一定是发生在多