月度归档:2014年06月

使用sbt搭建Scala开发环境

Scala是一种运行于JVM之上的新型语言。JRuby, Jython, Groovy等也可以将其他语言的一些特点带进Java的生态圈。但就个人体会,Scala和这些编程语言相比,如同Symbian之于Android或 IOS。虽然大家都是一个圈子里面的,但完全不在同一个时代。加之Play Framework的助力,Scala的路会越走越宽。对于企业开发,Scala最大的障碍不在对之前Java产品的支持,而在程序员的数量上。 Scala的切入点很好,因为它是强类型的语言,在运行的性能上远超出JRuby, Jython, Groovy,执行效率与Native的Java程序几无差别。在对Java的互操作方面体现很好,这就保证了企业可在使用Scala的开发中保证了之前 Java软件产品的投资不会丢失。而它带入的函数式编程,对多线程编程的良好支持,为开发大量并发应用体现出优势。(这也是Play Framework的亮点)

相关信息参见: http://www.scala-sbt.org/release/tutorial/zh-cn/index.html  里面有详细的介绍, 本文是从网络搜集的一些介绍供参考。

http://www.scala-sbt.org/0.13/tutorial/zh-cn/Basic-Def.html  介绍sbt的使用方法等

回到现实,Scala短期内还无迹象被业界大规模采用。但如果打算将Scala带进企业开发,最好的出发点可能是自动化测试和支持软件开发的工 具。对于这两方面,大多数企业的态度是,知道长期来看它的好处,但不愿在眼下去投资。如果要想做些事情,程序员不得不牺牲自己的业余时间。而Scala用 于此再合适不过。所以下面我将Scala项目的开发搭建搭建做了一个总结。学语言,学以致用,用它去做一些正经的事情。

1. 安装、配置需要的软件

    Scala:      http://www.scala-lang.org/downloads
Scala IDE:  http://scala-ide.org/
Scala-sbt: http://www.scala-sbt.org/release/docs/Getting-Started/Setup.html因为sbt会使用ivy作为库管理工具。ivy默认把library repository建在user home下面。Unix/Linux/Mac OS都还好说,如果就一个分区(或一个逻辑分区),无所谓发在哪个位置啦。如果操作系统是Windows,有分了C: D: E: 等若干分区,还是不要放在默认的%USERPROFILE%下面,C盘会随着开发的项目越来越多,大量的空间被开发库所占用。定制library local repository的位置的方法是:编辑文件sbt启动脚本: [sbt安装目录]\sbt.bat,设置Java启动参数:-Dsbt.ivy.home=E:/dev/ivy/
@REM SBT launcher script
@REM
@REM Envioronment:
@REM JAVA_HOME - location of a JDK home dir (mandatory)
@REM SBT_OPTS  - JVM options (optional)
@setlocal
@echo off
set SBT_HOME=%~dp0
set ERROR_CODE=0
rem We use the value of the JAVACMD environment variable if defined
set _JAVACMD=%JAVACMD%
if "%_JAVACMD%"=="" (
if not "%JAVA_HOME%"=="" (
if exist "%JAVA_HOME%\bin\java.exe" set "_JAVACMD=%JAVA_HOME%\bin\java.exe"
)
)
if "%_JAVACMD%"=="" set _JAVACMD=java
rem We use the value of the JAVA_OPTS environment variable if defined
set _JAVA_OPTS=%JAVA_OPTS%
if "%_JAVA_OPTS%"=="" set _JAVA_OPTS=-Xmx512M -XX:MaxPermSize=256m -XX:ReservedCodeCacheSize=128m -Dsbt.log.format=true -Dsbt.ivy.home=E:/dev/ivy/
:run
"%_JAVACMD%" %_JAVA_OPTS% %SBT_OPTS% -cp "%SBT_HOME%jansi.jar;%SBT_HOME%sbt-launch.jar;%SBT_HOME%classes" SbtJansiLaunch %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal
exit /B %ERROR_CODE%

用sbt创建,运行,测试和发布项目都很方便,但这还不够,软件开发是要写代码的。而务实的开发者都会用现代的IDE去写代码。用写字板,vi或其他文本 编辑做开发,更多的是体现一种编程文化的象征意义,正经干活没必要放着更先进的好东西不用。好,这方面咱们就不解释了。说正事。安装sbteclipse 插件 https://github.com/typesafehub/sbteclipse , 可以在sbt console里面生成eclipse scala project。更为有用的事,sbteclipse在生成项目时会根据build.sbt里面定义的库依赖,生成一个.classpath文件。也就是 说把Eclipse Scala项目的classpath,设置好了。如果以后的开发中有使用新的第三方库,安装如下次序,可以更新classpath的配置

1). 在build.sbt里面定义库依赖2). 在sbt console里面执行update命令,下载相应的库到local library repository3). 在sbt console里面执行eclipse命令,更新classpath设置

2. 用sbt创建项目

这里用一个例子来说明:

f:\tmp\test>dir
Volume in drive F is file
Volume Serial Number is A646-F0A7

Directory of f:\tmp\test

02/19/2013  02:09 PM    <DIR>          .
02/19/2013  02:09 PM    <DIR>          ..
02/19/2013  11:04 AM               269 build.sbt
02/19/2013  02:08 PM    <DIR>          target
1 File(s)            269 bytes
3 Dir(s)  29,323,710,464 bytes free

