以下是我看csapp的一些感想。其实之前看anup的时候也明白了,只是时间久了,有点忘记了。当我再次阅读csapp看到这部分时,他有了更好的理解。可重入函数当进程处理捕获的信号时,进程执行的正常指令序列会暂时被信号处理程序中断。它首先执行信号处理程序中的指令。如果您从信号处理程序返回(例如,未调用exit或longjmp),则继续执行捕获信号时进程正在执行的正常指令序列(类似于发生硬件中断时发生的情况)。但是在信号处理程序中,当信号被捕获时,我们不知道进程在哪里执行代码。如果进程正在使用malloc在其堆上分配额外的内存,并且由于捕获到调用malloc的信号而插入信号处理程序,会发生什么情况?或者,如果进程正在调用将结果存储在静态区域中的函数(如getpwnam),而我们在信号处理程序中调用相同的函数,会发生什么情况?在malloc的情况下,进程可能会被严重中断,因为malloc通常维护它已分配的所有区域的链表,并且当信号处理程序被中断时,进程可能正在更改此链表。在getpwnam的情况下,返回给普通调用者的信息可能会被返回给信号处理程序的信息覆盖。SUS指定必须保证可重入的函数。下表列出了这些可重入函数:可重入函数简单来说就是一个可以被中断的函数,即在这个函数执行过程中可以随时被中断,并转移到OS调度去执行另一段代码,以及无错返回控制。一个可重入函数可以被多个任务同时使用而不用担心数据错误。相反,不可重入函数不能被多个任务共享,除非确保函数的互斥(通过使用信号量,或通过在代码的关键部分禁用中断)。可重入函数可以随时中断并在以后恢复而不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。信号安全,其实就是异步信号安全,就是说如果线程在信号处理函数中,不管你怎么调用你的函数,如果不死锁,不修改数据,就是信号安全的。因此,我认为可重入和异步信号安全是一个概念。线程安全线程安全:一个函数被称为线程安全的当且仅当它被多个并发线程重复调用时,它总是会产生正确的结果。一类重要的线程安全函数,称为可重入函数,其特点是它们在被多个线程调用时不引用任何共享数据。尽管线程安全和可重入有时(错误地)用作同义词,但它们之间存在明显的技术差异。可重入函数是线程安全函数的一个适当子集。可重入和线程安全的区别以及与可重入函数的关系:可重入是指重复进入。首先,这意味着这个函数可以被中断。其次,意味着它除了使用自己栈上的变量(包括static)外不依赖于任何环境,这样的函数是purecode(纯代码)可重入的,它允许函数的多个副本运行,并且由于它们分别使用堆栈,它们不会相互干扰。可重入函数是线程安全的函数,但反过来,线程安全的函数不一定是可重入函数。事实上,可重入函数很少。在APUE的10.6节中,SingleUNIXSpecification中描述的可重入函数只有115个;在APUE的Section12.5中,只有89个函数不能保证POSIX.1中的线程安全。个人。信号就像硬件中断,中断正在执行的指令序列。信号处理程序无法确定捕获到信号时进程在何处运行。如果信号处理函数中的操作与中断函数中的操作相同,并且该操作中存在静态数据结构,则当信号处理函数返回时(当然,这里讨论的信号处理函数可以返回),原执行将恢复序列,这可能导致信号处理程序中的操作覆盖以前正常操作中的数据。几种不可重入的情况使用静态数据结构,如getpwnam、getpwuid:如果信号发生时正在执行getpwnam,信号处理程序中getpwnam的执行可能会覆盖getpwnam获取的旧值调用malloc或free:如果信号signal发生时正在malloced(修改堆上存储空间的链接表),signalhandler调用malloc,会破坏内核的数据结构。使用标准IO函数,因为很多标准IO实现使用的是全局数据结构,比如printf(文件偏移量是全局的)在函数中调用longjmp或siglongjmp:程序在信号发生时正在修改一个数据结构,处理程序返回到另一个地方,导致数据被部分更新。即使是可重入函数,在信号处理函数中使用时需要注意的一个问题就是errno。一个线程中只有一个errno变量,信号处理函数中使用的可重入函数也可能修改errno。比如read函数是可重入的,但也可能修改errno。因此,正确的做法是将errno保存在信号处理函数的开头;并在信号处理函数退出时恢复errno。比如程序在调用printf进行输出,但是在调用printf的时候,出现了信号,对应的信号处理函数也有printf语句,会导致两个printf的输出混在一起。如果是给printf加锁,同样的情况也会导致死锁。对于这种情况,采用的方法一般是屏蔽特定区域的某种信号。屏蔽信号的方法:signal(SIGPIPE,SIG_IGN);//忽略一些信号sigprocmask();//sigprocmask只为单线程定义pthread_sigmask();//pthread_sigmasks可以在多线程中使用现在看来信号是异步安全的和可重入限制好像是一样的,所以这里一视同仁;线程安全:如果一个函数可以同时被多个线程安全调用,则称该函数是线程安全的。Malloc函数是线程安全的。当不需要共享时,给每个线程一个数据的专用副本。如果共享很重要,请提供显式同步以确保程序以确定性方式运行。通过将过程包含在锁定和解锁互斥锁的语句中,可以使不安全的过程成为线程安全的和可序列化的。许多函数不是线程安全的,因为它们返回的数据存储在静态内存缓冲区中。可以通过修改接口使这些函数成为线程安全的,以便调用者自己提供缓冲区。当操作系统支持线程安全函数时,它会为POSIX.1中的一些非线程安全函数提供一些替代的线程安全版本。例如gethostbyname()是线程不安全的,Linux中提供了gethostbyname_r()的线程安全实现。在函数名后面加上_r表示这个版本是可重入的(reentrantforthreads,也就是说线程安全的,但不代表对于信号处理函数也是可重入的,或者异步信号安全的)。多线程程序中一个常见的无意问题:将指针向上传递给调用者堆栈作为新线程的参数。在没有同步机制保护的情况下访问全局内存的共享可变状态。当两个线程试图轮流获取对同一对全局资源的权限时,就会导致死锁。其中一个线程控制第一个资源,另一个线程控制第二个资源。在其中一个线程放弃之前,两个线程都无法继续。尝试重新获取已持有的锁(递归死锁)。在同步保护中创建隐藏的漏洞。如果被保护的代码部分包含释放同步机制然后在返回给调用者之前重新获取它的函数,则在保护中会出现此间隙。结果具有误导性。对于调用者,全局数据似乎受到保护,但实际上并没有。将UNIX信号与线程混合使用时,请使用sigwait(2)模型来处理异步信号。调用setjmp(3C)和longjmp(3C),然后在不释放互斥量的情况下进行长跳转。从对_cond_wait()或_cond_timedwait()的调用返回后,无法重新评估条件。总结一下,判断一个函数是否为可重入函数就是判断它是否可以被中断,中断后重新运行才能得到正确的结果。(中断执行的指令序列不会改变函数的数据)判断一个函数是否线程安全就是判断它在多个线程同时执行其指令序列时是否能保证每个线程都能得到正确的结果.如果一个函数对于多个线程是可重入的,那么它就是线程安全的,但这并不意味着该函数对于信号处理程序也是可重入的。如果一个函数可以安全地避免异步信号处理程序的重入,则称该函数是“异步信号安全的”。可重入性和线程安全是两个独立的概念,都与函数处理资源的方式有关。首先,可重入和线程安全是两个不等价的概念。一个函数可以是可重入的,也可以是线程安全的,两者都可以满足,也都不能满足(这个描述严格来说有漏洞,见第二篇)。其次,从集合和逻辑上看,可重入是线程安全的一个子集,可重入是线程安全的充分非必要条件。可重入函数必须是线程安全的,但事实并非如此。三、POSIX中关于可重入和线程安全两个概念的定义:ReentrantFunction:一个函数,当被两个或多个线程调用时,其效果保证如同每个线程在一个未定义的环境中一个接一个地执行该函数顺序,即使实际执行是交错的。线程安全函数:可以被多个线程安全并发调用的函数。异步信号安全函数:可以调用的函数,不受信号捕获函数的限制。除非明确描述,否则没有函数是异步信号安全的。以上三者之间的关系是:可重入函数必须是线程安全函数和异步信号安全函数;线程安全函数不一定是可重入函数。可重入和线程安全的区别体现在是否可以在信号处理函数中调用。可重入函数可以在信号处理函数中安全调用,因此也是一个Async-Signal-SafeFunction;而线程安全的函数不保证在信号处理函数中被安全调用。如果一个不可重入函数没有通过设置信号阻塞集等方式被信号打断,那么它也是一个Async-Signal-SafeFunction。值得一提的是,POSIX1003.1的SystemInterface默认是Thread-Safe,而不是Async-Signal-Safe。Async-Signal-Safe需要显式表达,比如fork()和signal()。不可重入函数通常(尽管不总是)由其外部接口和用法来标识。例如:strtok()是不可重入的,因为它在内部存储以标记分隔的字符串;ctime()函数也是不可重入的,因为它返回指向静态数据的指针,该指针在每次调用中都会被覆盖。线程安全函数通过锁定实现对共享数据的多线程安全访问。线程安全的概念只与函数内部实现有关,不影响函数对外接口。在C语言中,局部变量分配在栈上。因此,任何不使用静态数据或其他共享资源的函数都是线程安全的。在AIX的当前版本中,以下库是线程安全的:C标准库和BSD兼容库使用非线程安全的全局变量(函数)。此类信息应存储在线程单元中,以便可以序列化对数据的访问。一个线程可能会读取另一个线程生成的错误代码。在AIX中,每个线程都有一个单独的errno变量。最后,我们想象一个线程安全但不可重入的函数:假设函数func()在执行过程中需要访问一个共享资源,那么为了实现线程安全,在使用前锁定资源,使用时解锁资源这是不需要的。假设在某个函数执行过程中,获得资源锁后,出现异步信号,将程序的执行流程转移到对应的信号处理函数;进一步假设在信号处理函数中也需要调用函数func(),那么func()在本次执行中仍然会在访问共享资源之前尝试获取资源锁,但是我们知道前面的func()实例已经获取了锁,所以signalhandler阻塞了——另一方面,signalhandler结束了,之前被signal中断的线程无法恢复执行,当然就没有机会释放资源了,所以产生了死锁线程和信号处理函数之间的情况。因此,虽然func()可以通过加锁来保证线程安全,但是由于函数体访问共享资源,所以是不可重入的。
