linux中可重入函数、不可重入函数

版权声明:guojawee https://blog.csdn.net/weixin_36750623/article/details/84942341

1、结论:可重入函数必然是线程安全函数异步信号安全函数线程安全函数不一定是可重入函数
例如:strtok是既不可重入的,也不是线程安全的;加锁的strtok不是可重入的,但线程安全。

2、不可重入导致死锁的案例
① 假设函数func()在执行过程中需要访问某个共享资源,因此为了实现线程安全,在使用该资源前加锁,在不需要资源解锁。
② 假设该函数在某次执行过程中,在已经获得资源锁之后,突然有异步信号发生,程序的执行权转交给对应的信号处理函数;
③ 再假设在该信号处理函数中也需要调用函数 func(),那么func()在这次执行中仍会在访问共享资源前试图获得资源锁,然而前一个func()实例已然获得该锁,因此信号处理函数阻塞;另一方面,信号处理函数结束前被信号中断的线程是无法恢复执行的,当然也没有释放资源的机会 ⇒ ⇒ ⇒ 这样就出现了线程和信号处理函数之间的死锁局面。

因此,func()尽管通过加锁的方式能保证线程安全,但是由于函数体对共享资源的访问,因此是非可重入。

我们知道,当捕捉到信号时,不论进程的主控制流程当前执行到哪儿,都会先跳到信号处理函数中执行,从信号处理函数返回后再继续执行主控制流程。信号处理函数是一个单独的控制流程,因为它和主控制流程是异步的,二者不存在调用和被调用的关系,并且使用不同的堆栈空间。

3、不可重入导致插入链表错乱的案例
main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后向链表中插入两个节点,而最后只有一个节点真正插入链表中了。

像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant)函数。
在这里插入图片描述

4、不可重入函数的原因在于:
<1> 已知它们使用静态数据结构
<2> 它们调用mallocfree.
因为malloc通常会为所分配的存储区维护一个链接表,而插入执行信号处理函数的时候,进程可能正在修改此链接表。
<3> 它们是标准IO函数.
因为标准IO库的很多实现都使用了全局数据结构

5、导致insert混乱的原因:非原子操作
在上面的图示例子中,main和sighandler都调用insert函数则有可能出现链表的错乱,其根本原因在于,对全局链表的插入操作要分两步完成,不是一个原子操作(假如这两步操作必定会一起做完,中间不可能被打断,就不会出现错乱了)

==解释什么是原子操作?==关于原子操作最原始的说法是一条汇编指令能够完成(对于多线程程序来说原子操作可以指加锁后的几个步骤集合),即使是一条C语句也不一定是一个原子操作,比如 a = 5; 如果a是32位的int变量,在32位机上赋值是原子操作(需要4字节对齐),在16位机上就不是(注:一个字节的读写如bool类型,都是原子性的)。

猜你喜欢

转载自blog.csdn.net/weixin_36750623/article/details/84942341