f:\tmp\test>type build.sbt
name := "Project Plan"
version := "1.0"
scalaVersion := "2.9.2"

libraryDependencies ++= Seq(
"org.mongodb" %% "casbah" % "2.5.0",
"net.sourceforge" % "mpxj" % "4.3.0"
)

f:\tmp\test>sbt
[info] Loading global plugins from C:\Users\gzhang\.sbt\plugins
[info] Set current project to Project Plan (in build file:/F:/tmp/test/)
> eclipse
[info] About to create Eclipse project files for your project(s).
[info] Updating {file:/F:/tmp/test/}default-c4a35f...
[info] Resolving org.scala-lang#scala-library;2.9.2 ...
[info] Resolving org.mongodb#casbah_2.9.2;2.5.0 ...
[info] Resolving org.mongodb#casbah-commons_2.9.2;2.5.0 ...
[info] Resolving com.github.nscala-time#nscala-time_2.9.2;0.2.0 ...
[info] Resolving joda-time#joda-time;2.1 ...
[info] Resolving org.joda#joda-convert;1.2 ...
[info] Resolving org.specs2#specs2_2.9.2;1.12.2 ...
[info] Resolving org.specs2#specs2-scalaz-core_2.9.2;6.0.1 ...
[info] Resolving org.slf4j#slf4j-api;1.6.0 ...
[info] Resolving org.mongodb#mongo-java-driver;2.10.1 ...
[info] Resolving org.mongodb#casbah-core_2.9.2;2.5.0 ...
[info] Resolving org.mongodb#casbah-query_2.9.2;2.5.0 ...
[info] Resolving org.mongodb#casbah-gridfs_2.9.2;2.5.0 ...
[info] Resolving net.sourceforge#mpxj;4.3.0 ...
[info] Resolving org.apache.poi#poi;3.7 ...
[info] Resolving junit#junit;3.8.1 ...
[info] Done updating.
[info] Successfully created Eclipse project files for project(s):
[info] Project Plan
> exit

f:\tmp\test>tree
Folder PATH listing for volume file
Volume serial number is A646-F0A7
F:.
├─project
│  └─target
│      └─config-classes
├─src
│  ├─main
│  │  ├─java
│  │  └─scala
│  └─test
│      ├─java
│      └─scala
└─target
├─scala-2.9.2
│  └─cache
│      └─update
└─streams
└─$global
├─ivy-configuration
│  └─$global
├─ivy-sbt
│  └─$global
├─project-descriptors
│  └─$global
└─update
└─$global

其中,
定义build.sbt文件,请参照sbt的文档: http://www.scala-sbt.org/release/docs/index.html 。 特别说明的是,当添加一个依赖库的时候,通过Maven Central Repository Search来查找很便捷。比如,项目需要使用Mongo DB的driver

在Maven Central Repository里面查找”mongodb“:

 

点击所需库的版本号到Artifact Details页面:

 

展开Scala SBT,将里面的Dependency Information拷贝粘贴到build.sbt文件里面,然后在sbt console里面运用update,eclipse。下载库,配置项目的classpath就自动完成了。

Ehcache 整合Spring 使用页面、对象缓存

Ehcache在很多项目中都出现过,用法也比较简单。一般的加些配置就可以了,而且Ehcache可以对页面、对象、数据进行缓存,同时支持集群 /分布式缓存。如果整合Spring、Hibernate也非常的简单,Spring对Ehcache的支持也非常好。EHCache支持内存和磁盘的缓 存,支持LRU、LFU和FIFO多种淘汰算法,支持分布式的Cache,可以作为Hibernate的缓存插件。同时它也能提供基于Filter的 Cache,该Filter可以缓存响应的内容并采用Gzip压缩提高响应速度。

一、准备工作

如果你的系统中已经成功加入Spring、Hibernate;那么你就可以进入下面Ehcache的准备工作。

1、 下载jar包

Ehcache 对象、数据缓存:http://ehcache.org/downloads/destination?name=ehcache-core-2.5.2-distribution.tar.gz&bucket=tcdistributions&file=ehcache-core-2.5.2-distribution.tar.gz

Web页面缓存:http://ehcache.org/downloads/destination?name=ehcache-web-2.0.4-distribution.tar.gz&bucket=tcdistributions&file=ehcache-web-2.0.4-distribution.tar.gz

2、 需要添加如下jar包到lib目录下

ehcache-core-2.5.2.jar

ehcache-web-2.0.4.jar 主要针对页面缓存

3、 当前工程的src目录中加入配置文件

ehcache.xml

ehcache.xsd

这些配置文件在ehcache-core这个jar包中可以找到

二、Ehcache基本用法

CacheManager cacheManager = CacheManager.create();

// 或者

cacheManager = CacheManager.getInstance();

// 或者

cacheManager = CacheManager.create("/config/ehcache.xml");

// 或者

cacheManager = CacheManager.create("http://localhost:8080/test/ehcache.xml");

cacheManager = CacheManager.newInstance("/config/ehcache.xml");

// .......

// 获取ehcache配置文件中的一个cache

Cache sample = cacheManager.getCache("sample");

// 获取页面缓存
BlockingCache cache = new BlockingCache(cacheManager.getEhcache("SimplePageCachingFilter"));

// 添加数据到缓存中

Element element = new Element("key", "val");

sample.put(element);

