请根据自己的理解,描述网卡计算机的驱动程序序的基本功能与编程方法?

大部分内容基于中国大学MOOC的2021考研数据结构课程所做的笔记,后续又根据2023年考研的大纲增加了一些内容,主要有操作系统引导、虚拟机、多级队列调度算法、互斥锁、调度器和闲逛进程、内存映射文件、文件系统的全局结构、虚拟文件系统、固态硬盘SSD、输入输出应用程序接口 、驱动程序接口等等。

感谢我的室友HXN,他帮我写了一部分第五章的内容。

课程内容和西电平时讲课的内容大致重合,西电可能每章会多讲一点UNIX系统的实例,可以听完这课再快速过一遍老师的课件防止漏掉什么内容。

这门课讲的其实不算特别硬核,没怎么涉及具体的代码。不过我其实感觉操作系统是个大无底洞,能学到多深基本取决于愿意花多少时间和精力。如果有闲心,推荐看下南大蒋炎岩老师的《操作系统:设计与实现》和哈工大李治军老师的《操作系统》,讲的更深入,当然难度也相应的大的多。

其他各章节的链接如下:

I/O 设备基本概念与分类

“I/O” 就是 “输入/输出”(Input/Output) I/O 设备就是可以将数据输入到计算机,或者可以接收计算机输出数据的外部设备,属于计算机中的硬件部件

  • 鼠标、键盘——典型的输入型设备
  • 移动硬盘——即可输入、又可输出的设备

UNIX系统将外部设备抽象为一种特殊的文件,用户可以使用与文件操作相同的方式对外部设备进行操作。

  • Write操作:向外部设备写出数据

  • Read操作:从外部设备读入数据

I/O 设备的分类 —— 按使用特性

I/O 设备的分类 —— 按传输速率分类

I/O 设备的分类 —— 按信息交换的单位分类

可寻址:这种设备可以随机地读写任意一块

I/O 设备由机械部件和电子部件组成

I/O 设备的机械部件

I/O 设备的机械部件主要用来执行具体I/O 操作。 如我们看得见摸得着的鼠标/键盘的按钮;显示器的LED屏;移动硬盘的磁臂、磁盘盘面。

I/O 设备的电子部件通常是一块插入主板扩充槽的印刷电路板。

I/O 设备的电子部件( I/O 控制器)

CPU通过控制线向I/O 设备发出具体的I/O指令,同时CPU还会在地址线上说明自己要操纵哪一个设备

CPU此时发出的I/O 指令可能会有一些相应的参数,这些参数会被放到数据寄存器当中

内存映像 I/O v.s. 寄存器独立编址

如果采用寄存器独立编址,这些寄存器和内存的地址空间并不统一,它们是两个独立的体系

一个通道可以控制多个I/O控制器,而一个I/O控制器又可以控制多个IO设备

I/O控制方式即:用什么样的方式来控制I/O设备的数据读/写

1.完成一次读/写操作的流程(以读操作为例)

如果I/O设备出错,也会在I/O设备的状态寄存器当中写入相应的代码

使用 p r i n t f printf printf时把内存当中存储的这些变量的数据拿出来经过CPU再输出到输出设备上

去看看B站蛋黄派大师兄“操作系统运行机制(小补充)”

CPU会给I/O模块发出一个读或者写一个块的指令,之后CPU就可以转头做其他事情,DMA控制器会根据CPU发出的这些命令,参数来完成CPU指定的一系列读写工作。当CPU指定的这些块读完或者写完之后又会由DMA控制器向CPU发出一个中断信号,然后CPU又介入处理这个中断

为了实现控制器和CPU之间的通信,会在主机-控制器接口这设置一系列的寄存器,CPU可以通过系统总线来读或者写其中的某一些寄存器当中的内容来达到控制I/O设备的目的

系统总线还会把DMA控制器和内存连接在一起,所以DMA控制器和内存之间可以直接进行数据的读写,不再需要经过CPU

