对 MBCS 文本的 QuickSearch 搜索的误搜 节选

October 24, 2006

作者: JIURL
邮箱: thejiurl@gmail.com
主页: http://jiurl.blogsome.com/
http://jiurl.yeah.net/

[前言]

这篇文章是以前写给自己备忘的一个研究文档的节选。

[MBCS]

MBCS: Multi-Byte Character Set
DBCS: Double-Byte Character Set

MBCS 文本中,既可能有1个字节的 Ascii 字符,又可能有2个字节的 DBCS 字符。
硬盘上的大多数文本文件就是以 MBCS 方式来保存的。

[区分大小写的搜索]

QuickSearch 寻找匹配串,是以字节为单位来进行匹配的,
对字节的256个可能值,建立 MoveLenTable[] 表,来计算移动距离,
详细见 QuickSearch.txt 中的介绍。

对于 MBCS 文本来说,这样的匹配结果,可能有误搜,但是肯定没有漏搜。
我们应该去除掉误搜的情况。

会产生 DBCS TrailByte 引起的误搜。

DBCS TrailByte 引起的误搜,一种情况,比如:

szPattern 为 “gabc”
szText 为 “鎔abc xyz”

‘鎔’是一个 DBCS 字符,编码为 0xe6 0x67。
‘g’ 是一个 ASCII 字符,编码为 0x67。

用 szPattern 对 szText 进行区分大小写的搜索,会得到一个匹配,
匹配串从 DBCS 字符 ‘鎔’ 的 TrailByte 开始,
而实际上,并不应该有匹配,所以就出现了误搜。

DBCS TrailByte 引起的误搜,另一种情况,比如:

略。

[不区分大小写的搜索]

对于不区分大小写的搜索,

对于文本中的每个字节,不管它是一个 ascii 字符,还是一个 dbcs 字符的 LeadByte,TrailByte,都当作 ascii 字节来处理,只要它落在了 ascii 英文字母的范围内就进行不区分大小写处理。

由此,可能引起误搜,所以对匹配结果还需要进行误搜处理,去掉误搜的情况即可。

不区分大小写处理,包括:
建立 MoveLenTable[] 的时候,同一个英文字母,大写对应项,和,小写对应项,的值相同。

比较 chFirstPatternChar 的时候,应该既和 chFirstPatternChar 的大写值比较,又和 chFirstPatternChar 小写值比较。

进行串匹配的时候,也应该进行 不区分大小写 的串匹配。

详细实现参考 QuickSearchNotMatchCase() 函数的源码。

对于 MBCS 文本来说,这样的匹配结果,可能有误搜,但是肯定没有漏搜。
我们应该去除掉误搜的情况。

同样存在和 区分大小写的搜索 一样的,DBCS TrailByte 引起的误搜。
除此之外,还存在 不区分大小写 引起的误搜。

不区分大小写 引起的误搜,比如:

szPattern 为 “鎔abc”
szText 为 “鍳abc xyz”

‘鎔’是一个 DBCS 字符,编码为 0xe6 0x67。
‘鍳’是一个 DBCS 字符,编码为 0xe6 0x47。

DBCS 的 TrailByte 本身是一个字符的一部分,不存在大小写问题,但是,
匹配是以字节来进行的,并不知道这个字节是否是一个 DBCS 的 TrailByte,只要看到了这个字节的值落在了英文字母的范围内,就进行不区分大小写的处理,导致了认为 0x67 和 0x47 是匹配的。

用 szPattern 对 szText 进行区分大小写的搜索,会得到一个匹配,
匹配串为”鍳abc”,
而实际上,并不应该有匹配,所以就出现了误搜。

可以看到,只有 szPattern 中含有 DBCS 字符,才可能出现这种误搜。

[误搜的去除]

略。

[误搜的出现几率]

误搜的出现几率是非常小的,这是因为:

不管是两种误搜中的哪种,都是由于 DBCS 字符的 TrailByte 小于 0x80,落在了 ascii 字符的范围内,而本身 DBCS 的 TrailByte 小于 0x80 的字符,都是些不太常用的汉字。

