可重入函數(shù)

什么是可重入函數(shù)

可重入的概念

若一個(gè)程序子程序可以“在任意時(shí)刻被中斷,然后操作系統(tǒng)調(diào)度執(zhí)行另外一段代碼,這段代碼又調(diào)用了該子程序不會(huì)出錯(cuò)”,則稱其為可重入(reentrant或re-entrant)的。

也就是說,當(dāng)該子程序正在運(yùn)行時(shí),執(zhí)行線程可以再次進(jìn)入并執(zhí)行它,仍然獲得符合設(shè)計(jì)時(shí)預(yù)期的結(jié)果。與多線程并發(fā)執(zhí)行的線程安全不同,可重入強(qiáng)調(diào)對(duì)單個(gè)線程執(zhí)行時(shí),重新進(jìn)入同一個(gè)子程序,仍然是安全的。

可重入函數(shù)可以簡(jiǎn)單地理解為: 函數(shù)可以在任意時(shí)刻被中斷并再次被調(diào)用(即重入),稍后再繼續(xù)執(zhí)行被中斷的那次調(diào)用,而不會(huì)丟失數(shù)據(jù)。

不可重入

為便于理解可重入的概念,先來考察一個(gè)不可重入的例子:
我們現(xiàn)在有一個(gè)鏈表,insert()函數(shù)可向鏈表中插入一個(gè)結(jié)點(diǎn)

不可重入函數(shù)示例

  1. 在正在調(diào)用insert()函數(shù)插入結(jié)點(diǎn)node1時(shí)(假設(shè)此時(shí)剛完成node1的指針指向,圖中1所示),
  2. sighandler()函數(shù)中斷,并在sighandler()函數(shù)中再次調(diào)用insert()函數(shù)向鏈表插入node2,
  3. 完成后,此時(shí)head指向了node2,
  4. 繼續(xù)進(jìn)行被中斷的插入動(dòng)作(即繼續(xù)插入node1),則完成該動(dòng)作之后head將指向node1,這樣就導(dǎo)致中間插入的node2丟失。

因此,函數(shù)insert()是不可重入的。

可重入函數(shù)的條件

若一個(gè)函數(shù)是可重入的,則該函數(shù)應(yīng)當(dāng)滿足下述條件:

  • 不能含有靜態(tài)(全局)非常量數(shù)據(jù)。
  • 不能返回靜態(tài)(全局)非常量數(shù)據(jù)的地址。
  • 只能處理由調(diào)用者提供的數(shù)據(jù)。
  • 不能依賴于單實(shí)例模式資源的鎖。
  • 調(diào)用的函數(shù)也必需是可重入的。

上述條件就是要求可重入函數(shù)使用的所有變量都保存在調(diào)用棧的當(dāng)前函數(shù)幀(frame)上。因此,同一執(zhí)行線程重入執(zhí)行該函數(shù)時(shí) 加載了新的函數(shù)幀,與前一次執(zhí)行該函數(shù)時(shí)使用的函數(shù)幀不沖突、不互相覆蓋,從而保證了可重入執(zhí)行安全。

多“用戶/對(duì)象/進(jìn)程優(yōu)先級(jí)”以及多進(jìn)程,一般會(huì)使得對(duì)可重入代碼的控制變得復(fù)雜。同時(shí),IO代碼通常不是可重入的,因?yàn)樗麄円蕾囉谙翊疟P這樣共享的、單獨(dú)的資源(類似編程中的靜態(tài)Static、全域Global資源)。

因此,如果函數(shù)體內(nèi)調(diào)用了標(biāo)準(zhǔn)I/O函數(shù),則該函數(shù)是不可重入的。

可重入出現(xiàn)的背景

可重入概念是在單線程操作系統(tǒng)的時(shí)代提出的。

這也使得可重入函數(shù)未必是線程安全的;線程安全函數(shù)未必是可重入的。

一個(gè)子程序的重入,可能由于自身原因,如執(zhí)行了jmp或者call,類似于子程序的遞歸調(diào)用;或者由于操作系統(tǒng)的中斷響應(yīng)。UNIX系統(tǒng)的signal的處理,即子程序被中斷處理程序或者signal處理程序調(diào)用。所以,可重入也可稱作“異步信號(hào)安全”。這里的異步是指信號(hào)中斷可發(fā)生在任意時(shí)刻。 重入的子程序,按照后進(jìn)先出線性序依次執(zhí)行。

