可重入函数列表

 

 

10.6. Reentrant Functions

When a signal that is being caught is handled by a process, the normal sequence of instructions being executed by the process is temporarily interrupted by the signal handler. The process then continues executing, but the instructions in the signal handler are now executed. If the signal handler returns (instead of calling exit or longjmp, for example), then the normal sequence of instructions that the process was executing when the signal was caught continues executing. (This is similar to what happens when a hardware interrupt occurs.) But in the signal handler, we can't tell where the process was executing when the signal was caught. What if the process was in the middle of allocating additional memory on its heap using malloc, and we call malloc from the signal handler? Or, what if the process was in the middle of a call to a function, such as getpwnam (Section 6.2), that stores its result in a static location, and we call the same function from the signal handler? In the malloc example, havoc can result for the process, since malloc usually maintains a linked list of all its allocated areas, and it may have been in the middle of changing this list. In the case of getpwnam, the information returned to the normal caller can get overwritten with the information returned to the signal handler.

The Single UNIX Specification specifies the functions that are guaranteed to be reentrant. Figure 10.4 lists these reentrant functions.

Figure 10.4. Reentrant functions that may be called from a signal handler

accept

fchmod

lseek

sendto

stat

access

fchown

lstat

setgid

symlink

aio_error

fcntl

mkdir

setpgid

sysconf

aio_return

fdatasync

mkfifo

setsid

tcdrain

aio_suspend

fork

open

setsockopt

tcflow

alarm

fpathconf

pathconf

setuid

tcflush

bind

fstat

pause

shutdown

tcgetattr

cfgetispeed

fsync

pipe

sigaction

tcgetpgrp

cfgetospeed

ftruncate

poll

sigaddset

tcsendbreak

cfsetispeed

getegid

posix_trace_event

sigdelset

tcsetattr

cfsetospeed

geteuid

pselect

sigemptyset

tcsetpgrp

chdir

getgid

raise

sigfillset

time

chmod

getgroups

read

sigismember

timer_getoverrun

chown

getpeername

readlink

signal

timer_gettime

clock_gettime

getpgrp

recv

sigpause

timer_settime

close

getpid

recvfrom

sigpending

times

connect

getppid

recvmsg

sigprocmask

umask

creat

getsockname

rename

sigqueue

uname

dup

getsockopt

rmdir

sigset

unlink

dup2

getuid

select

sigsuspend

utime

execle

kill

sem_post

sleep

wait

execve

link

send

socket

waitpid

_Exit & _exit

listen

sendmsg

socketpair

write


Most functions that are not in Figure 10.4 are missing because (a) they are known to use static data structures, (b) they call malloc or free, or (c) they are part of the standard I/O library. Most implementations of the standard I/O library use global data structures in a nonreentrant way. Note that even though we call printf from signal handlers in some of our examples, it is not guaranteed to produce the expected results, since the signal hander can interrupt a call to printf from our main program.

Be aware that even if we call a function listed in Figure 10.4 from a signal handler, there is only one errno variable per thread (recall the discussion of errno and threads in Section 1.7), and we might modify its value. Consider a signal handler that is invoked right after main has set errno. If the signal handler calls read, for example, this call can change the value of errno, wiping out the value that was just stored in main. Therefore, as a general rule, when calling the functions listed in Figure 10.4 from a signal handler, we should save and restore errno. (Be aware that a commonly caught signal is SIGCHLD, and its signal handler usually calls one of the wait functions. All the wait functions can change errno.)

Note that longjmp (Section 7.10) and siglongjmp (Section 10.15) are missing from Figure 10.4, because the signal may have occurred while the main routine was updating a data structure in a nonreentrant way. This data structure could be left half updated if we call siglongjmp instead of returning from the signal handler. If it is going to do such things as update global data structures, as we describe here, while catching signals that cause sigsetjmp to be executed, an application needs to block the signals while updating the data structures.

Example

Figure 10.5 shows a program that calls the nonreentrant function getpwnam from a signal handler that is called every second. We describe the alarm function in Section 10.10. We use it here to generate a SIGALRM signal every second.

When this program was run, the results were random. Usually, the program would be terminated by a SIGSEGV signal when the signal handler returned after several iterations. An examination of the core file showed that the main function had called getpwnam, but that some internal pointers had been corrupted when the signal handler called the same function. Occasionally, the program would run for several seconds before crashing with a SIGSEGV error. When the main function did run correctly after the signal had been caught, the return value was sometimes corrupted and sometimes fine. Once (on Mac OS X), messages were printed from the malloc library routine warning about freeing pointers not allocated through malloc.