DMA控制器并不是每次直接读入一整块的数据然后直接把一整块放到内存当中。其实DMA在读入数据的过程当中也是一个字一个字读入的,每次读入的一个字都是先存放在DR,再从DR写入到内存当中。用这样一个字一个字的方式最终就可以完成一整块的数据读入工作

既然需要使用I/O设备进行输出操作,用户层软件肯定需要请求操作系统提供服务,因为只有操作系统才有对硬件操作的权力

用户层软件会使用设备独立性软件这一层向上提供的系统调用接口来请求操作系统内核的服务

设备独立性软件,又称设备无关性软件。与设备的硬件特性无关的功能几乎都在这一层实现

1.向上层提供统一的调用接口(如 read/write 系统调用)

6.建立逻辑设备名到物理设备名的映射关系;根据设备类型选择调用相应的驱动程序

所谓逻辑设备名就是用户在请求使用一个设备时提供的名字,也就是用户所看到的设备名,操作系统对这些设备进行管理在背后还会有物理设备名,所以当选择某一个逻辑设备的时候操作系统需要知道逻辑设备具体对应的到底是哪一个物理设备

很多操作系统都会把设备当作一种特殊的文件,所以这个文件当然也会有存储的路径

各种设备内部的硬件特性不同,因此必须执行与它对应的特定的驱动程序才可以正常地完成对这个设备硬件的控制

思考:为何不同的设备需要不同的设备驱动程序?

  • 各式各样的设备,外形不同,其内部的电子部件(I/O控制器)也有可能不同
  • 不同设备的内部硬件特性也不同,这些特性只有厂家才知道,因此厂家须提供与设备相对应的驱动程序,CPU执行驱动程序的指令序列,来完成设置设备寄存器,检查设备状态等工作

输入输出应用程序接口 & 驱动程序接口

用户进程可以使用网络设备相关的系统调用接口来创建一个套接字对象。套接字和套接字之间需要建立点对点连接,每一个套接字会绑定一个本机的端口,通过主机IP地址和套接字绑定的端口就可以找到全世界任何一个套接字对象

P1和P3进程建立套接字连接:

P3先使用socket系统调用创建一个网络套接字对象,socket系统调用返回用户一个描述符,有了套接字对象之后还要使用bind系统调用将套接字绑定到本地端口6666。这样主机2的套接字就可以等待着被连接。主机1进行相同的操作

不妨将网络套接字简单地理解为要申请一块内核存储空间用于接收或发送数据,返回的描述符理解为指向套接字的一个指针

接下来P1进程使用connect系统调用指明要把fd所指向的套接字连接到主机2IP地址的6666端口,这个系统调用就会使得这两个套接字之间建立起应用层连接

它们在传输层可以指定使用TCP或者UDP协议

接下来两台主机就可以通过套接字进行通信。比如P1想给P3发送数据包,P1首先在自身用户区准备好数据,然后使用write系统调用指明要往fd所指向的套接字当中写入数据,设备独立性软件接收到write系统调用后就会把P1准备好的数据复制到套接字所对应的这片内核缓冲区当中

接下来设备独立性软件调用网络控制器的驱动程序处理这片数据,驱动程序负责把准备好的数据输出到网络设备上

接下来网络控制器就可以把这些数据包发送到网络上,最后发到主机2的网络控制器,这个网络控制器接收到数据包后会向主机2发送一个中断信号。主机2的中断处理程序发现中断信号来自于网络控制器,调用网络控制器驱动程序,让驱动程序把网络程序收到的这些数据复制到6666端口所对应的内核缓冲区中

P3要接收网络数据包只需要使用read系统调用,指明要从fd所指的套接字对象当中读出数据包,设备独立性软件会从缓冲区里边把这些数据复制到P3的用户区当中

P2进程也可以使用socket系统调用申请一个新的套接字并绑定另一个端口。不同的套接字绑定不同的端口,因此网卡接收到许多数据包之后才可以根据数据包里面指明的端口信息把数据包放到对应的套接字对象这

scanf等待键盘输入,只要不输入进程就无法继续向下执行

