❶ java内存或者是缓存管理怎么实现
晕, 你确定你是用Java,
OK,不管Java还是C#
1. 定义你的map
HashMap<String, String> map = new HashMap<String, String>();
2. 存入你的信息, 反正你得要有个唯一标识的key,以及你想存的对象
map.put("userid", "userMessage");
....
3. 每次存入后,判断一下当前大小,java 的hashmap是size()方法,如果大于你的最大数,把原来存的前进前出删掉
if(map.size()>100){
//map.remove(arg0)
....
}
更高级内容:
如果对map中的cache内容有过访问,给该内容的权重+1,每次删除的时候先将权重由小至大删除。 这就是命中率缓存,一般缓存都是按这种算法做的,只是具体算法有改进
❷ Cache和Buffer的主要区别是什么都是缓存,区别在哪
cache是高速缓冲存储器,,介于CPU与主存之间,它的工作速度数倍于主存,全部功能由硬件实现,并且对程序员是透明的.buffer一般是主存.还有,一般buffer对程序员是不透明的,除非是底层的地程序员,偶尔会需要知道一些buffer的详细信息(一般是嵌入式的,必须对每个地址都要自己分配),一般情况下,只要程序自己去分配就好了!
❸ hibernate一级缓存和二级缓存的区别
一级缓存:
就是Session级别的缓存。一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中。
如果短时间内这个session(一定要同一个session)又做了同一个操作,那么hibernate直接从一级缓存中拿,而不会再去连数据库,取数据。
它是内置的事务范围的缓存,不能被卸载。
二级缓存:
就是SessionFactory级别的缓存。顾名思义,就是查询的时候会把查询结果缓存到二级缓存中。
如果同一个sessionFactory创建的某个session执行了相同的操作,hibernate就会从二级缓存中拿结果,而不会再去连接数据库。
这是可选的插件式的缓存,在默认情况下,SessionFactory不会启用这个插件。
可以在每个类或每个集合的粒度上配置。缓存适配器用于把具体的缓存实现软件与Hibernate集成。
严格意义上说,SessionFactory缓存分为两类:内置缓存和外置缓存。我们通常意义上说的二级缓存是指外置缓存。
内置缓存与session级别缓存实现方式相似。前者是SessionFactory对象的一些集合属性包含的数据,后者是指Session的一些集合属性包含的数据
SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句。
映射元数据是映射文件中数据的拷贝;
而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来。
SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。
Hibernate的这两级缓存都位于持久化层,存放的都是数据库数据的拷贝。
缓存的两个特性:
缓存的范围
缓存的并发访问策略
1、缓存的范围
决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。
事务范围
进程范围
集群范围
注:
对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。
事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。
2、缓存的并发访问策略
当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。
在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。
因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低。
A 事务型:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。
对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
B 读写型:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。
对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
C 非严格读写型:不保证缓存与数据库中数据的一致性。
如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。
对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
D 只读型:对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。
什么样的数据适合存放到第二级缓存中?
1、很少被修改的数据
2、不是很重要的数据,允许出现偶尔并发的数据
3、不会被并发访问的数据
4、参考数据
不适合存放到第二级缓存的数据?
1、经常被修改的数据
2、财务数据,绝对不允许出现并发
3、与其他应用共享的数据。
Hibernate的二级缓存策略的一般过程如下:
1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
注:
Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。
Query缓存策略的过程如下:
1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。
2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。
3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。
❹ java程序员最常用的技术有哪些
Java的技术体系是非常庞大的,需要我们学习的技术非常多,往往很多初学的人,通过互联网查阅了一个庞大的学习列表,然后不知道如何下手。网上很多大牛列的技术不是不重要,但是掌握住企业应用的Java的核心技术,快速上手,是一种高效的学习手段。结合本人十余年的项目研发和带人经验,整理出如下方面。
首先JavaSE是核心,这是初学Java人员应最先接触学习的部分。Java的运行原理,jdk的配置,与jre的区别,基本数据类型,流程控制(顺序结构、选择结构、循环结构),数组、集合框架,异常处理等,这些都是比较容易学习的,需要多练习,在练习过程中加强理解。面向对象部分是Java初学者,尤其是没有任何编程语言基础的学起来有些难度,类、对象、继承、封装、多态等技术点需要多参照些现有的设计模型,学习设计的思路。诸如工厂模式、观察者模式、代理模式等重要的设计模式也是需要学习的,否则在将来应用框架时就会只知其然,不知其所以然。IO流、多线程也一定需要学习,尤其是XML、JSON等文件格式一定要掌握,这在数据交互时常用。
其次是数据库知识,作为初级Java程序员必须要掌握一种常用的关系型书库的应用,如MySQL或oracle等,数据库对象诸如表、视图等的创建、增删改查语句,尤其是查询,在企业中经常需要从十几张表、或几十张表中查询数据,所以对于如何进行内连接、外连接、以及联合查询等一定要掌握,另外对于索引、事务等也要掌握。
第三是Java Web部分,由于Java主要做web开发,一些前端技术HTML3、CSS5,javaScript,jQuery等这些不见得要学得有多深入,但是必须要掌握,tomcat、jsp,以及和数据库的交互这些都是必须要掌握的。
第四是框架部分,主流的ORM框架有Mybatis、hibernate,MVC框架有Spring MVC、Struts2等,可以优先掌握主流的SSM框架组合,框架的学习有人认为很简单,就按照规定、规范调用、使用呗,初学者可以先学习如何使用这些框架,然后慢慢的探究内部原理,因为框架是技术封装、简化的产物。
这里面有些同类型的技术比如hibernate,如果会使用Mybatis了,那么上手就会很容易,同理如果Spring MVC框架应用熟练了,那么Struts2框架其实就可以现学现卖了。
一个web程序包含的模块很多,不一定包括所有模块。
系统模块:Windows、Linux系统等。
存储模块:这里既包括关系型数据库MySQL、oracle等,也包括内存数据库redis、memcached等。
程序模块:还可以细化成持久化模块、业务逻辑模块、表现层模块,MVC框架的实现。
搜索模块:应用solr或Elasticsearch等。
服务器模块:tomcat、weblogic、Resion等
中间件模块:nginx、MQ消息队列技术等。
在这里额外说一下技术和技能的区别,初学者学一个技术可能很容易,但是这个技术如何在企业开发环境中应用这就是技能了,所以在学习的同时,要多应用,最好通过一些案例项目来学习,这样既高效,学习的还扎实。
补充一点,现在的应用级别越来越大,海量数据、高并发是处理的重点,单应用的程序已经无法满足要求,分布式是趋势,Dubbo、Zookeeper、Docker、SpringBoot、SpringCloud、MyCat等技术,包括上面系统模块里提到的一些技术都要学习的。
❺ 缓存与寄存器关系
缓存是用来存放从内存中取出的指令和数据,用来提高cpu访问内存的速度
而寄存器是用来存放cpu在执行指令时所需要的操作数或执行结果
寄存器的内容可以通过编程控制,也就是说对程序员而言是可见的,而缓存不能通过编程控制,对程序员而言是透明的。
❻ (畅想)如何改进编程模式及cpu体系结构防止缓冲区溢出,不要求标准答案,只要想象得有道理
缓冲区溢出。本文首先解释什么是缓冲区溢出,以及它们为何如此常见和如此危险。然后讨论广泛用于解决缓冲区溢出的新 Linux 和 UNIX 方法 ―― 以及为什么这些方法还不足够。随后将展示 C/C++ 程序中防止缓冲区溢出的各种方法,同时包括静态调整大小的方法(比如标准的 C 库和 OpenBSD/strlcpy 解决方案)和动态调整大小的解决方案,以及一些将为您提供帮助的工具。最后,本文以一些关于缓冲区溢出缺陷的未来发展形势的预测来结束全文的讨论。
如果希望自己的程序是安全的,您需要知道什么是缓冲区溢出,如何防止它们,可以采用哪些最新的自动化工具来防止它们(以及为什么这些工具还不足够),还有如何在您自己的程序中防止它们。
什么是缓冲区溢出?
缓冲区以前可能被定义为“包含相同数据类型的实例的一个连续计算机内存块”。在 C 和 C++ 中,缓冲区通常是使用数组和诸如 malloc() 和 new 这样的内存分配例程来实现的。极其常见的缓冲区种类是简单的字符数组。 溢出 是指数据被添加到分配给该缓冲区的内存块之外。
如果攻击者能够导致缓冲区溢出,那么它就能控制程序中的其他值。虽然存在许多利用缓冲区溢出的方法,不过最常见的方法还是“stack-smashing”攻击。Elias Levy (又名为 Aleph One)的一篇经典文章“Smashing the Stack for Fun and Profit”解释了 stack-smashing 攻击,Elias Levy 是 Bugtraq 邮件列表(请参阅 参考资料 以获得相关链接)的前任主持人。
清单 1. 一个简单的程序
void function1(int a, int b, int c) {
char buffer1[5];
gets(buffer1); /* DON'T DO THIS */
}
void main() {
function(1,2,3);
}
假设使用 gcc 来编译清单 1 中的简单程序,在 X86 上的 Linux 中运行,并且紧跟在对 gets() 的调用之后中止。此时的内存内容看起来像什么样子呢?答案是它看起来类似图 1,其中展示了从左边的低位地址到右边的高位地址排序的内存布局。
图 1. 堆栈视图
内存的底部 内存的顶部
buffer1 sfp ret a b c
<--- 增长 --- [ ] [ ] [ ] [ ] [ ] [ ] ...
为什么缓冲区溢出如此常见?
在几乎所有计算机语言中,不管是新的语言还是旧的语言,使缓冲区溢出的任何尝试通常都会被该语言本身自动检测并阻止(比如通过引发一个异常或根据需要给缓冲区添加更多空间)。但是有两种语言不是这样:C 和 C++ 语言。C 和 C++ 语言通常只是让额外的数据乱写到其余内存的任何位置,而这种情况可能被利用从而导致恐怖的结果。更糟糕的是,用 C 和 C++ 编写正确的代码来始终如一地处理缓冲区溢出则更为困难;很容易就会意外地导致缓冲区溢出。除了 C 和 C++ 使用得 非常广泛外,上述这些可能都是不相关的事实;例如,Red Hat Linux 7.1 中 86% 的代码行都是用 C 或 C ++ 编写的。因此,大量的代码对这个问题都是脆弱的,因为实现语言无法保护代码避免这个问题。
在 C 和 C++ 语言本身中,这个问题是不容易解决的。该问题基于 C 语言的根本设计决定(特别是 C 语言中指针和数组的处理方式)。由于 C++ 是最兼容的 C 语言超集,它也具有相同的问题。存在一些能防止这个问题的 C/C++ 兼容版本,但是它们存在极其严重的性能问题。而且一旦改变 C 语言来防止这个问题,它就不再是 C 语言了。许多语言(比如 Java 和 C#)在语法上类似 C,但它们实际上是不同的语言,将现有 C 或 C++ 程序改为使用那些语言是一项艰巨的任务。
然而,其他语言的用户也不应该沾沾自喜。有些语言存在允许缓冲区溢出发生的“转义”子句。Ada 一般会检测和防止缓冲区溢出(即针对这样的尝试引发一个异常),但是不同的程序可能会禁用这个特性。C# 一般会检测和防止缓冲区溢出,但是它允许程序员将某些例程定义为“不安全的”,而这样的代码 可能 会导致缓冲区溢出。因此如果您使用那些转义机制,就需要使用 C/C++ 程序所必须使用的相同种类的保护机制。许多语言都是用 C 语言来实现的(至少部分是用 C 语言来实现的 ),并且用任何语言编写的所有程序本质上都依赖用 C 或 C++ 编写的库。因此,所有程序都会继承那些问题,所以了解这些问题是很重要的。
导致缓冲区溢出的常见 C 和 C++ 错误
从根本上讲,在程序将数据读入或复制到缓冲区中的任何时候,它需要在复制 之前检查是否有足够的空间。能够容易看出来的异常就不可能会发生 ―― 但是程序通常会随时间而变更,从而使得不可能成为可能。
遗憾的是,C 和 C++ 附带的大量危险函数(或普遍使用的库)甚至连这点(指检查空间)也无法做到。程序对这些函数的任何使用都是一个警告信号,因为除非慎重地使用它们,否则它们就会成为程序缺陷。您不需要记住这些函数的列表;我的真正目的是说明这个问题是多么普遍。这些函数包括 strcpy(3)、strcat(3)、sprintf(3) (及其同类 vsprintf(3) )和 gets(3) 。 scanf() 函数集( scanf(3)、fscanf(3)、sscanf(3)、vscanf(3)、vsscanf(3) 和 vfscanf(3) )可能会导致问题,因为使用一个没有定义最大长度的格式是很容易的(当读取不受信任的输入时,使用格式“%s”总是一个错误)。
其他危险的函数包括 realpath(3)、getopt(3)、getpass(3)、streadd(3)、strecpy(3) 和 strtrns(3) 。 从理论上讲, snprintf() 应该是相对安全的 ―― 在现代 GNU/Linux 系统中的确是这样。但是非常老的 UNIX 和 Linux 系统没有实现 snprintf() 所应该实现的保护机制。
Microsoft 的库中还有在相应平台上导致同类问题的其他函数(这些函数包括 wcscpy()、_tcscpy()、_mbscpy()、wcscat()、_tcscat()、_mbscat() 和 CopyMemory() )。注意,如果使用 Microsoft 的 MultiByteToWideChar() 函数,还存在一个常见的危险错误 ―― 该函数需要一个最大尺寸作为字符数目,但是程序员经常将该尺寸以字节计(更普遍的需要),结果导致缓冲区溢出缺陷。
另一个问题是 C 和 C++ 对整数具有非常弱的类型检查,一般不会检测操作这些整数的问题。由于它们要求程序员手工做所有的问题检测工作,因此以某种可被利用的方式不正确地操作那些整数是很容易的。特别是,当您需要跟踪缓冲区长度或读取某个内容的长度时,通常就是这种情况。但是如果使用一个有符号的值来存储这个长度值会发生什么情况呢 ―― 攻击者会使它“成为负值”,然后把该数据解释为一个实际上很大的正值吗?当数字值在不同的尺寸之间转换时,攻击者会利用这个操作吗?数值溢出可被利用吗? 有时处理整数的方式会导致程序缺陷。
防止缓冲区溢出的新技术
当然,要让程序员 不犯常见错误是很难的,而让程序(以及程序员)改为使用另一种语言通常更为困难。那么为何不让底层系统自动保护程序避免这些问题呢?最起码,避免 stack-smashing 攻击是一件好事,因为 stack-smashing 攻击是特别容易做到的。
一般来说,更改底层系统以避免常见的安全问题是一个极好的想法,我们在本文后面也会遇到这个主题。事实证明存在许多可用的防御措施,而一些最受欢迎的措施可分组为以下类别:
基于探测方法(canary)的防御。这包括 StackGuard(由 Immunix 所使用)、ProPolice(由 OpenBSD 所使用)和 Microsoft 的 /GS 选项。
非执行的堆栈防御。这包括 Solar Designer 的 non-exec 补丁(由 OpenWall 所使用)和 exec shield(由 Red Hat/Fedora 所使用)。
其他方法。这包括 libsafe(由 Mandrake 所使用)和堆栈分割方法。
遗憾的是,迄今所见的所有方法都具有弱点,因此它们不是万能药,但是它们会提供一些帮助。
基于探测方法的防御
研究人员 Crispen Cowan 创建了一个称为 StackGuard 的有趣方法。Stackguard 修改 C 编译器(gcc),以便将一个“探测”值插入到返回地址的前面。“探测仪”就像煤矿中的探测仪:它在某个地方出故障时发出警告。在任何函数返回之前,它执行检查以确保探测值没有改变。如果攻击者改写返回地址(作为 stack-smashing 攻击的一部分),探测仪的值或许就会改变,系统内就会相应地中止。这是一种有用的方法,不过要注意这种方法无法防止缓冲区溢出改写其他值(攻击者仍然能够利用这些值来攻击系统)。人们也曾扩展这种方法来保护其他值(比如堆上的值)。Stackguard(以及其他防御措施)由 Immunix 所使用。
IBM 的 stack-smashing 保护程序(ssp,起初名为 ProPolice)是 StackGuard 的方法的一种变化形式。像 StackGuard 一样,ssp 使用一个修改过的编译器在函数调用中插入一个探测仪以检测堆栈溢出。然而,它给这种基本的思路添加了一些有趣的变化。 它对存储局部变量的位置进行重新排序,并复制函数参数中的指针,以便它们也在任何数组之前。这样增强了ssp 的保护能力;它意味着缓冲区溢出不会修改指针值(否则能够控制指针的攻击者就能使用指针来控制程序保存数据的位置)。默认情况下,它不会检测所有函数,而只是检测确实需要保护的函数(主要是使用字符数组的函数)。从理论上讲,这样会稍微削弱保护能力,但是这种默认行为改进了性能,同时仍然能够防止大多数问题。考虑到实用的因素,它们以独立于体系结构的方式使用 gcc 来实现它们的方法,从而使其更易于运用。从 2003 年 5 月的发布版本开始,广受赞誉的 OpenBSD(它重点关注安全性)在他们的整个发行套件中使用了 ssp(也称为 ProPolice)。
Microsoft 基于 StackGuard 的成果,添加了一个编译器标记(/GS)来实现其 C 编译器中的探测仪。
非执行的堆栈防御
另一种方法首先使得在堆栈上执行代码变得不可能。 遗憾的是,x86 处理器(最常见的处理器)的内存保护机制无法容易地支持这点;通常,如果一个内存页是可读的,它就是可执行的。一个名叫 Solar Designer 的开发人员想出了一种内核和处理器机制的聪明组合,为 Linux 内核创建了一个“非执行的堆栈补丁”;有了这个补丁,堆栈上的程序就不再能够像通常的那样在 x86 上运行。 事实证明在有些情况下,可执行程序 需要在堆栈上;这包括信号处理和跳板代码(trampoline)处理。trampoline 是有时由编译器(比如 GNAT Ada 编译器)生成的奇妙结构,用以支持像嵌套子例程之类的结构。Solar Designer 还解决了如何在防止攻击的同时使这些特殊情况不受影响的问题。
一段时间之后,人们又想出了一种防止该问题的新思路:将所有可执行代码转移到一个称为“ASCII 保护(ASCII armor)”区域的内存区。要理解这是如何工作的,就必须知道攻击者通常不能使用一般的缓冲区溢出攻击来插入 ASCII NUL 字符(0)这个事实。 这意味着攻击者会发现,要使一个程序返回包含 0 的地址是很困难的。由于这个事实,将所有可执行代码转移到包含 0 的地址就会使得攻击该程序困难多了。
具有这个属性的最大连续内存范围是从 0 到 0x01010100 的一组内存地址,因此它们就被命名为 ASCII 保护区域(还有具有此属性的其他地址,但它们是分散的)。与非可执行的堆栈相结合,这种方法就相当有价值了:非可执行的堆栈阻止攻击者发送可执行代码,而 ASCII 保护内存使得攻击者难于通过利用现有代码来绕过非可执行堆栈。这样将保护程序代码避免堆栈、缓冲区和函数指针溢出,而且全都不需重新编译。
然而,ASCII 保护内存并不适用于所有程序;大程序也许无法装入 ASCII 保护内存区域(因此这种保护是不完美的),而且有时攻击者 能够将 0 插入目的地址。 此外,有些实现不支持跳板代码,因此可能必须对需要这种保护的程序禁用该特性。Red Hat 的 Ingo Molnar 在他的“exec-shield”补丁中实现了这种思想,该补丁由 Fedora 核心(可从 Red Hat 获得它的免费版本)所使用。最新版本的 OpenWall GNU/Linux (OWL)使用了 Solar Designer 提供的这种方法的实现(请参阅 参考资料 以获得指向这些版本的链接)。
其他方法
还有其他许多方法。一种方法就是使标准库对攻击更具抵抗力。Lucent Technologies 开发了 Libsafe,这是多个标准 C 库函数的包装,也就是像 strcpy() 这样已知的对 stack-smashing 攻击很脆弱的函数。Libsafe 是在 LGPL 下授予许可证的开放源代码软件。那些函数的 libsafe 版本执行相关的检查,确保数组改写不会超出堆栈桢。然而,这种方法仅保护那些特定的函数,而不是从总体上防止堆栈溢出缺陷,并且它仅保护堆栈,而不保护堆栈中的局部变量。它们的最初实现使用了 LD_PRELOAD ,而这可能与其他程序产生冲突。Linux 的 Mandrake 发行套件(从 7.1 版开始)包括了 libsafe。
另一种方法称为“分割控制和数据堆栈”―― 基本的思路是将堆栈分割为两个堆栈,一个用于存储控制信息(比如“返回”地址),另一个用于控制其他所有数据。Xu et al. 在 gcc 中实现了这种方法,StackShield 在汇编程序中实现了这种方法。这样使得操纵返回地址困难多了,但它不会阻止改变调用函数的数据的缓冲区溢出攻击。
事实上还有其他方法,包括随机化可执行程序的位置;Crispen 的“PointGuard”将这种探测仪思想引申到了堆中,等等。如何保护当今的计算机现在已成了一项活跃的研究任务。
一般保护是不足够的
如此多不同的方法意味着什么呢?对用户来说,好的一面在于大量创新的方法正在试验之中;长期看来,这种“竞争”会更容易看出哪种方法最好。而且,这种多样性还使得攻击者躲避所有这些方法更加困难。然而,这种多样性也意味着开发人员需要 避免编写会干扰其中任何一种方法的代码。这在实践上是很容易的;只要不编写对堆栈桢执行低级操作或对堆栈的布局作假设的代码就行了。即使不存在这些方法,这也是一个很好的建议。
操作系统供应商需要参与进来就相当明显了:至少挑选一种方法,并使用它。缓冲区溢出是第一号的问题,这些方法中最好的方法通常能够减轻发行套件中几乎半数已知缺陷的影响。可以证明,不管是基于探测仪的方法更好,还是基于非可执行堆栈的方法更好,它们都具有各自的优点。可以将它们结合起来使用,但是少数方法不支持这样使用,因为附加的性能损失使得这样做不值得。我并没有其他意思,至少就这些方法本身而言是这样;libsafe 和分割控制及数据堆栈的方法在它们所提供的保护方面都具有局限性。当然,最糟糕的解决办法就是根本不对这个第一号的缺陷提供保护。还没有实现一种方法的软件供应商需要立即计划这样做。从 2004 年开始,用户应该开始避免使用这样的操作系统,即它们至少没有对缓冲区溢出提供某种自动保护机制。
然而,没有哪种方法允许开发人员忽略缓冲区溢出。所有这些方法都能够被攻击者破坏。 攻击者也许能够通过改变函数中其他数据的值来利用缓冲区溢出;没有哪种方法能够防止这点。如果能够插入某些难于创建的值(比如 NUL 字符),那么这其中的许多方法都能被攻击者绕开;随着多媒体和压缩数据变得更加普遍,攻击者绕开这些方法就更容易了。从根本上讲,所有这些方法都能减轻从程序接管攻击到拒绝服务攻击的缓冲区溢出攻击所带来的破坏。遗憾的是,随着计算机系统在更多关键场合的使用,即使拒绝服务通常也是不可接受的。因而,尽管发行套件应该至少包括一种适当的防御方法,并且开发人员应该使用(而不是反对)那些方法,但是开发人员仍然需要最初就编写无缺陷的软件。
C/C++ 解决方案
针对缓冲区溢出的一种简单解决办法就是转为使用能够防止缓冲区溢出的语言。毕竟,除了 C 和 C++ 外,几乎每种高级语言都具有有效防止缓冲区溢出的内置机制。但是许多开发人员因为种种原因还是选择使用 C 和 C++。那么您能做什么呢?
事实证明存在许多防止缓冲区溢出的不同技术,但它们都可划分为以下两种方法:静态分配的缓冲区和动态分配的缓冲区。首先,我们将讲述这两种方法分别是什么。然后,我们将讨论静态方法的两个例子(标准 C strncpy/strncat 和 OpenBSD 的 strlcpy/strlcat ),接着讨论动态方法的两个例子(SafeStr 和 C++ 的 std::string )。
重要选择:静态和动态分配的缓冲区
缓冲区具有有限的空间。因此实际上存在处理缓冲区空间不足的两种可能方式。
“静态分配的缓冲区”方法:也就是当缓冲区用完时,您抱怨并拒绝为缓冲区增加任何空间。
“动态分配的缓冲区”方法:也就是当缓冲区用完时,动态地将缓冲区大小调整到更大的尺寸,直至用完所有内存。
静态方法具有一些缺点。事实上,静态方法有时可能会带来不同的缺陷。静态方法基本上就是丢弃“过多的”数据。如果程序无论如何还是使用了结果数据,那么攻击者会尝试填满缓冲区,以便在数据被截断时使用他希望的任何内容来填充缓冲区。如果使用静态方法,应该确保攻击者能够做的最糟糕的事情不会使得预先的假设无效,而且检查最终结果也是一个好主意。
动态方法具有许多优点:它们能够向上适用于更大的问题(而不是带来任意的限制),而且它们没有导致安全问题的字符数组截断问题。但它们也具有自身的问题:在接受任意大小的数据时,可能会遇到内存不足的情况 ―― 而这在输入时也许不会发生。任何内存分配都可能会失败,而编写真正很好地处理该问题的 C 或 C++ 程序是很困难的。甚至在内存真正用完之前,也可能导致计算机变得太忙而不可用。简而言之,动态方法通常使得攻击者发起拒绝服务攻击变得更加容易。因此仍然需要限制输入。此外,必须小心设计程序来处理任意位置的内存耗尽问题,而这不是一件容易的事情。
标准 C 库方法
最简单的方法之一是简单地使用那些设计用于防止缓冲区溢出的标准 C 库函数(即使在使用 C ++,这也是可行的),特别是 strncpy(3) 和 strncat(3) 。这些标准 C 库函数一般支持静态分配方法,也就是在数据无法装入缓冲区时丢弃它。这种方法的最大优点在于,您可以肯定这些函数在任何机器上都可用,并且任何 C/C++ 开发人员都会了解它们。许许多多的程序都是以这种方式编写的,并且确实可行。
遗憾的是,要正确地做到这点却是令人吃惊的困难。下面是其中的一些问题:
strncpy(3) 和 strncat(3) 都要求您给出 剩余的空间,而不是给出缓冲区的总大小。这之所以会成为问题是因为,虽然缓冲区的大小一经分配就不会变化,但是缓冲区中剩余的空间量会在每次添加或删除数据时发生变化。这意味着程序员必须始终跟踪或重新计算剩余的空间。这种跟踪或重新计算很容易出错,而任何错误都可能给缓冲区攻击打开方便之门。
在发生了溢出(和数据丢失)时,两个函数都不会给出简单的报告,因此如果要检测缓冲区溢出,程序员就必须做更多的工作。
如果源字符串至少和目标一样长,那么函数 strncpy(3) 还不会使用 NUL 来结束字符串;这可能会在以后导致严重破坏。因而,在运行 strncpy(3) 之后,您通常需要重新结束目标字符串。
函数 strncpy(3) 还可以用来仅把源字符串的 一部分复制到目标中。 在执行这个操作时,要复制的字符的数目通常是基于源字符串的相关信息来计算的。 这样的危险之处在于,如果忘了考虑可用的缓冲区空间,那么 即使在使用 strncpy(3) 时也可能会留下缓冲区攻击隐患。这个函数也不会复制 NUL 字符,这可能也是一个问题。
可以通过一种防止缓冲区溢出的方式使用 sprintf() ,但是意外地留下缓冲区溢出攻击隐患是非常容易的。 sprintf() 函数使用一个控制字符串来指定输出格式,该控制字符串通常包括“ %s ”(字符串输出)。如果指定字符串输出的精确指定符(比如 %.10s ),那么您就能够通过指定输出的最大长度来防止缓冲区溢出。甚至可以使用“ * ”作为精确指定符(比如“ %.*s ”),这样您就可以传入一个最大长度值,而不是在控制字符串中嵌入最大长度值。这样的问题在于,很容易就会不正确地使用 sprintf() 。一个“字段宽度”(比如“ %10s ”)仅指定了最小长度 ―― 而不是最大长度。“字段宽度”指定符会留下缓冲区溢出隐患,而字段宽度和精确宽度指定符看起来几乎完全相同 ―― 唯一的区别在于安全的版本具有一个点号。另一个问题在于,精确字段仅指定一个参数的最大长度,但是缓冲区需要针对组合起来的数据的最大尺寸调整大小。
scanf() 系列函数具有一个最大宽度值,至少 IEEE Standard 1003-2001 清楚地规定这些函数一定不能读取超过最大宽度的数据。遗憾的是,并非所有规范都清楚地规定了这一点,我们不清楚是否所有实现都正确地实现了这些限制(这在如今的 GNU/Linux 系统上就 不能正确地工作)。如果您依赖它,那么在安装或初始化期间运行小测试来确保它能正确工作,这样做将是明智的。
strncpy(3) 还存在一个恼人的性能问题。从理论上讲, strncpy(3) 是 strcpy(3) 的安全替代者,但是 strncpy(3) 还会在源字符串结束时使用 NUL 来填充整个目标空间。 这是很奇怪的,因为实际上并不存在这样做的很好理由,但是它从一开始就是这样,并且有些程序还依赖这个特性。这意味着从 strcpy(3) 切换到 strncpy(3) 会降低性能 ―― 这在如今的计算机上通常不是一个严重的问题,但它仍然是有害的。
那么可以使用标准 C 库的例程来防止缓冲区溢出吗?是的,不过并不容易。如果计划沿着这条路线走,您需要理解上述的所有要点。或者,您可以使用下面几节将要讲述的一种替代方法。
OpenBSD 的 strlcpy/strlcat
OpenBSD 开发人员开发了一种不同的静态方法,这种方法基于他们开发的新函数 strlcpy(3) 和 strlcat(3) 。这些函数执行字符串复制和拼接,不过更不容易出错。这些函数的原型如下:
size_t strlcpy (char *dst, const char *src, size_t size);
size_t strlcat (char *dst, const char *src, size_t size);
strlcpy() 函数把以 NUL 结尾的字符串从“ src ”复制到“ dst ”(最多 size-1 个字符)。 strlcat() 函数把以 NUL 结尾的字符串 src 附加到 dst 的结尾(但是目标中的字符数目将不超过 size-1)。
初看起来,它们的原型和标准 C 库函数并没有多大区别。但是事实上,它们之间存在一些显着区别。这些函数都接受目标的总大小(而不是剩余空间)作为参数。这意味着您不必连续地重新计算空间大小,而这是一项易于出错的任务。此外,只要目标的大小至少为 1,两个函数都保证目标将以 NUL 结尾(您不能将任何内容放入零长度的缓冲区)。如果没有发生缓冲区溢出,返回值始终是组合字符串的长度;这使得检测缓冲区溢出真正变得容易了。
遗憾的是, strlcpy(3) 和 strlcat(3) 并不是在类 UNIX 系统的标准库中普遍可用。OpenBSD 和 Solaris 将它们内置在 <string.h> 中,但是 GNU/Linux 系统却不是这样。这并不是一件那么困难的事情;因为当底层系统没有提供它们时,您甚至可以将一些小函数直接包括在自己的程序源代码中。
SafeStr
Messier 和 Viega 开发了“SafeStr”库,这是一种用于 C 的动态方法,它自动根据需要调整字符串的大小。使用 malloc() 实现所使用的相同技巧,Safestr 字符串很容易转换为常规的 C“ char * ”字符串:safestr 在传递指针“之前”的地址处存储重要信息。这种技术的优点在于,在现有程序中使用 SafeStr 将会很容易。SafeStr 还支持“只读”和“受信任”的字符串,这也可能是有用的。这种方法的一个问题在于它需要 XXL(这是一个给 C 添加异常处理和资源管理支持的库),因此您实际上要仅为了处理字符串而引入一个重要的库。Safestr 是在开放源代码的 BSD 风格的许可证下发布的。
C++ std::string
针对 C++ 用户的另一种解决方案是标准的 std::string 类,这是一种动态的方法(缓冲区根据需要而增长)。它几乎是不需要伤脑筋的,因为 C++ 语言直接支持该类,因此不需要做特殊的工作就可使用它,并且其他库也可能会使用它。就其本身而言, std::string 通常会防止缓冲区溢出,但是如果通过它提取一个普通 C 字符串(比如使用 data() 或 c_str() ),那么上面讨论的所有问题都会重新出现。还要记住 data() 并不总是返回以 NUL 结尾的字符串。
由于种种历史原因,许多 C++ 库和预先存在的程序都创建了它们自己的字符串类。这可能使得 std::string 更难于使用,并且在使用那些库或修改那些程序时效率很低,因为不同的字符串类型将不得不连续地来回转换。并非其他所有那些字符串类都会防止缓冲区溢出,并且如果它们对 C 不受保护的 char* 类型执行自动转换,那么缓冲区溢出缺陷很容易引入那些类中。
❼ CPU里专用寄存器和通用寄存器是不是一、二级缓存
寄存器组可分为专用寄存器和通用寄存器。专用寄存器的作用是固定的,分别寄存相应的数据。而通用寄存器用途广泛并可由程序员规定其用途。通用寄存器的数目因微处理器而异。这里说的寄存器全部都指的是一级缓存即L1
❽ OPENGL 缓冲区中的颜色缓存的功能是什么
颜色缓存
指程序员绘图所用的缓存,分为:
左、右缓存——用于立体感视图(必须要有左缓存);
前、后缓存——用于双缓存(必须要有前缓存);
4个辅助缓存——可选择的、不可显示(程序员可以自己定义和使用它们)。
❾ 为什么说cache对程序员是透明的
cache对程序员是透明的是因为程序员不需要知道其运行原理。因为程序员不需要知道cache的缓存机制,直接调用cache接口即可实现cache缓存。
cache独一无二地提供了三种整合的、能并发访问同一数据的数据库技术:成熟的对象访问方式,高性能的 SQL 访问方式以及丰富的多维访问。在对象数据、关系型数据以及多维数据视图之间不需要映射,这样就大大节省了开发和运行时间。
(9)程序员常见缓存种类扩展阅读:
cache的作用
Caché提供了快速 Web 应用开发、高速的事务处理、大规模的扩展性、对事务数据的实时查询。 Caché运行概述对Caché架构和性能进行了深层次的描述。 Caché的技术优势主要在为什么选择Caché这一文档中称述。
在小册子以多维引擎全面整合对象和 SQL 中,你可以了解到后关系型技术更多的优势。 Caché问与答中主要回答了一些关于Caché的常见问题,以及为什么增值商和企业选择Caché来提升他们应用的性能。
❿ mybatis的缓存机制是怎么样的
通常为了减轻数据库的压力,我们会引入缓存。在Dao查询数据库之前,先去缓存中找是否有要找的数据,如果有则用缓存中的数据即可,就不用查询数据库了。如果没有才去数据库中查找。这样就能分担一下数据库的压力。另外,为了让缓存中的数据与数据库同步,我们应该在该数据发生变化的地方加入更新缓存的逻辑代码。这样无形之中增加了工作量,同时也是一种对原有代码的入侵。这对于有着代码洁癖的程序员来说,无疑是一种伤害。MyBatis框架早就考虑到了这些问题,因此MyBatis提供了自定义的二级缓存概念,方便引入我们自己的缓存机制,而不用更改原有的业务逻辑。