// 获取缓存中的对象,注意添加到cache中对象要序列化 实现Serializable接口

Element result = sample.get("key");

// 删除缓存

sample.remove("key");

sample.removeAll();

// 获取缓存管理器中的缓存配置名称

for (String cacheName : cacheManager.getCacheNames()) {

System.out.println(cacheName);

}

// 获取所有的缓存对象

for (Object key : cache.getKeys()) {

System.out.println(key);

}

// 得到缓存中的对象数

cache.getSize();

// 得到缓存对象占用内存的大小

cache.getMemoryStoreSize();

// 得到缓存读取的命中次数

cache.getStatistics().getCacheHits();

// 得到缓存读取的错失次数

cache.getStatistics().getCacheMisses();

三、页面缓存

页面缓存主要用Filter过滤器对请求的url进行过滤,如果该url在缓存中出现。那么页面数据就从缓存对象中获取,并以gzip压缩后返回。 其速度是没有压缩缓存时速度的3-5倍,效率相当之高!其中页面缓存的过滤器有CachingFilter,一般要扩展filter或是自定义 Filter都继承该CachingFilter。

CachingFilter功能可以对HTTP响应的内容进行缓存。这种方式缓存数据的粒度比较粗,例如缓存整张页面。它的优点是使用简单、效率高,缺点是不够灵活,可重用程度不高。

EHCache使用SimplePageCachingFilter类实现Filter缓存。该类继承自CachingFilter,有默认产生 cache key的calculateKey()方法,该方法使用HTTP请求的URI和查询条件来组成key。也可以自己实现一个Filter,同样继承 CachingFilter类,然后覆写calculateKey()方法,生成自定义的key。

CachingFilter输出的数据会根据浏览器发送的Accept-Encoding头信息进行Gzip压缩。

在使用Gzip压缩时,需注意两个问题:

1. Filter在进行Gzip压缩时,采用系统默认编码,对于使用GBK编码的中文网页来说,需要将操作系统的语言设置为:zh_CN.GBK,否则会出现乱码的问题。

2. 默认情况下CachingFilter会根据浏览器发送的请求头部所包含的Accept-Encoding参数值来判断是否进行Gzip压缩。虽然 IE6/7浏览器是支持Gzip压缩的,但是在发送请求的时候却不带该参数。为了对IE6/7也能进行Gzip压缩,可以通过继承 CachingFilter,实现自己的Filter,然后在具体的实现中覆写方法acceptsGzipEncoding。

具体实现参考:

protected boolean acceptsGzipEncoding(HttpServletRequest request) {

boolean ie6 = headerContains(request, "User-Agent", "MSIE 6.0");

boolean ie7 = headerContains(request, "User-Agent", "MSIE 7.0");

return acceptsEncoding(request, "gzip") || ie6 || ie7;

}

在ehcache.xml中加入如下配置

<?xml version="1.0" encoding="gbk"?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd">

<diskStore path="java.io.tmpdir"/>

<defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="false"/>

<!--

配置自定义缓存

maxElementsInMemory:缓存中允许创建的最大对象数

eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。

timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,

两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,

如果该值是 0 就意味着元素可以停顿无穷长的时间。

timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,

这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。

overflowToDisk:内存不足时,是否启用磁盘缓存。

memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。

-->

<cache name="SimplePageCachingFilter"

maxElementsInMemory="10000"

eternal="false"

overflowToDisk="false"

timeToIdleSeconds="900"

timeToLiveSeconds="1800"

memoryStoreEvictionPolicy="LFU" />

</ehcache>

具体代码:

package com.hoo.ehcache.filter;

import java.util.Enumeration;

import javax.servlet.FilterChain;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import net.sf.ehcache.CacheException;

import net.sf.ehcache.constructs.blocking.LockTimeoutException;

import net.sf.ehcache.constructs.web.AlreadyCommittedException;

import net.sf.ehcache.constructs.web.AlreadyGzippedException;

import net.sf.ehcache.constructs.web.filter.FilterNonReentrantException;

import net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter;

import org.apache.commons.lang.StringUtils;

import org.apache.log4j.Logger;

/**

* <b>function:</b> mobile 页面缓存过滤器

* @author hoojo

* @createDate 2012-7-4 上午09:34:30

* @file PageEhCacheFilter.java

* @package com.hoo.ehcache.filter

* @project Ehcache

* @blog http://blog.csdn.net/IBM_hoojo

* @email hoojo_@126.com

* @version 1.0

*/