出现 DBCS 字符 TrailByte 小于 0x80 的几率较小,
又碰巧出现 szPattern 和 szText 的误搜的几率就更小了。

gdi显卡驱动节选

作者: JIURL
邮箱: thejiurl@gmail.com
主页: http://jiurl.blogsome.com/
http://jiurl.yeah.net/

[前言]

这篇文章是以前写给自己备忘的一个研究文档的节选。

[gdi 显卡驱动]

显卡驱动将讨论,显卡驱动中支持 gdi 实现的部分。

如果是一个纯 frame buffer 的话,显卡不作任何加速,所有 gdi 的实现都是 win32k 中的代码软件实现,gdi 把 frame buffer 当作一个位图,直接访问,进行相应的绘制。当然,这种情况除非特意设计,否则的话,是不会出现的。

不是纯 frame buffer 的情况,gdi 不直接访问 frame buffer,
显卡硬件实现了某些硬件加速,可能实现了某些 gdi 需要的硬件加速。gdi 中,有硬件加速的,就交给显卡驱动来完成,没有硬件加速的,gdi 软件实现,然后把结果交给显卡驱动来完成显示。

显卡驱动至少需要实现,DrvCopyBits, DrvTextOut 和 DrvStrokePath,其他 gdi 函数可以软件实现,最终调用这几个驱动实现的函数,来把结果写入 frame buffer。

显卡驱动可以实现的 gdi 的功能:
DrvBitBlt
DrvPlgBlt
DrvStretchBlt
DrvStretchBltROP
DrvTextOut
DrvStrokePath
DrvFillPath
DrvStrokeAndFillPath
DrvLineTo
DrvCopyBits
DrvAlphaBlend
DrvGradientFill
DrvTransparentBlt

更多细节可以参考关于 SURFACE, HDC 等的讨论。

[显卡驱动]

显卡驱动(支持gdi部分)相关内容主要在下面的4个文件中:

win32k.sys
VideoPort.sys

disp 驱动 (.dll) (如 vga.dll)
mini port 驱动 (.sys) (如 vga.sys)

win32k.sys 和 VideoPort.sys 是系统文件。
disp 驱动 (.dll) 和 mini port 驱动 (.sys) 是显卡驱动。

[win32k.sys]
gdi 就是在 win32k.sys 中实现的。EngXxx 是在 win32k.sys 中实现的。

[VideoPort.sys]

mini port 驱动 将会调用 VideoPort.sys 导出的函数。
VideoPort.sys 导出的函数,例如:

VideoPortWritePortUlong
VideoPortZeroMemory
VideoPortInitialize

只是做了个简单封装,这样逻辑性更好一些,也使得 mini port 驱动的代码和 平台(体系结构)无关。
比如 VideoPortWritePortUlong,对x86,基本上也就是一条 out 指令。

[mini port 驱动 (.sys)]

查询显卡支持的所有可用的模式,查询和设置当前的显示模式(比如,1280*1024 16位色 刷新率60hz)。
大致也就是完成,对显卡初始化,对显卡的状态进行查询和设置,将寄存器映射到虚拟地址空间,等工作。

也就是一些对应 IOCTL_VIDEO_XXX 的实现的工作。

在 DriverEntry 中,调用 VideoPortInitiaze,设置 DRIVER_OBJECT 的 MajorFunction[],IoCreateDevice 创建 DEVICE_OBJECT。

[disp 驱动 (.dll)]

显卡驱动中最重要的部分。显卡驱动的主要工作在 “disp 驱动 (.dll)” 中完成。

它知道显卡硬件的细节,知道如何向显卡发命令,来让显卡硬件完成某个工作。

它将直接访问显卡的寄存器,来向显卡发命令,或者读取显卡的情况。可能显卡的一块寄存器最终映射到虚拟地址空间的话,那么驱动中就直接访问这部分虚拟地址空间。也可能还需要访问 io 端口。

