转载:可重入函数与线程安全函数及其它
内容非独创,来自网络,比较凌乱,部分有修改,就不一一附加引用地址了。
虚拟内存与MMU
虚拟内存是计算机系统的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个地址空间),而实际上,它可能是被分隔成多个碎片,甚至被交换到磁盘存储器上的。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存(例如RAM)的使用也更有效率。
注意:虚拟内存不只是「用磁盘空间来扩展物理内存」
MMU是存储器管理单元的缩写,是用来管理虚拟内存系统的器件。MMU通常是CPU的一部分,本身有少量存储空间存放从虚拟地址到物理地址的匹配表。此表称作TLB(转换旁置缓冲区)。所有数据请求都送往MMU,由MMU决定数据是在RAM内还是在大容量存储器设备内。如果数据不在存储空间内,MMU将产生页面错误中断。
MMU的两个主要功能是:
1. 将虚地址转换成物理地址。
2. 控制存储器存取允许。MMU关掉时,虚地址直接输出到物理地址总线。
TLB(translation look-aside buffer)。TLB是关联的寄存器,TLB条目由两部分组成:页号和帧号。
--------------
写直达模式(writethrough)和写回模式(writeback)。
写回法是指CPU在执行写操作时,被写数据只写入Cache,不写入主存。仅当需要替换时,才把已经修改过的Cache块写回到主存。在采用这种更新算法的Cache块表中,一般有一个"修改位"。当一块中的任何一个单元被修改时,这一块的修改位就被置"1",否则这一块的修改位仍保持"0"。在需要替换这一块时,如果对应的修该位为"1",则必须先把这一块写回到主存中去之后,才能再调入新的块。
写直达法是指CPU在执行写操作时,必须把数据同时写入Cache和主存。这样,在Cache的块表中就不需要"修改位"。当某一块需要替换时,也不必把这一块写回到主存中去,新调入的块可以立即把这一块覆盖掉。
比较写回法与写直达法的优缺点如下:
(1) 可靠性:写直达法要优于写回法。这是因为写直达法能够始终保持Cache是主存的正确副本。如果Cache发生错误,可以从主存得到纠正。而写回法当发生图5.36所示的两种情况时,在一段时间内,Cache并不是主存的正确副本。
(2) 与主存的通信量:一般情况下,写回法少于写直达法。原因可以从两方面来分析。一方面,由于Cache的命中率一般很高,对于写回法,CPU的绝大多数写操作只需写Cache,不必写主存。另一方面,当写Cache发生块失效时,可能要写一个块到主存,而写直达法每次只写一个字到主存。而且,即使是读操作,当Cache不命中时,写回法也可能因为发生块替换而要写一块到主存。
(3) 控制的复杂性:写直达法比写回法简单。写回法要在块表中为每一块设置一个修改位,而且要对修改位进行管理和判断。而写直达法不需要设置修改位。另外,由于写直达法能够始终保持Cache是主存的正确副本。一旦Cache发生错误,可以从主存得到纠正。因此,Cache通常只需要采用简单的奇偶校验即可。而对于写回法,Cache中的块必须自己独立保证正确性,因此要采用相对比较复杂的纠错码。
(4) 硬件实现的代价:写回法要比写直达法好。在写直达法中,因为每次写操作都要写主存,因此为了节省写主存所花费的时间,通常要采用一个高速小容量的缓冲存储器,把要写主存的数据和地址先写到这个缓冲存储器中。在每次读主存时,也要首先判断所读的数据是否在这个缓冲存储器中。而写回法的硬件实现代价相对比较低。
目前,很多单处理机都采用写回法,主要目的是为了减少Cache与主存之间的通信量。也有不少的单处理机采用写直达法,目的是为了硬件的控制比较简单。而几乎所有的多处理机都采用写直达法。例如,IBM 3033、IBM 370/168、VAX-11/780、Honeywell 66/60、Honeywell 66/80 等机器采用写直达法。而Amdahl 的所有机器,IBM 3081等机器采用写回法。
--------------
可重入函数与线程安全函数
可重入函数(reentrant function)与线程安全函数(thread-safe function)有时容易混淆,而且各种文档中的解释也不是很清楚,这里根据笔者的经验来说明一下。
线程安全函数?
1,概念:
线程安全的概念比较直观。一般说来,一个函数被称为线程安全的,当且仅当被多个并发线程反复调用时,它会一直产生正确的结果,正确处理各线程的局部变量。
2,确保线程安全:
要确保函数线程安全,主要需要考虑的是线程之间的共享变量。属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈和寄存器。因此,对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量、局部静态变量、分配于堆的变量都是共享的。在对这些共享变量进行访问时,如果要保证线程安全,则必须通过加锁的方式。
3,线程不安全的后果:
线程不安全可能导致的后果是显而易见的——共享变量的值由于不同线程的访问,可能发生不可预料的变化,进而导致程序的错误,甚至崩溃。
可重入函数
1,概念:
按照Wiki上的说法,若一个程序可以“安全的被并行执行(Parallel computing)”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,可以再次进入并执行它(并行执行时,个别的执行结果,都符合设计时的预期)。“A computer program or routine is described as reentrant if it can be safely executed concurrently; that is, the routine can be re-entered while it is already running.”根据笔者的经验,所谓“重入”,常见的情况是,程序执行到某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入。此时如果foo()能够正确的运行,而且处理完成后,之前暂停的foo()也能够正确运行,则说明它是可重入的。
2,确保可重入:
要确保函数可重入,需满足以下几个条件:
1、不在函数内部使用静态或全局数据(数据只有一个拷贝,重入被破坏)。
2、不返回静态或全局数据的地址
3、所有数据都由函数的调用者提供。
4、不能依赖单实例资源的锁。
5、不调用不可重入函数(注意,IO代码通常不是可重入的,因为他们依赖于像磁盘这样共享的、单独的(类似编程中的静态(Static)、全域(Global))资源)。
3,不可重入的后果:
不可重入的后果主要体现在象信号处理函数这样需要重入的情况中。如果信号处理函数中使用了不可重入的函数,则可能导致程序的错误甚至崩溃。
可重入与线程安全?
可重入与线程安全并不等同。可重入概念会影响函数的外部接口,而线程安全只关心函数的实现。
- 大多数情况下,要将不可重入函数改为可重入的,需要修改函数接口,使得所有的数据都通过函数的调用者提供。
- 要将非线程安全的函数改为线程安全的,则只需要修改函数的实现部分。一般通过加入同步机制以保护共享的资源,使之不会被几个线程同时访问。
可重入的函数与线程安全函数相似但是不同,而且可重入的函数不一定线程安全,线程安全函数也不一定可重入。如一个函数读一个文件,这个函数是可重入的,因为读文件可以有多个实例同时运行,不会造成冲突;而这个函数不是线程安全的,因为可能有别的线程正在修改该文件,为了线程安全必须对文件加“同步锁”(我理解是读锁)。
我们可以采用下面的变化过程来进一步说明:
- 如果一个函数中用到了全局或静态变量,那么它不是线程安全的,也不是可重入的;
- 如果我们对它加以改进,在访问全局或静态变量时使用互斥量或信号量等方式加锁,则可以使它变成线程安全的,但此时它仍然是不可重入的,因为通常加锁方式是针对不同线程的访问,而对同一线程可能出现问题;
- 如果将函数中的全局或静态变量去掉,改成函数参数等其他形式,则有可能使函数变成既线程安全,又可重入。
比如:strtok函数是既不可重入的,也不是线程安全的;加锁的strtok不是可重入的,但线程安全;而strtok_r既是可重入的,也是线程安全的
不可重入函数指的是该函数在被调用还没有结束以前,再次被调用可能会产生错误。可重入函数不存在这样的问题。 还有strtok |
--------------
临界区
不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。每个进程中访问临界资源的那段代码称为临界区(Critical Section)。
Windows种可以使用 CRITICAL_SECTION
Linux可是使用锁,信号量
--------------
一道相当阴险的题目
int i, j, r;
for(i = 1, j = 5; i++ < 4 || j++ < 9;)
r = i * j;
printf("\ni=%d, j=%d, r=%d\n", i, j, r);
答案是:9、10、72,注意C语言的表达式中含有"&&","||"时,满足左结合,如果左边的部分表达式结果就可以确定整个表达式的值,则其它部分不会被计算,如"1||XXX","0&&XXX"时,XXX都不会被计算。
java与C++的区别
指针:java中没有指针。
多重继承:java中没有多重继承,只能单根继承。java通过接口(Interface)来实现多重继承的功能;
数据类型:boolean数据类型c++定义可以取值0和正整数,也可以取值true和false;而java中只能是true和false。
堆栈放的数据类型不同:java的栈中只能放8中基本数据类型,(字符串不是基本数据类型。)应用数据类型则放在堆栈。C++不是。
内存管理:C++通过new 和 delete来创建和回收内存。java通过new和垃圾回收器来创建和回收内存。
操作符重载:java不支持操作符重载。
预处理功能:java中没有预处理。java中的important和C++中的#include功能虽然相同,但实现机制不同。
缺省参数:java不支持缺省参数,c++支持。