public class PageEhCacheFilter extends SimplePageCachingFilter {

private final static Logger log = Logger.getLogger(PageEhCacheFilter.class);

private final static String FILTER_URL_PATTERNS = "patterns";

private static String[] cacheURLs;

private void init() throws CacheException {

String patterns = filterConfig.getInitParameter(FILTER_URL_PATTERNS);

cacheURLs = StringUtils.split(patterns, ",");

}

@Override

protected void doFilter(final HttpServletRequest request,

final HttpServletResponse response, final FilterChain chain)

throws AlreadyGzippedException, AlreadyCommittedException,

FilterNonReentrantException, LockTimeoutException, Exception {

if (cacheURLs == null) {

init();

}

String url = request.getRequestURI();

boolean flag = false;

if (cacheURLs != null && cacheURLs.length > 0) {

for (String cacheURL : cacheURLs) {

if (url.contains(cacheURL.trim())) {

flag = true;

break;

}

}

}

// 如果包含我们要缓存的url 就缓存该页面,否则执行正常的页面转向

if (flag) {

String query = request.getQueryString();

if (query != null) {

query = "?" + query;
}

log.info("当前请求被缓存:" + url + query);

super.doFilter(request, response, chain);

} else {

chain.doFilter(request, response);

}

}

@SuppressWarnings("unchecked")

private boolean headerContains(final HttpServletRequest request, final String header, final String value) {

logRequestHeaders(request);

final Enumeration accepted = request.getHeaders(header);

while (accepted.hasMoreElements()) {

final String headerValue = (String) accepted.nextElement();

if (headerValue.indexOf(value) != -1) {

return true;

}

}

return false;

}

/**

* @see net.sf.ehcache.constructs.web.filter.Filter#acceptsGzipEncoding(javax.servlet.http.HttpServletRequest)

* <b>function:</b> 兼容ie6/7 gzip压缩

* @author hoojo

* @createDate 2012-7-4 上午11:07:11

*/

@Override

protected boolean acceptsGzipEncoding(HttpServletRequest request) {

boolean ie6 = headerContains(request, "User-Agent", "MSIE 6.0");

boolean ie7 = headerContains(request, "User-Agent", "MSIE 7.0");

return acceptsEncoding(request, "gzip") || ie6 || ie7;

}

}

这里的PageEhCacheFilter继承了SimplePageCachingFilter,一般情况下 SimplePageCachingFilter就够用了,这里是为了满足当前系统需求才做了覆盖操作。使用 SimplePageCachingFilter需要在web.xml中配置cacheName,cacheName默认是 SimplePageCachingFilter,对应ehcache.xml中的cache配置。

在web.xml中加入如下配置

<!-- 缓存、gzip压缩核心过滤器 -->

<filter>

<filter-name>PageEhCacheFilter</filter-name>

<filter-class>com.hoo.ehcache.filter.PageEhCacheFilter</filter-class>

<init-param>

<param-name>patterns</param-name>

<!-- 配置你需要缓存的url -->

<param-value>/cache.jsp, product.action, market.action </param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>PageEhCacheFilter</filter-name>

<url-pattern>*.action</url-pattern>

</filter-mapping>

<filter-mapping>

<filter-name>PageEhCacheFilter</filter-name>

<url-pattern>*.jsp</url-pattern>

</filter-mapping>

当第一次请求这些页面后,这些页面就会被添加到缓存中,以后请求这些页面将会从缓存中获取。你可以在cache.jsp页面中用小脚本来测试该页面 是否被缓存。<%=new Date()%>如果时间是变动的,则表示该页面没有被缓存或是缓存已经过期,否则则是在缓存状态了。

四、对象缓存

对象缓存就是将查询的数据,添加到缓存中,下次再次查询的时候直接从缓存中获取,而不去数据库中查询。

对象缓存一般是针对方法、类而来的,结合Spring的Aop对象、方法缓存就很简单。这里需要用到切面编程,用到了Spring的MethodInterceptor或是用@Aspect。

代码如下:

package com.hoo.common.ehcache;

import java.io.Serializable;

import net.sf.ehcache.Cache;

import net.sf.ehcache.Element;

import org.aopalliance.intercept.MethodInterceptor;

import org.aopalliance.intercept.MethodInvocation;

import org.apache.log4j.Logger;

import org.springframework.beans.factory.InitializingBean;

/**

* <b>function:</b> 缓存方法拦截器核心代码

* @author hoojo

* @createDate 2012-7-2 下午06:05:34

* @file MethodCacheInterceptor.java

* @package com.hoo.common.ehcache

* @project Ehcache

* @blog http://blog.csdn.net/IBM_hoojo

* @email hoojo_@126.com

* @version 1.0

*/

public class MethodCacheInterceptor implements MethodInterceptor, InitializingBean {

private static final Logger log = Logger.getLogger(MethodCacheInterceptor.class);

private Cache cache;

public void setCache(Cache cache) {

this.cache = cache;

}

public void afterPropertiesSet() throws Exception {

log.info(cache + " A cache is required. Use setCache(Cache) to provide one.");

}

public Object invoke(MethodInvocation invocation) throws Throwable {

String targetName = invocation.getThis().getClass().getName();

String methodName = invocation.getMethod().getName();

Object[] arguments = invocation.getArguments();

Object result;

String cacheKey = getCacheKey(targetName, methodName, arguments);

Element element = null;

synchronized (this) {

element = cache.get(cacheKey);

if (element == null) {

log.info(cacheKey + "加入到缓存: " + cache.getName());

// 调用实际的方法

result = invocation.proceed();

element = new Element(cacheKey, (Serializable) result);

cache.put(element);

} else {

log.info(cacheKey + "使用缓存: " + cache.getName());

}

}

return element.getValue();

}

/**

* <b>function:</b> 返回具体的方法全路径名称 参数

* @author hoojo

* @createDate 2012-7-2 下午06:12:39

* @param targetName 全路径

* @param methodName 方法名称

* @param arguments 参数

* @return 完整方法名称

*/

private String getCacheKey(String targetName, String methodName, Object[] arguments) {

StringBuffer sb = new StringBuffer();

sb.append(targetName).append(".").append(methodName);

if ((arguments != null) && (arguments.length != 0)) {

for (int i = 0; i < arguments.length; i++) {

sb.append(".").append(arguments[i]);

}

}

return sb.toString();

}

}