示例

下面兩個(gè)函數(shù)f1()和f2()都不是可重入函數(shù):

int global_var = 1;

int f1()
{
    global_var += 2;
    return global_var;
}

int f2()
{
    return f1() + 2;
}

原因分析

函數(shù)f1()使用了全局變量global_var,所以 如果兩個(gè)或多個(gè)線程同時(shí)執(zhí)行它并訪問global_var,則返回的結(jié)果取決于執(zhí)行的時(shí)間。因此,f1()不可重入。而函數(shù)f2()調(diào)用了f1(),所以它也不可重入。

修改為可重入函數(shù)

不再使用全局變量,而是改為以參數(shù)的形式接收輸入,則兩個(gè)函數(shù)都是可重入的:

int f1(int i)
{
  return i + 2;
}

int f2(int i)
{
  return f1(i) + 2;
}

Linux提供的可重入函數(shù)

slot@slot-ubt:~$ man 7 signal
Async-signal-safe functions
       A signal handler function must be  very  careful,  since
       processing  elsewhere  may  be interrupted at some arbi‐
       trary point in the execution of the program.  POSIX  has
       the  concept of "safe function".  If a signal interrupts
       the execution of an unsafe function, and  handler  calls
       an  unsafe function, then the behavior of the program is
       undefined.

       POSIX.1-2004 (also known as POSIX.1-2001 Technical  Cor‐
       rigendum 2) requires an implementation to guarantee that
       the following functions can be safely  called  inside  a
       signal handler:

           _Exit()
           _exit()
           abort()
           accept()
           access()
           aio_error()
           aio_return()
           aio_suspend()
           alarm()
           bind()
           cfgetispeed()
           cfgetospeed()
           cfsetispeed()
           cfsetospeed()
           chdir()
           chmod()
           chown()
           clock_gettime()
           close()
           connect()
           creat()
           dup()
           dup2()
           execle()
           execve()
           fchmod()
           fchown()
           fcntl()
           fdatasync()
           fork()
           fpathconf()
           fstat()
           fsync()
           ftruncate()
           getegid()
           geteuid()
           getgid()
           getgroups()
           getpeername()
           getpgrp()
           getpid()
           getppid()
           getsockname()
           getsockopt()
           getuid()
           kill()
           link()
           listen()
           lseek()
           lstat()
           mkdir()
           mkfifo()
           open()
           pathconf()
           pause()
           pipe()
           poll()
           posix_trace_event()
           pselect()
           raise()
           read()
           readlink()
           recv()
           recvfrom()
           recvmsg()
           rename()
           rmdir()
           select()
           sem_post()
           send()
           sendmsg()
           sendto()
           setgid()
           setpgid()
           setsid()
           setsockopt()
           setuid()
           shutdown()
           sigaction()
           sigaddset()
           sigdelset()
           sigemptyset()
           sigfillset()
           sigismember()
           signal()
           sigpause()
           sigpending()
           sigprocmask()
           sigqueue()
           sigset()
           sigsuspend()
           sleep()
           sockatmark()
           socket()
           socketpair()
           stat()
           symlink()
           sysconf()
           tcdrain()
           tcflow()
           tcflush()
           tcgetattr()
           tcgetpgrp()
           tcsendbreak()
           tcsetattr()
           tcsetpgrp()
           time()
           timer_getoverrun()
           timer_gettime()
           timer_settime()
           times()
           umask()
           uname()
           unlink()
           utime()
           wait()
           waitpid()
           write()
POSIX.1-2008   removes   fpathconf(),   pathconf(),  and
       sysconf() from the above list, and  adds  the  following
       functions:

           execl()
           execv()
           faccessat()
           fchmodat()
           fchownat()
           fexecve()
           fstatat()
           futimens()
           linkat()
           mkdirat()
           mkfifoat()
           mknod()
           mknodat()
           openat()
           readlinkat()
           renameat()
           symlinkat()
           unlinkat()
           utimensat()
           utimes()

       POSIX.1-2008  Technical  Corrigendum  1  (2013) adds the
       following functions:

           fchdir()
           pthread_kill()
           pthread_self()
           pthread_sigmask()

聲明:本文部分整理自維基百科:可重入,本文所有文字遵從知識(shí)共享 署名-相同方式共享 3.0協(xié)議

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容