【问题标题】:Reentrancy and Reentrant in C?C中的可重入和可重入?
【发布时间】:2015-06-05 06:21:02
【问题描述】:

我正在阅读一本名为Linux 系统编程 的书。引用本书:

系统调用和其他库函数呢?如果你的 进程正在写入文件或分配内存, 和信号处理程序写入同一个文件还是调用 malloc()? 有些函数显然是不可重入的。如果一个程序在 在执行一个不可重入函数的中间,一个信号出现并且 信号处理程序然后调用相同的不可重入函数,混沌 可以接踵而至。

但它会随之而来:

保证重入函数

保证在信号中使用可安全重入的函数

这里有一些功能..

写()

这里有一些功能..

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

如果您的进程正在写入文件怎么办?

【问题讨论】:

    标签: c concurrency reentrancy


    【解决方案1】:

    添加 @Joachim Pileborg 先生在他的answer 中已经提到的内容,根据wiki entry for Reentrancy,函数可重入的基本规则是

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

    详细地说,如果函数是可重入的,那么无论是从不同的上下文调用,它的自己的实现(引入它自己使用的内部数据结构)都不会有任何问题。

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

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

    【讨论】:

    • write 在当前 Linux 内核中是线程安全的。详情请见我的answer
    【解决方案2】:

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

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

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


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

    【讨论】:

    • 但是重入是否也意味着线程安全?我想不是! (我认为重要的是要注意这一点)
    • 请解释一下context这个词的意思。
    • @SergioFormiggini 正确,我会添加一个关于它的注释。
    • 先生,我刚刚添加了对您的观点的详细说明作为答案,因为我认为发表评论太多了。你能复习一次吗?
    【解决方案3】:

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

    如果您不编写信号处理程序,则此文档对您没有用处。不过,以下是 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().
    

    【讨论】:

      【解决方案4】:

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

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

      但是,来自glibc wiki

      目前 Linux 写入系统调用不是 MT 安全的。多个线程竞相写入可能会获得相同的文件位置值并写入相同的位置,从而导致数据丢失。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多