这里的方法拦截器主要是对你要拦截的类的方法进行拦截,然后判断该方法的类路径+方法名称+参数值组合的cache key在缓存cache中是否存在。如果存在就从缓存中取出该对象,转换成我们要的返回类型。没有的话就把该方法返回的对象添加到缓存中即可。值得主意的 是当前方法的参数和返回值的对象类型需要序列化。

我们需要在src目录下添加applicationContext.xml完成对MethodCacheInterceptor拦截器的配置,该配置主意是注入我们的cache对象,哪个cache来管理对象缓存,然后哪些类、方法参与该拦截器的扫描。

添加配置如下:

<context:component-scan base-package="com.hoo.common.interceptor"/>

<!-- 配置eh缓存管理器 -->

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>

<!-- 配置一个简单的缓存工厂bean对象 -->

<bean id="simpleCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">

<property name="cacheManager" ref="cacheManager" />

<!-- 使用缓存 关联ehcache.xml中的缓存配置 -->

<property name="cacheName" value="mobileCache" />

</bean>

<!-- 配置一个缓存拦截器对象,处理具体的缓存业务 -->

<bean id="methodCacheInterceptor" class="com. hoo.common.interceptor.MethodCacheInterceptor">

<property name="cache" ref="simpleCache"/>

</bean>

<!-- 参与缓存的切入点对象 (切入点对象,确定何时何地调用拦截器) -->

<bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">

<!-- 配置缓存aop切面 -->

<property name="advice" ref="methodCacheInterceptor" />

<!-- 配置哪些方法参与缓存策略 -->

<!--

.表示符合任何单一字元

###  +表示符合前一个字元一次或多次

###  *表示符合前一个字元零次或多次

###  \Escape任何Regular expression使用到的符号

-->

<!-- .*表示前面的前缀(包括包名) 表示print方法-->

<property name="patterns">

<list>

<value>com.hoo.rest.*RestService*\.*get.*</value>

<value>com.hoo.rest.*RestService*\.*search.*</value>

</list>

</property>

</bean>

在ehcache.xml中添加如下cache配置

<cache name="mobileCache"

maxElementsInMemory="10000"

eternal="false"

overflowToDisk="true"

timeToIdleSeconds="1800"

timeToLiveSeconds="3600"

memoryStoreEvictionPolicy="LFU" />

作者:hoojo
出处:

http://www.cnblogs.com/hoojo/archive/2012/07/12/2587556.html

blog:http://blog.csdn.net/IBM_hoojo
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权所有,转载请注明出处本文出自:http://www.cnblogs.com/hoojo/archive/2012/07/12/2587556.html

CPU的工作过程

    接触计算机应该是在95年左右,那个时候最流行 的一个词语是多媒体。 依旧记得当时在同学家看同学输入几个DOS命令就成功的打开了一个游戏,当时实在是佩服的五体投地。因为对我来说,屏幕上的东西简直就是天书。有了计算机 我们生活发生了巨大的变化,打游戏,上网,购物, 看小说, 听音乐,听评书, 看电影, 看电视..... 聊天,甚至到现在以此为业。有时无不感叹计算机的强大。

人类总是聪明的而又懒惰的。即便是1+1这种简单的计算都不想自己 做,1623年Wilhelm Schickard 制作了一个能进行六位以内数加减法,并能通过铃声输出答案的"计算钟"。通过转动齿轮来进行操作。 这已经相当高端了,说起计算器,我们5000年文明古国在东汉末年就有记载了---算盘。

计算机的发展也是随着科技的发展经历了机械计算机、电子计算机、晶体管计算机、小规模集成电路和超大规模集成电路计算机。我们无意讨论整个计算机的发展过程,主要还是介绍基于冯诺依曼体系结构的现代计算机。

 一. 计算机的发展

计算机的发展包括了硬件和软件的发展,硬件的发展为计算机提供了更快的处理速度,而软件的发展为用户提供了更好的体验。两者相辅相成,密不可分。

  • 第一阶段: 60年代中期以前,是计算机系统发展的早期时代。在这个时期通用硬件已经相当普遍,软件却是为每个具体应用而专门编写的,大多数人认为软件开发是无需预先计划的事情。这时的软件实际上就是规模较小的程序,程序的编写者和使用者往往是同一个(或同一组)人;
  • 第二阶段:从60年代中期到70年代中期,是计算机系统发展的第二代。在这10年中计算机技术有了很大进步。多道程序、多用户系统引入了人机交互的新概念,开创了计算机应用的新境界,使硬件和软件的配合上了一个新的层次;
  • 第三阶段:计算机系统发展的第三代从20世纪70年代中期开始,并且跨越了整整10年。在这10年中计算机技术又有了很大进步。分布式系统极大地增加亍计算机系统的复杂性,局域网、广域网、宽带数字通信以及对“即时”数据访问需求的增加,都对软件开发者提出了更高的要求;
  • 第四阶段:在计算机系统发展的第四代已经不再看重单台计算机和程序,人们感受到的是硬件和软件的综合效果。由复杂操作系统控制的强大的桌面机及局域网和广域网,与先进的应用软件相配合,已经成为当前的主流。计算机体系结构已迅速地从集中的主机环境转变成分布的客户机/服务器。