DrvXxx 函数就是由 “disp 驱动 (.dll)” 提供的。

“disp 驱动 (.dll)” 中会调用 EngXxx。比如 EngCreateDeviceSurface,EngDeviceIoControl。也可能调用 EngXxx 来帮助完成某项绘制功能。

注意:disp 驱动 (.dll) 的入口点为 DrvEnableDriver。它将被载入系统地址空间。

汉朝定都于长安的原因

October 22, 2006

公元前202年(两千二百多年前),汉朝开国皇帝刘邦定都于长安。

大概是这么回事,当时有两派意见。一派主要是俩人:娄敬,张良,主张定都于长安。另一派是好多大臣,主张定都于洛阳。

这事发生在洛阳。
最开头是,娄敬求见刘邦,跟刘邦说了一大套,怂恿刘邦定都长安。
我估计,一来,刘邦跟娄敬不熟,有点半信半疑。二来,刘邦是大风大浪过来的人,可能还是比较爱听大伙都说两句。
于是,就跟群臣说这事,大伙的意思是定都洛阳。
据司马迁推测,因为这帮人大多不是长安附近的,定都洛阳离这伙人家近点,所以他们都起哄让定都洛阳。我估计,这可能也是个原因。他们嘴上说的是:周朝定都洛阳,痛快了好几百年,秦朝定都长安,只过了二世的瘾。我估计,很多人可能也是真诚的。
刘邦可能觉得两边说的都有点道理,就犹豫不决。
张良就跟刘邦说了一通,基本上也是老词,跟娄敬那套差不多,让他定都长安。刘邦立刻就决定定都长安了。我估计,一来,刘邦是大风大浪过来的人,一听就知道娄敬,张良说的也在理。二来,刘邦跟张良那是太熟了,知道这就是一个人精啊,听他的,保准吃不了亏。所以,这事就这么定了。

娄敬,张良,他们说长安有各种的好处。所以,刘邦最终决定,定都于长安。
他们是怎么说的?长安到底有什么好处呢?。。。你猜。。。

另外:
史籍资料,未必可信,所以应该首先考证它的可信度,但是,咱们没那个功夫,就算了。凭感觉,司马迁的这部《史记》,可信度应该还是比较高的。

所有内容来源于:
《史记·留候世家》
《史记·刘敬叔孙通列传》

http://jiurl.blogsome.com/

DPC 延迟过程调用

September 25, 2006

作者: JIURL
邮箱: thejiurl@gmail.com
主页: http://jiurl.blogsome.com/
http://jiurl.yeah.net/

$ 前言

这篇文章应该写于 2005年4月13日,是写给自己备忘的研究文档,本来是根本没有打算放出来的。

$ DPC

deferred procedure call (DPC) 延迟过程调用

dpc 主要是为了减少处于高 IRQL 的硬件中断处理的时间。
由于低 IRQL 的硬件中断不能中断 高 IRQL 的硬件中断处理。
而可能有些硬件的中断处理代码量比较大,花的时间比较多,会造成其他硬件中断的响应会等的时间比较长。
很有可能就是因为 时钟中断处理,非常非常频繁,代码量也比较多,可能会比较大的影响到其他硬件中断的响应,至于硬件中断响应比较慢可能会对不同的硬件造成什么结果,有待研究。

$ DPC 队列

整个系统只有一个 DPC 队列,至少对单cpu的系统是如此,对于多cpu系统有可能是一个cpu一个,有待验证。

这个队列是个 LIST_ENTRY 链表。

链表头在 pcr 的 +800 struct _LIST_ENTRY DpcListHead。
链的深度在 pcr 的 +808 uint32 DpcQueueDepth。

所有的 dpc 都链在这个链上。

链上的每项是个 KDPC 结构。

struct _KDPC (sizeof=32)
+00 int16 Type
+02 byte Number
+03 byte Importance
+04 struct _LIST_ENTRY DpcListEntry
+04 struct _LIST_ENTRY *Flink
+08 struct _LIST_ENTRY *Blink
+0c function *DeferredRoutine
+10 void *DeferredContext
+14 void *SystemArgument1
+18 void *SystemArgument2
+1c uint32 *Lock