进程准备的数据在用户区。发出write系统调用想要把数据写入磁盘,即便磁盘正在忙碌,设备独立性软件也会迅速响应系统调用请求先把数据复制到内核区,用户进程只要完成数据复制就可以继续往下执行,内核再慢慢把这些数据写入磁盘

统一标准的设备驱动程序接口

要调用不同公司编写的驱动程序还得修改函数调用的代码,也就是要频繁地修改操作系统内核,这显然不科学


这些功能要在哪个层次实现?

假脱机技术(SPOOLing技术)

假脱机技术 —— 输入井和输出井

显然“输入进程”和“输出进程”肯定需要和用户进程并发地执行才可以完成这种模拟脱机输入和脱机输出的过程,因此SPOOLing技术肯定需要有多道程序技术的支持

独占式设备——只允许各个进程串行使用的设备。一段时间内只能满足一个进程的请求

共享设备——允许多个进程“同时”使用的设备(宏观上同时使用,微观上可能是交替使用)。可以同时满足多个进程的使用请求

这个表就是用来说明用户的打印数据放在哪个缓冲区里,存放在什么地方等等这一系列信息

设备分配时应考虑的因素

设备分配时应考虑的因素:设备的固有属性,设备分配算法,设备分配中的安全性

设备的固有属性可分为三种:独占设备、共享设备、虚拟设备

独占设备——一个时段只能分配给一个进程(如打印机)

共享设备——可同时分配给多个进程使用(如磁盘),各进程往往是宏观上同时共享使用设备,而微观上交替使用

虚拟设备——采用 SPOOLing 技术将独占设备改造成虚拟的共享设备,可同时分配给多个进程使用(如采用 SPOOLing 技术实现的共享打印机)

设备的分配算法:先来先服务,优先级高者优先,短任务优先 ……

从进程运行的安全性上考虑,设备分配有两种方式:

安全分配方式:为进程分配一个设备后就将进程阻塞,本次I/O完成后才将进程唤醒。(eg:考虑
进程请求打印机打印输出的例子)

一个时段内每个进程只能使用一个设备

优点:破坏了“请求和保持”条件,不会死锁

缺点:对于一个进程来说,CPU和I/O设备只能串行工作

不安全分配方式:进程发出I/O请求后,系统为其分配I/O设备,进程可继续执行,之后还可以发出新的I/O请求。只有某个I/O请求得不到满足时才将进程阻塞

一个进程可以同时使用多个设备

优点:进程的计算任务和I/O任务可以并行处理,使进程迅速推进

缺点:有可能发生死锁(死锁避免、死锁的检测和解除)

静态分配:进程运行前为其分配全部所需资源,运行结束后归还资源

破坏了“请求和保持”条件,不会发生死锁

动态分配:进程运行过程中动态申请设备资源

设备分配管理中的数据结构

一个系统中可能会有多个通道

1.根据进程请求的物理设备名查找SDT

2.根据SDT找到DCT,若设备忙碌则将进程PCB挂到设备等待队列中,不忙碌则将设备分配给进程

除了分配这个设备之外,还需要把这个设备对应的控制器也分配给这个进程,所以系统会根据“指向控制器表的指针”这个字段找到这个设备对应的控制器控制表COCT

3.根据DCT找到COCT,若控制器忙碌则将进程PCB挂到控制器等待队列中,不忙碌则将控制器分配给进程

4.根据COCT找到CHCT,若通道忙碌则将进程PCB挂到通道等待队列中,不忙碌则将通道分配给进程

用户编程时必须使用“物理设备名”,底层细节对用户不透 明,不方便编程

若换了一个物理设备,则程序无法运行

若进程请求的物理设备正在忙碌,则即使系统中还有同类型 的设备,进程也必须阻塞等待

建立逻辑设备名与物理设备名的映射机制,用户编程时只需提供逻辑设备名

什么是缓冲区?有什么作用?

联想寄存器中的寄存器也称为快表