二. 冯诺依曼体系结构

现在计算机的结构主要是:冯诺依曼体系结构, 有以下特点:

  1. 计算机处理的数据和指令一律用二进制数表示;
  2. 指令和数据不加区别混合存储在同一个存储器中;
  3. 顺序执行程序的每一条指令;
  4. 计算机硬件由运算器、控制器、存储器、输入设备和输出设备五大部分组成。

冯诺依曼体系结构的计算机必须具有如下功能:

  • 把需要的程序和数据送至计算机中;
  • 必须具有长期记忆程序、数据、中间结果及最终运算结果的能力;
  • 能够完成各种算术、逻辑运算和数据传送等数据加工处理的能力;
  • 能够根据需要控制程序走向,并能根据指令控制机器的各部件协调操作;
  • 能够按照要求将处理结果输出给用户。

 

三.  CPU的工作过程

以下内容,来源: https://software.intel.com/zh-cn/articles/book-Processor-Architecture_CPU_work_process

CPU的基本工作是执行存储的指令序列,即程序。程序的执行过程实际上是不断地取出指令、分析指令、执行指令的过程。

CPU从存放程序的主存储器里取出一条指令,译码并执行这条指令,保存执行结果,紧接着又去取指令,译码,执行指令……,如此周而复始,反复循环,使得计算机能够自动地工作。除非遇到停机指令,否则这个循环将一直进行下去。其过程如图3-3所示

 程序的执行过程

指令的执行过程

几乎所有的冯•诺伊曼型计算机的CPU,其工作都可以分为5个阶段:取指令、指令译码、执行指令、访存取数和结果写回。如图3-4所示。

图3-4 指令的执行过程

1.取指令阶段

取指令(Instruction Fetch,IF)阶段是将一条指令从主存中取到指令寄存器的过程。

程序计数器PC中的数值,用来指示当前指令在主存中的位置。当一条指令被取出后,PC中的数值将根据指令字长度而自动递增。若为单字长指令,则(PC)+1PC,若为双字长指令,则(PC)+2PC,依此类推。

2.指令译码阶段

取出指令后,计算机立即进入指令译码(Instruction Decode,ID)阶段。

在指令译码阶段,指令译码器按照预定的指令格式,对取回的指令进行拆分和解释,识别和区分出不同的指令类别及各种获取操作数的方法。

在组合逻辑控制的计算机中,指令译码器对不同的指令操作码产生不同的控制电位,以形成不同的微操作序列;在微程序控制的计算机中,指令译码器用指令操作码找到执行该指令的微程序的入口,并从此入口开始执行。

在传统的设计里,CPU中负责指令译码的部分是无法改变的硬件。不过,在众多运用微程序控制技术的新型CPU中,微程序有时是可重写的,可以通过修改成品CPU来改变CPU的译码方式。

3.执行指令阶段

在取指令和指令译码阶段之后,接着进入执行指令(Execute,EX)阶段。

此阶段的任务是完成指令所规定的各种操作,具体实现指令的功能。为此,CPU的不同部分被连接起来,以执行所需的操作。

例如,如果要求完成一个加法运算,算术逻辑单元(ALU)将被连接到一组输入和一组输出,输入端提供需要相加的数值,而输出端将含有最后的运算结果。

4.访存取数阶段

根据指令需要,有可能要访问主存,读取操作数,这样就进入了访存取数(Memory,MEM)阶段。

此阶段的任务是:根据指令地址码,得到操作数在主存中的地址,并从主存中读取该操作数用于运算。

5.结果写回阶段

作为最后一个阶段,结果写回(Writeback,WB)阶段把执行指令阶段的运行结果数据“写回”到某种存储形式。结果数据经常被写到CPU的内 部寄存器中,以便被后续的指令快速地存取。在有些情况下,结果数据也可被写入相对较慢、但较廉价且容量较大的主存。许多指令还会改变程序状态字寄存器中标 志位的状态,这些标志位标识着不同的操作结果,可被用来影响程序的动作。

在指令执行完毕、结果数据写回之后,若无意外事件(如结果溢出等)发生,计算机就接着从程序计数器PC中取得下一条指令地址,开始新一轮的循环,下一个指令周期将正常地顺序取出下一条指令。

许多新型CPU可以同时取出、译码和执行多条指令,体现出并行处理的特性。

 指令周期

1.指令周期的基本概念

(1)指令周期

CPU取出一条指令并执行该指令所需的时间称为指令周期。
指令周期的长短与指令的复杂程度有关。

(2)CPU周期

指令周期常常用若干个CPU周期数来表示。
由于CPU内部的操作速度较快,而CPU访问一次主存所花的时间较长,因此通常用从主存读取一条指令的最短时间来规定CPU周期。
CPU周期也称为机器周期。

(3)时钟周期

一个CPU周期包含有若干个时钟周期。
时钟周期是处理操作的最基本时间单位,由机器的主频决定。
一个CPU周期的时间宽度由若干个时钟周期的总和决定。

图3-5为采用定长CPU周期的指令周期示意图。

图3-5 指令周期

(4)取出和执行任何一条指令所需的最短时间为两个CPU周期。

