C中的可重入和可重入?

Reentrancy and Reentrant in C?

我正在看一本书叫Linux系统编程。引自本书:

What about system calls and other library functions? What if your process is in the middle of writing to a file or allocating memory, and a signal handler writes to the same file or also invokes malloc()? Some functions are clearly not reentrant. If a program is in the middle of executing a nonreentrant function and a signal occurs and the signal handler then invokes that same nonreentrant function, chaos can ensue.

但接下来会是:

Guaranteed-Reentrant Functions

Functions guaranteed to be safely reentrant for use in signals

some functions here..

write()

some functions here..

我很困惑,write()是可重入的,还是不可重入的?因为我认为它与声明冲突:

What if your process is in the middle of writing to a file?

如果您可以从不同的上下文中调用一个函数而不打扰来自另一个上下文的另一个调用,那么可重入性有更多的工作要做。

strtok函数为例。它通常包含一个 static 局部变量来跟踪您正在标记化的字符串中的下一个位置。由于局部 static 变量在函数的所有调用之间共享,因此从两个不同的上下文调用函数会导致问题。

另一方面,write 系统调用没有在调用之间存储的内部数据,这使得从不同上下文调用是安全的。


请务必注意,可重入与线程安全不同。以 write 函数为例,因为它是可重入的,所以您可以使用不同的文件从不同的线程调用它,而不必担心内部数据会被破坏。但是,它不是线程安全的。从使用相同文件描述符的不同线程调用它导致问题。

您引用的文档是指信号处理程序。这是一种 非常 特定类型的函数,在异常情况下调用,并被视为特定的系统编程。它们违背了程序中的正常控制流程。

如果您不编写信号处理程序,则本文档对您没有实际用处。不过,这里是在 Mac OS:

上信号安全的函数列表
$ man sigaction

The following functions are either reentrant or not interruptible by
signals and are async-signal safe.  Therefore applications may invoke
them, without restriction, from signal-catching functions:

Base Interfaces:

_exit(), access(), alarm(), cfgetispeed(), cfgetospeed(),
cfsetispeed(), cfsetospeed(), chdir(), chmod(), chown(), close(),
creat(), dup(), dup2(), execle(), execve(), fcntl(), fork(),
fpathconf(), fstat(), fsync(), getegid(), geteuid(), getgid(),
getgroups(), getpgrp(), getpid(), getppid(), getuid(), kill(),
link(), lseek(), mkdir(), mkfifo(), open(), pathconf(), pause(),
pipe(), raise(), read(), rename(), rmdir(), setgid(), setpgid(),
setsid(), setuid(), sigaction(), sigaddset(), sigdelset(),
sigemptyset(), sigfillset(), sigismember(), signal(), sigpending(),
sigprocmask(), sigsuspend(), sleep(), stat(), sysconf(), tcdrain(),
tcflow(), tcflush(), tcgetattr(), tcgetpgrp(), tcsendbreak(),
tcsetattr(), tcsetpgrp(), time(), times(), umask(), uname(),
unlink(), utime(), wait(), waitpid(), write().

补充一下 @Joachim Pileborg 先生在他的 , as per the wiki entry for Reentrancy 中已经提到的内容,函数可重入的基本规则是

  1. 可重入代码不能保存任何静态(或全局)非常量数据。
  2. 可重入代码不能修改自己的代码。
  3. 可重入代码不得调用非可重入计算机程序或例程。

详细来说,如果函数是可重入的,那么它的自己的实现不会有任何问题(归纳出它自己使用的内部数据结构)是否从不同的上下文调用.

提供给函数的参数(例如文件描述符)不会影响它的可重入性。

所以,对于write(),函数本身是可重入的,但是如果从不同的线程使用相同的文件描述符调用,显然会产生错误的结果。同样,这并不意味着 write()Reentrancy 消失了。 可重入,但不是线程安全的,这两者是不同的方面

关于 write 的线程安全性,Sourav Ghosh 和 Joachim Pileborg 的答案似乎不正确:

write 根据 POSIX.1-2008 应是线程安全的,因为它不在此 list.

但是,来自 glibc wiki

At present the Linux write syscall is not MT-safe. Multiple threads racing to the write may get the same file position value and write to the same position resulting in the loss of data.

这个问题似乎已在 Linux 内核中修复(请参阅 linux kernel mailing list)。