As shown by this example, if we call a nonreentrant function from a signal handler, the results are unpredictable.

Figure 10.5. Call a nonreentrant function from a signal handler
#include "apue.h"
#include <pwd.h>

static void
my_alarm(int signo)
{
    struct passwd   *rootptr;

    printf("in signal handler/n");
    if ((rootptr = getpwnam("root")) == NULL)
            err_sys("getpwnam(root) error");
    alarm(1);
}

int
main(void)
{
    struct passwd   *ptr;

    signal(SIGALRM, my_alarm);
    alarm(1);
    for ( ; ; ) {
        if ((ptr = getpwnam("sar")) == NULL)
            err_sys("getpwnam error");
        if (strcmp(ptr->pw_name, "sar") != 0)
            printf("return value corrupted!, pw_name = %s/n",
                    ptr->pw_name);
    }
}
 
<think> 首先,用户的问题是关于“不可重入函数”的。我需要用中文回答,并在回答后提出3-5个相关问题。问题必须以“§§相关问题§§”开头,并且不能换行分散。 我的回答: 1. 解释什么是不可重入函数。 2. 提供一些例子或特征。 3. 讨论为什么它重要。 然后,在回答结束时,添加“§§相关问题§§”作为第一行,后面跟着3-5个相关问题。 关键点: - 不可重入函数在并发或多线程环境下可能不安全。 - 原因:通常使用全局变量或静态变量。 - 与可重入函数的区别。 定义: - 不可重入函数:如果一个函数在尚未结束前被再次调用,可能会导致不正确的结果,因为它依赖于静态数据或共享资源。 例子: - 使用静态变量的函数。 回答结构: 1. 定义不可重入函数。 2. 解释为什么它不可重入。 3. 潜在问题。 4. 如何避免。 然后,提出相关问题: - 基于我的回答和用户的问题。 - 例如: 1. 不可重入函数在什么情况下会导致问题? 2. 如何将一个不可重入函数转换为可重入函数? 3. 可重入函数与线程安全函数的区别是什么? 4. 在实际编程中,如何识别不可重入函数? 确保输出格式: - 回答内容。 - 然后,直接输出“§§相关问题§§”。 - 接着是1. 2. 3. 列表。 最后,确保所有内容符合规则。 </think>### 什么是不可重入函数? 不可重入函数(Non-reentrant Function)是指在多线程或中断等并发场景中,无法被安全地同时调用或重入的函数。这类函数在执行过程中依赖共享状态(如全局变量、静态变量),如果被多个执行流同时访问,可能导致数据竞争、结果错误或程序崩溃。 #### 核心特征: 1. **依赖外部状态**:使用全局变量或静态局部变量存储中间结果(例如,`static int counter;`)。 2. **非原子操作**:对共享资源的修改未加锁,可能被其他执行流中断。 3. **不可预测性**:多次并发调用时,输出可能不一致(例如,返回相同的错误码或覆盖数据)。 #### 示例: ```c // 不可重入函数示例:使用静态变量 char *get_time() { static char buffer[20]; // 静态变量,导致不可重入 time_t now = time(NULL); strftime(buffer, 20, "%Y-%m-%d %H:%M:%S", localtime(&now)); return buffer; // 并发调用时,buffer 可能被覆盖 } ``` - **问题**:如果线程A和线程B同时调用 `get_time()`,返回的字符串可能被对方的操作覆盖。 #### 与可重入函数的区别: - **可重入函数**:仅使用局部变量或参数,或通过锁保护共享资源(如 `strtok_r` 替代 `strtok`)。 - **不可重入函数**:标准库中的 `malloc`、`printf` 在某些实现中不可重入,需谨慎使用。 #### 风险场景: - 多线程程序:线程竞争导致数据损坏。 - 信号处理函数:信号中断主程序时,调用不可重入函数可能破坏状态。 - 递归调用:如果函数自身递归且依赖静态数据,会引发逻辑错误。 #### 解决方法: 1. 避免全局/静态变量。 2. 使用线程局部存储(Thread-Local Storage, TLS)。 3. 对共享资源加锁(如互斥锁)。 4. 替换为可重入版本(如 `strtok_r` 替代 `strtok`)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值