任何一条指令,它的指令周期至少需要两个CPU周期,而复杂指令的指令周期则需要更多的CPU周期。这是因为,一条指令的取出阶段需要一个CPU周 期时间,而一条指令的执行阶段则需要至少一个CPU周期时间。由于不同复杂度指令的执行周期所需的CPU周期数不尽相等,因此,各种指令的指令周期也是不 尽相同的。

例题3-1:现有一个由5条典型指令组成的程序(如表3-1所示),请分析每一条指令的指令周期。

表3-1 一个由5条典型指令组成的程序

主存储器
操作说明
地址
指令或数据内容
020
CLA
0→AC,累加器AC清零
021
ADD 30
(AC)+(30)AC,累加器AC的值与主存地址30中的数据相加,结果存入累加器AC
022
STA 40
(AC)40,把累加器AC的值存入主存地址40
023
NOP
空操作,没有任何功能
024
JMP 21
无条件转移到主存地址21处开始执行
030
操作数
040
存放运算结果

【解】

①CLA指令

CLA指令是一条不访问主存的清零指令,它需要2个CPU周期,其中取指令阶段需要1个CPU周期,执行指令阶段需要1个CPU周期。

图3-6 CLA指令周期

在第1个CPU周期,即取指令阶段,CPU从主存取出指令,对程序计数器PC加1,并对指令操作码进行译码,以确定执行何种操作;

在第2个CPU周期,即执行指令阶段,CPU完成指令所要求的操作。

②ADD 30指令

ADD 30指令是一条访问主存取数并执行加法的指令,其指令周期由3个CPU周期组成,其中取指令阶段需要1个CPU周期,执行指令阶段需要2个CPU周期。

图3-7 ADD 30指令周期

在第1个CPU周期,即取指令阶段,CPU从主存取出指令并译码,以确定执行何种操作;

执行指令阶段由2个CPU周期组成,其中在第2个CPU周期,CPU将指令的地址码(操作数地址)部分(30)送往地址寄存器,并完成地址译码,而在第3个CPU周期,CPU从主存取出操作数,并执行加法操作。

③STA 40指令

STA 40指令是一条访问主存的存数指令,其指令周期由3个CPU周期组成,其中取指令阶段需要1个CPU周期,执行指令阶段需要2个CPU周期。

图3-8 STA 40指令周期

在第1个CPU周期,即取指令阶段,CPU从主存取出指令并译码,以确定执行何种操作;

执行指令阶段由2个CPU周期组成,其中在第2个CPU周期,CPU将指令的地址码(操作数地址)部分(40)送往地址寄存器,并完成地址译码,而在第3个CPU周期,CPU把累加寄存器的内容写入主存单元(40)中。

④NOP指令

NOP指令是一条空操作指令,没有任何功能,相当于CPU空转,但仍需要2个CPU周期,其中取指令阶段需要1个CPU周期,执行指令阶段需要1个CPU周期。(指令周期图与图3-6 CLA指令相同)

在第1个CPU周期,即取指令阶段,CPU从主存取出指令并译码,以确定执行何种操作;

在第2个CPU周期,即执行指令阶段,操作控制器不发出任何控制信号,CPU不做任何操作。

⑤JMP 21指令

JMP 21指令是一条直接寻址的程序控制(转移)指令,由2个CPU周期组成,其中取指令阶段需要1个CPU周期,执行指令阶段需要1个CPU周期。

JMP 21指令周期

在第1个CPU周期,即取指令阶段,CPU从主存取出指令并译码,以确定执行何种操作;

在第2个CPU周期,即执行指令阶段,CPU把指令的地址码(转移地址)部分(21)送到程序计数器PC中,从而改变程序的执行顺序,实现程序的无条件转移。

2.用指令流程图表示指令周期

在进行计算机设计时,可以像画程序流程图那样,采用指令流程图来表示一条指令的指令周期。

在指令流程图中,

方框:代表一个操作步骤,方框中的内容表示数据通路的操作或某种控制操作。

菱形框:通常用来表示某种判别或测试,其动作依附于它前面的一个方框。

公操作符号“~”:表示一条指令已经执行完毕,转入公操作。所谓公操作,就是一条指令执行完毕后,CPU所开始进行的一些操作,这些操作主要是CPU对外设请求的处理。如果外设没有向CPU请求交换数据,那么CPU又转向主存取下一条指令。

例题3-2:对于例题3-1中由5条典型指令组成的程序,请用指令流程图表示其指令周期。

【解】

图3-10 用指令流程图表示指令周期

由图3-10可见,所有指令的取指阶段是完全相同的,而且是一个CPU周期。

但是指令的执行阶段,由于各条指令的功能不同,所用的CPU周期是各不相同的。其中,CLA、NOP、JMP指令是一个CPU周期,ADD、STA指令是两个CPU周期。

一般指令流程图有一个公共的流程段和许多并列的分支。公共流程段是取指令操作的流程序列。取指令操作是每条指令共同的操作步骤,而且所有的指令读取 步骤是相同的,所以所有读指令的操作流程是相同的。执行指令阶段的操作是各指令互不相同的操作,所以在取指令阶段之后,流程就根据指令分成许多分支,通常 为每种指令都安排一个分支流程。

例题3-3:如图3-11所示为双总线结构机器的数据通路,IR为指令寄存器,PC为程序计数器(具有自增功能),M为主存(受R/W信号控 制),AR为主存地址寄存器,DR为数据寄存器,ALU由+、-控制信号决定完成何种操作,控制信号G控制的是一个门电路。另外,线上标注有控制信号,例 如Yi表示Y寄存器的输入控制信号,R1o为寄存器R1的输出控制信号,未标字符的线为直通线,不受控制。