$ DPC 的源头

所有的 DPC 都是在 IRQL >= DISPATCH_LEVEL 的代码中产生的。
也就是只有 IRQL 大于等于 DISPATCH_LEVEL 的代码使用 DPC。

DPC 都是在硬件中断服务例程(ISR)中,由硬件中断服务例程根据自己的需要,链入到 DPC 队列中的。
中断服务例程中调用 KeInsertQueueDpc 将 dpc 链入 dpc 队列,或者,中断服务例程直接操作 dpc 链表,将 dpc 链入。

KeInsertQueueDpc 除了将 dpc 链入链之外,还会调用 KiRequestSoftwareInterrupt(DISPATCH_LEVEL),如果调用是的 IRQL 高于 DISPATCH_LEVEL,会使得 dpc pending,等到 irql 降下来的时候得到执行。

如果是自己直接操作链来将dpc链入的话,也需要自己调用类似 KiRequestSoftwareInterrupt(DISPATCH_LEVEL) 的函数。

$ DPC 的执行

在硬件中断处理的最后,会调用 HalEndSystemInterrupt。
HalEndSystemInterrupt 中,会将 IRQL 降低,然后检查是否有 DPC pending,有的话,会调用 KiDispatchInterrupt 处理 dpc。

KiDispatchInterrupt 中调用 KiRetireDpcList 处理 dpc。

也就是说,当在硬件中断处理中加入一个 dpc,那么当这个硬件中断处理结束的时候,就会调用这个被加入的 DPC。

a:所有处理都放在isr中
b:处理分两部分,必须要做的放在isr中,剩下的放在dpc中,isr中使用dpc

a的处理代码一直执行下来,只可能被irql比它高的硬件中断中断。
b的isr一直执行下来,紧接着执行dpc。isr部分只能被irql比它高的硬件中断中断。dpc部分可被任何硬件中断中断。

$ DPC 不可能受到线程切换的影响

会不会 DPC 中的代码执行到一半,发生线程切换,或者线程抢占,CPU 转去执行什么线程?
答案是绝对不会。

这是由于负责线程切换,抢占的代码就是运行在 IRQL DISPATCH_LEVEL,而 IRQL 小于等于当前 IRQL 的中断不能发生。
所以运行 dpc 时,irql 为 DISPATCH_LEVEL,根本就不会发生线程切换,线程抢占之类的事情。

当然中断中,就更不会了。

$ KiDispatchInterrupt

DISPATCH_LEVEL 上执行的代码有两大种,
一种是各种 isr 加的 dpc,处理一个 dpc,也就是调用这个 dpc 中的 DeferredRoutine。
一种是关于线程调度的。

KiDispatchInterrupt 是和 dpc 相关的重要函数。
KiDispatchInterrupt 主要做以下工作:

{
从 pcr 中得到 dpc 链表头。
如果链表不空的话,调用 KiRetireDpcList 处理 dpc。也就是一个一个的调用dpc里的 DeferredRoutine。

检查 pcr 中的 QuantumEnd,看是否为0。不为0进行 QuantumEnd 的处理。
检查 pcr 中的 NextThread,看是否为0。不为0进行 更换新的线程的 处理。
}

由于 KiDispatchInterrupt 会在各种硬件中断处理的结束有可能得到调用,当硬件中断处理过程中使用dpc的情况下就会在中断处理结束的时候被调用。

如果有任何代码使用 DISPATCH_LEVEL RequestSoftwareInterrupt 的话,KiDispatchInterrupt 也可能被立即执行。

QuantumEnd 不为0的情况,目前只有一种情况引起,就是时钟中断处理中,将当前线程的 Quantum 减少后,发现当前线程 Quantum 用完,就会设置 QuantumEnd,并在 时钟中断 处理结束的时候,引起 KiDispatchInterrupt 执行。

NextThread 不为0的情况,是哪些情况下引起的,还待研究。