如果采用双缓冲结构,并且 T<C+M 的话,那很难找到一个和刚开始的这种初始状态一模一样的状态

结论:采用双缓冲策略,处理一个数据块的平均耗时为 Max (T, C+M)

使用单 / 双缓冲在通信时的区别

1.输入进程请求输入数据

从空缓冲队列中取出一块作为收容输入数据的工作缓冲区(hin)。冲满数据后将缓冲区挂到输入队列队尾

2.计算进程想要取得一块输入数据

从输入队列中取得一块冲满输入数据的缓冲区作为“提取输入数据的工作缓冲区(sin)”。缓冲区读空后挂到空缓冲区队列

3.计算进程想要将准备好的数据冲入缓冲区

从空缓冲队列中取出一块作为“收容输出数据的工作缓冲区(hout)”。数据冲满后将缓冲区挂到输出队列队尾

4.输出进程请求输出数据

从输出队列中取得一块冲满输出数据的缓冲区作为“提取输出数据的工作缓冲区(sout)”。缓冲区读空后挂到空缓冲区队列

有人看到苹果的AppStore里面不少付费应用,都觉得贵,另外有人则说,能用得起iPhone的人,还会在乎这么几个应用的收费么?嘿,你还真说对了,确实有些应用真TMD老贵了!你还别不信,数量虽说不多,不过还是有一些的哦。要不带你一起来瞧瞧吧,看看都是些神马人做出的神马东西:

描述:“不管你是业主经营商、承包商、或是大型公司,Spray App可以整合现场信息并直接传送回办公室。”

描述:“专为牙科从业人员设计,帮助他们向病人解释牙齿情况和治疗方法。”

描述:LSAT学习软件,包含6300道LSAT真题

描述:“阿拉伯湾和科威特水域的2D流体力学预测模型。”可以模拟1975年至2035年阿拉伯湾地区的潮流和水位,以小时为单位给出结果。

描述:iRa Pro和iRa Direct可以观测并管理企业的监控录像设备。面向对象包括公司、学校、政府等。

描述:iRa Pro和iRa Direct可以观测并管理企业的监控录像设备。面向对象包括公司、学校、政府等。

描述:帮助管理沃福视讯(WolfVision visualizers)的应用程序,例如视频会议管理,会议中的图像也可以截图、保存和发送邮件。

描述:夺旗橄榄球应用程序,帮助时刻关注各大联赛的统计数据。

描述:“Agro App”帮助农学家在农场建立、邮件发送现场检验报告,并在当场进行分发。避免了跑回办公室进行编排打印。

描述:该应用程序还在销售,但其制作者敬告顾客不要继续购买了。该软件的新版本SongBook ’11 UPNP已经发布,且售价仅为$25。

描述:“为医生设计的电子电荷捕获解决方案。”

描述:“为百万富翁设计的应用程序。iVIP的会员可以通过地图寻找iVIP合作伙伴,并享受贵宾级待遇,包括惊喜礼物、欢迎包裹、免费套房升级、会员特价、优先入住等特殊待遇。”

描述:“进行抵押即时比较的应用程序,轻松一点便可选择银行”

描述:包含1471道MBE题目和100多道论文考试题目,帮助法学学生备战纽约律师考试。

由iPhone开发者和哈佛律师开发。

描述:包含1371道MBE题目和100道论文考试题目,帮助法学学生备战纽约律师考试。

由iPhone开发者和哈佛律师开发。