“ADD R2,R0”指令完成(R0)+(R2)→R0的功能操作,试画出其指令周期流程图(假设该指令的地址已放入PC中),并列出相应的微操作控制信号序列。

图3-11 双总线结构机器的数据通路

【解】

“ADD R2,R0”指令是一条加法指令,参与运算的两个数放在寄存器R2和R0中,指令周期流程图包括取指令阶段和执行指令阶段两部分。根据给定的数据通路 图,“ADD R2,R0”指令的详细指令周期流程图如图3-12所示,图的右边部分标注了每一个机器周期中用到的微操作控制信号序列。

图3-12 “ADD R2,R0”指令的详细指令周期流程图

3.2.3 时序发生器

1.时序信号

在计算机高速运行的过程中,计算机内各部件的每一个动作都必须严格遵守时间规定,不能有任何差错。

计算机内各部件的协调动作需要时间标志,而时间标志则是用时序信号来体现的。

计算机各部分工作所需的时序信号,在CPU中统一由时序发生器来产生。

例题3-4:用二进制码表示的指令和数据都放在主存里,那么CPU是怎样识别出它们是数据还是指令呢?

【解】

从时间上来说,取指令事件发生在指令周期的第一个CPU周期中,即发生在“取指令”阶段,而取数据事件发生在指令周期的后面几个CPU周期中,即发生在“执行指令”阶段。

从空间上来说,如果取出的代码是指令,那么一定送往指令寄存器,如果取出的代码是数据,那么一定送往运算器。

2.时序发生器

CPU中的时序信号发生器,其功能是用逻辑电路来发出时序信号,实现时序控制,使计算机可以准确、迅速、有条不紊地工作。

时序信号发生器是产生指令周期控制时序信号的部件,当CPU开始取指令并执行指令时,操作控制器利用时序信号发生器产生的定时脉冲的顺序和不同的脉冲间隔,提供计算机各部分工作时所需的各种微操作定时控制信号,有条理、有节奏地指挥机器各个部件按规定时间动作。

从操作控制器设计方法而言,组合逻辑控制器的时序电路比较复杂,而微程序控制器的时序电路则比较简单。

  控制方式

控制器控制一条指令运行的过程是依次执行一个确定的操作序列的过程。

为了使机器能够正确执行指令,控制器必须能够按正确的时序产生操作控制信号。

控制不同操作序列的时序信号的方法,称为控制器的控制方式。

控制方式通常分为三种:同步控制方式、异步控制方式、联合控制方式,其实质反映了时序信号的定时方式。

1.同步控制方式

同步控制方式是指操作序列中每一步操作的执行,都由确定的具有基准时标的时序信号来控制,其特点是系统有一个统一的时钟,所有的控制信号均来自这个统一的时钟信号。

在同步控制方式中,在任何情况下,给定的指令在执行时所需的CPU周期数和时钟周期数都是固定不变的。

同步控制方式有时又称为固定时序控制方式或无应答控制方式。

根据不同情况,同步控制方式可选取以下几种方案:

  1. 采用完全统一的机器周期执行各种不同的指令。显然,对简单指令和简单的操作而言,这将造成时间上的浪费。
  2. 采用不定长机器周期。将大多数操作安排在一个较短的机器周期内完成,而对于某些时间紧张的操作,则采取延长机器周期的办法来加以解决。
  3. 中央控制与局部控制结合。将大部分指令安排在固定的机器周期完成(称为中央控制),而对于少数复杂指令(乘、除、浮点运算)则采用另外的时序进行定时(称为局部控制)。

同步控制方式设计简单,操作控制容易实现。

2.异步控制方式

异步控制方式是一种按每条指令、每个操作的实际需要而占用时间的控制方式,不同指令所占用的时间完全根据需要来决定。

在异步控制方式中,每条指令的指令周期既可由数量不等的机器周期数组成,也可由执行部件完成CPU要求的操作后发回控制器的应答信号来决定。也就是说,CPU访问的每个操作控制信号的时间由其需要占用的时间来决定,每条指令、每个操作控制信号需要多少时间就占用多少时间。

显然,用这种方式形成的操作控制序列没有固定的CPU周期数和严格的时钟周期与之同步,所以称为异步方式。

异步控制方式有时又称为可变时序控制方式或应答控制方式。

在异步控制方式下,指令的运行效率高,但控制线路的硬件实现比较复杂。

异步控制方式在计算机中得到了广泛的应用。例如CPU对主存的读写、I/O设备与主存的数据交换等一般都采用异步控制方式,以保证执行时的高速度。

3.联合控制方式

现代计算机系统中一般采用的方式是同步控制和异步控制相结合的方式,即联合控制方式。

联合控制方式的设计思想是:在功能部件内部采用同步控制方式,而在功能部件之间采用异步控制方式,并且在硬件实现允许的情况下,尽可能多地采用异步控制方式。

联合控制方式通常选取以下两种方案:

  1. 大部分操作序列安排在固定的机器周期中,对某些时间难以确定的操作则以执行部件的应答信号作为本次操作的结束;
  2. 机器周期的时钟周期数固定,但是各条指令周期的机器周期数不固定。