描述:“iPhone测量工具,用来检测制造零件和组件。”

  【IT168技术】

  不用new关键词创建类的实例

  用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。

  在使用设计模式(Design Pattern)的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单。例如,下面是Factory模式的一个典型实现:

  改进后的代码使用clone()方法,如下所示:

  上面的思路对于数组处理同样很有用。

  版本较低的JDK不支持非阻塞I/O API。为避免I/O阻塞,一些应用采用了创建大量线程的办法(在较好的情况下,会使用一个缓冲池)。这种技术可以在许多必须支持并发I/O流的应用中见到,如Web服务器、报价和拍卖应用等。然而,创建Java线程需要相当可观的开销。

  JDK 1.4引入了非阻塞的I/O库(java.nio)。如果应用要求使用版本较早的JDK,在这里有一个支持非阻塞I/O的软件包。

  请参见Sun中国网站的《调整Java的I/O性能》。

  异常对性能不利。抛出异常首先要创建一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。

  异常只能用于错误处理,不应该用来控制程序流程。

  默认情况下,调用类的构造函数时, Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0,float和 double变量设置成0.0,逻辑值设置成false。当一个类从另一个类派生时,这一点尤其应该注意,因为用new关键词创建一个对象时,构造函数链中的所有构造函数都会被自动调用。

  尽量指定类的final修饰符

  另外,如果指定一个类为final,则该类所有的方法都是final。Java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%。

  调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化。请参见《尽可能使用堆栈变量》。

   用移位操作替代乘法操作可以极大地提高性能。下面是修改后的代码:

   修改后的代码不再做乘以8的操作,而是改用等价的左移3位操作,每左移1位相当于乘以2。相应地,右移1位操作相当于除以2。值得一提的是,虽然移位操作速度快,但可能使代码比较难于理解,所以最好加上一些注释。

  前面介绍的改善性能技巧适合于大多数Java应用,接下来要讨论的问题适合于使用JSP、EJB或JDBC的应用。

  一些应用服务器加入了面向JSP的缓冲标记功能。例如,BEA的WebLogic Server从6.0版本开始支持这个功能,Open Symphony工程也同样支持这个功能。JSP缓冲标记既能够缓冲页面片断,也能够缓冲整个页面。当JSP页面执行时,如果目标片断已经在缓冲之中,则生成该片断的代码就不用再执行。页面级缓冲捕获对指定URL的请求,并缓冲整个结果页面。对于购物篮、目录以及门户网站的主页来说,这个功能极其有用。对于这类应用,页面级缓冲能够保存页面执行的结果,供后继请求使用。

  对于代码逻辑复杂的页面,利用缓冲标记提高性能的效果比较明显;反之,效果可能略逊一筹。

  请参见《用缓冲技术提高JSP应用的性能和稳定性》。

  始终通过会话Bean访问实体Bean

  直接访问实体Bean不利于性能。当客户程序远程访问实体Bean时,每一个get方法都是一个远程调用。访问实体Bean的会话Bean是本地的,能够把所有数据组织成一个结构,然后返回它的值。

  用会话Bean封装对实体Bean的访问能够改进事务管理,因为会话Bean只有在到达事务边界时才会提交。每一个对get方法的直接调用产生一个事务,容器将在每一个实体Bean的事务之后执行一个“装入-读取”操作。

  一些时候,使用实体Bean会导致程序性能不佳。如果实体Bean的唯一用途就是提取和更新数据,改成在会话Bean之内利用JDBC访问数据库可以得到更好的性能。

  选择合适的引用机制

  在典型的JSP应用系统中,页头、页脚部分往往被抽取出来,然后根据需要引入页头、页脚。当前,在JSP页面中引入外部资源的方法主要有两种:include指令,以及include动作。

  include指令:例如。该指令在编译时引入指定的资源。在编译之前,带有include指令的页面和指定的资源被合并成一个文件。被引用的外部资源在编译时就确定,比运行时才确定资源更高效。

  include动作:例如。该动作引入指定页面执行后生成的结果。由于它在运行时完成,因此对输出结果的控制更加灵活。但时,只有当被引用的内容频繁地改变时,或者在对主页面的请求没有出现之前,被引用的页面无法确定时,使用include动作才合算。

   在部署描述器中设置只读属性

  实体Bean的部署描述器允许把所有get方法设置成“只读”。当某个事务单元的工作只包含执行读取操作的方法时,设置只读属性有利于提高性能,因为容器不必再执行存储操作。

  EJB Home接口通过JNDI名称查找获得。这个操作需要相当可观的开销。JNDI查找最好放入Servlet的init()方法里面。如果应用中多处频繁地出现EJB访问,最好创建一个EJBHomeCache类。EJBHomeCache类一般应该作为singleton实现。

  为EJB实现本地接口

  本地接口是EJB 2.0规范新增的内容,它使得Bean能够避免远程调用的开销。请考虑下面的代码。

  第一个语句表示我们要寻找Bean的Home接口。这个查找通过JNDI进行,它是一个RMI调用。然后,我们定位远程对象,返回代理引用,这也是一个 RMI调用。第二个语句示范了如何创建一个实例,涉及了创建IIOP请求并在网络上传输请求的stub程序,它也是一个RMI调用。

  要实现本地接口,我们必须作如下修改:

  所有数据和返回值都通过引用的方式传递,而不是传递值。

  本地接口必须在EJB部署的机器上使用。简而言之,客户程序和提供服务的组件必须在同一个JVM上运行。

  如果Bean实现了本地接口,则其引用不可串行化。

  请参见《用本地引用提高EJB访问效率》。

  在EJB之内生成主键有许多途径,下面分析了几种常见的办法以及它们的特点。

  由实体Bean自己计算主键值(比如做增量操作)。它的缺点是要求事务可串行化,而且速度也较慢。

  利用NTP之类的时钟服务。这要求有面向特定平台的本地代码,从而把Bean固定到了特定的OS之上。另外,它还导致了这样一种可能,即在多CPU的服务器上,同一个毫秒之内生成了两个主键。

  借鉴Microsoft的思路,在Bean中创建一个GUID。然而,如果不求助于JNI,Java不能确定网卡的MAC地址;如果使用JNI,则程序就要依赖于特定的OS。

  还有其他几种办法,但这些办法同样都有各自的局限。似乎只有一个答案比较理想:结合运用RMI和JNDI。先通过RMI注册把RMI远程对象绑定到JNDI树。客户程序通过JNDI进行查找。下面是一个例子:

  及时清除不再需要的会话

  为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为30分钟。当应用服务器需要保存更多会话时,如果内存容量不足,操作系统会把部分内存数据转移到磁盘,应用服务器也可能根据“最近最频繁使用”(Most Recently Used)算法把部分不活跃的会话转储到磁盘,甚至可能抛出“内存不足”异常。在大规模系统中,串行化会话的代价是很昂贵的。当会话不再需要时,应当及时调用HttpSession.invalidate()方法清除会话。HttpSession.invalidate()方法通常可以在应用的退出页面调用。

   在JSP页面中关闭无用的会话

  对于那些无需跟踪会话状态的页面,关闭自动创建的会话可以节省一些资源。使用如下page指令:

  许多开发者随意地把大量信息保存到用户会话之中。一些时候,保存在会话中的对象没有及时地被垃圾回收机制回收。从性能上看,典型的症状是用户感到系统周期性地变慢,却又不能把原因归于任何一个具体的组件。如果监视JVM的堆空间,它的表现是内存占用不正常地大起大落。

  解决这类内存问题主要有二种办法。第一种办法是,在所有作用范围为会话的Bean中实现HttpSessionBindingListener接口。这样,只要实现valueUnbound()方法,就可以显式地释放Bean使用的资源。

  另外一种办法就是尽快地把会话作废。大多数应用服务器都有设置会话作废间隔时间的选项。另外,也可以用编程的方式调用会话的 setMaxInactiveInterval()方法,该方法用来设定在作废会话之前,Servlet容器允许的客户请求的最大间隔时间,以秒计。

  Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。市场上的大部分Web服务器,包括iPlanet、IIS和Apache,都支持HTTP Keep-Alive。对于提供静态内容的网站来说,这个功能通常很有用。但是,对于负担较重的网站来说,这里存在另外一个问题:虽然为客户保留打开的连接有一定的好处,但它同样影响了性能,因为在处理暂停期间,本来可以释放的资源仍旧被占用。当Web服务器和应用服务器在同一台机器上运行时,Keep- Alive功能对资源利用的影响尤其突出。

  想必你已经了解一些使用JDBC时提高性能的措施,比如利用连接池、正确地选择存储过程和直接执行的SQL、从结果集删除多余的列、预先编译SQL语句,等等。

  除了这些显而易见的选择之外,另一个提高性能的好选择可能就是把所有的字符数据都保存为Unicode(代码页13488)。Java以Unicode形式处理所有数据,因此,数据库驱动程序不必再执行转换过程。但应该记住:如果采用这种方式,数据库会变得更大,因为每个Unicode字符需要2个字节存储空间。另外,如果有其他非Unicode的程序访问数据库,性能问题仍旧会出现,因为这时数据库驱动程序仍旧必须执行转换过程。

  如果应用程序需要访问一个规模很大的数据集,则应当考虑使用块提取方式。默认情况下,JDBC每次提取32行数据。举例来说,假设我们要遍历一个5000 行的记录集,JDBC必须调用数据库157次才能提取到全部数据。如果把块大小改成512,则调用数据库的次数将减少到10次。

  在一些情形下这种技术无效。例如,如果使用可滚动的记录集,或者在查询中指定了FOR UPDATE,则块操作方式不再有效。

  许多应用需要以用户为单位在会话对象中保存相当数量的数据,典型的应用如购物篮和目录等。由于这类数据可以按照行/列的形式组织,因此,许多应用创建了庞大的Vector或HashMap。在会话中保存这类数据极大地限制了应用的可伸缩性,因为服务器拥有的内存至少必须达到每个会话占用的内存数量乘以并发用户最大数量,它不仅使服务器价格昂贵,而且垃圾收集的时间间隔也可能延长到难以忍受的程度。

  一些人把购物篮/目录功能转移到数据库层,在一定程度上提高了可伸缩性。然而,把这部分功能放到数据库层也存在问题,且问题的根源与大多数关系数据库系统的体系结构有关。对于关系数据库来说,运行时的重要原则之一是确保所有的写入操作稳定、可靠,因而,所有的性能问题都与物理上把数据写入磁盘的能力有关。关系数据库力图减少I/O操作,特别是对于读操作,但实现该目标的主要途径只是执行一套实现缓冲机制的复杂算法,而这正是数据库层第一号性能瓶颈通常总是

  一种替代传统关系数据库的方案是,使用在内存中运行的数据库(In-memory Database),例如TimesTen。内存数据库的出发点是允许数据临时地写入,但这些数据不必永久地保存到磁盘上,所有的操作都在内存中进行。这样,内存数据库不需要复杂的算法来减少I/O操作,而且可以采用比较简单的加锁机制,因而速度很快。

  这一部分介绍的内容适合于图形用户界面的应用(Applet和普通应用),要用到AWT或Swing。

   用JAR压缩类文件

  Java档案文件(JAR文件)是根据JavaBean标准压缩的文件,是发布JavaBean组件的主要方式和推荐方式。JAR档案有助于减少文件体积,缩短下载时间。例如,它有助于Applet提高启动速度。一个JAR文件可以包含一个或者多个相关的Bean以及支持文件,比如图形、声音、HTML 和其他资源。

  请参见《使用档案文件提高 applet 的加载速度》。

   提示Applet装入进程

  你是否看到过使用Applet的网站,注意到在应该运行Applet的地方出现了一个占位符?当Applet的下载时间较长时,会发生什么事情?最大的可能就是用户掉头离去。在这种情况下,显示一个Applet正在下载的信息无疑有助于鼓励用户继续等待。

  下面我们来看看一种具体的实现方法。首先创建一个很小的Applet,该Applet负责在后台下载正式的Applet:

  编译后的代码小于2K,下载速度很快。代码中有几个地方值得注意。首先,PreLoader实现了AppletStub接口。一般地,Applet从调用者判断自己的codebase。在本例中,我们必须调用setStub()告诉Applet到哪里提取这个信息。另一个值得注意的地方是,

  在画出图形之前预先装入它

  ImageObserver接口可用来接收图形装入的提示信息。ImageObserver接口只有一个方法imageUpdate(),能够用一次repaint()操作在屏幕上画出图形。下面提供了一个例子。

  当图形信息可用时,imageUpdate()方法被调用。如果需要进一步更新,该方法返回true;如果所需信息已经得到,该方法返回false。

  update()方法的默认动作是清除屏幕,然后调用paint()方法。如果使用默认的update()方法,频繁使用图形的应用可能出现显示闪烁现象。要避免在paint()调用之前的屏幕清除操作,只需按照如下方式覆盖update()方法:

  更理想的方案是:覆盖update(),只重画屏幕上发生变化的区域,如下所示:

  对于图形用户界面的应用来说,性能低下的主要原因往往可以归结为重画屏幕的效率低下。当用户改变窗口大小或者滚动一个窗口时,这一点通常可以很明显地观察到。改变窗口大小或者滚动屏幕之类的操作导致重画屏幕事件大量地、快速地生成,甚至超过了相关代码的执行速度。对付这个问题最好的办法是忽略所有“迟到” 的事件。

  建议在这里引入一个数毫秒的时差,即如果我们立即接收到了另一个重画事件,可以停止处理当前事件转而处理最后一个收到的重画事件;否则,我们继续进行当前的重画过程。

  如果事件要启动一项耗时的工作,分离出一个工作线程是一种较好的处理方式;否则,一些部件可能被“冻结”,因为每次只能处理一个事件。下面提供了一个事件处理的简单例子,但经过扩展后它可以用来控制工作线程。

  在屏幕之外的缓冲区绘图,完成后立即把整个图形显示出来。由于有两个缓冲区,所以程序可以来回切换。这样,我们可以用一个低优先级的线程负责画图,使得程序能够利用空闲的CPU时间执行其他任务。下面的伪代码片断示范了这种技术。

  Java JDK 1.2使用了一个软显示设备,使得文本在不同的平台上看起来相似。为实现这个功能,Java必须直接处理构成文字的像素。由于这种技术要在内存中大量地进行位复制操作,早期的JDK在使用这种技术时性能不佳。为解决这个问题而提出的Java标准实现了一种新的图形类型,即BufferedImage。

  BufferedImage子类描述的图形带有一个可访问的图形数据缓冲区。一个BufferedImage包含一个ColorModel和一组光栅图形数据。这个类一般使用RGB(红、绿、蓝)颜色模型,但也可以处理灰度级图形。它的构造函数很简单,如下所示:

  ImageType允许我们指定要缓冲的是什么类型的图形,比如5-位RGB、8-位RGB、灰度级等。

  许多硬件平台和它们的操作系统都提供基本的硬件加速支持。例如,硬件加速一般提供矩形填充功能,和利用CPU完成同一任务相比,硬件加速的效率更高。由于硬件加速分离了一部分工作,允许多个工作流并发进行,从而缓解了对CPU和系统总线的压力,使得应用能够运行得更快。利用VolatileImage可以创建硬件加速的图形以及管理图形的内容。由于它直接利用低层平台的能力,性能的改善程度主要取决于系统使用的图形适配器。VolatileImage的内容随时可能丢失,也即它是“不稳定的(volatile)”。因此,在使用图形之前,最好检查一下它的内容是否丢失。VolatileImage有两个能够检查内容是否丢失的方法:

  进行滚动操作时,所有可见的内容一般都要重画,从而导致大量不必要的重画工作。许多操作系统的图形子系统,包括WIN32 GDI、MacOS和X/Windows,都支持Window Blitting技术。Window Blitting技术直接在屏幕缓冲区中把图形移到新的位置,只重画新出现的区域。要在Swing应用中使用Window Blitting技术,设置方法如下:

  在大 多数应用中,使用这种技术能够提高滚动速度。只有在一种情形下,Window Blitting会导致性能降低,即应用在后台进行滚动操作。如果是用户在滚动一个应用,那么它总是在前台,无需担心任何负面影响。

我要回帖

更多关于 计算机的驱动程序 的文章

 

随机推荐