关于工程师的笑话

调侃工程师的笑话,多为工程师自娱自乐所为,其中逻辑及笑点也是非工程师所能深解,但好在工程师也不是什么高深的职业。

话说,某工刚下班,家里领导打来电话说:路上买三个馒头,看到西瓜买一个。结果等此工回到家时,领导看到他只买了一个馒头,并不是三个!

讲到此处,工程师们应该已笑开了怀。这则笑话以前就曾看过,后来,与朋友自驾浙江游玩时,一朋友又讲起此笑话,严格意义上当时只我一个算是个“工程师”,但这年头,是个知识分子,哪有没写过程序的?!所以全车人笑地还是异常热烈,特别是再经过“非工程师”人的诠释,以至这个如此简单的笑话几乎让我们笑了一路子。

还有个笑话:作家、物理学教授、和工程师开会回来,驾车驶进盘山小路,结果刹车失灵,车辆直冲而下,所幸为小树所阻,三人才得以死里逃生。从惊吓中恢复过来后,作家先发话说来灵感了,要将此次经历写成一部超惊险超刺激超恐怖的超现实体验主义小说;物理学教授也跟随,一定要搞出个数学模型来描述此次刹车片摩擦受热失灵的量子状态以及车辆带动力俯冲下山的动量矢能转换,话虽未说完但早已是一脸的急切;此时的工程师却是眉头紧缩,满脸疑惑地摇着头说:咱们为什么不再重现一次好找出问题的根本原因?

Continue reading » · Rating: · Written on: 06-28-12 · No Comments »

离奇事件薄之Irp死亡未遂

白天在外面耗了一整天,和各色朋友们讨论了一整天的新公司初创、经营、账务、税收等相关的问题,竟也蛮有兴致,也费了相当的口水,等回到家时间已很晚了,可精神却还是异常兴奋,只得爬起来找点事打发一下过剩的精力,第一直觉上便想到了前阵子曾遇到的一个稍许离奇的问题。

内容上可接承以前的一篇日志《离奇死亡事件薄之CoCreate篇》,于是继续取题《离奇事件薄》。细节如下:

驱动程序中要转向IRP_MJ_DIRECTORY_CONTROL/IRP_MN_QUERY_DIRECTORY,即Directory Enumeration操作,以方便处理此目录下所有的文件项。

由于会涉及文件I/O操作,不得不将操作移至系统线程中来做(至于为什么,请读者自己思考),所以在用户请求线程中不得不将原Irp请求的完成延后(pending),函数原型可简化成如下二个函数:

/* 用户线程环境:直接处理 IRP_MJ_DIR_CTRL / IRP_MN_QUERY_DIRECTORY */

NTSTATUS MyQueryDirectory(DevObj, Irp)

{

        ……
        MyQueueToSystemThread(DevObj, Irp);
        return STATUS_PENDING:

}

/* 系统线程,用以处理面向新路径(Dcb)的IRP_MN_QUEYR_DIRECTORY操作 */

NTSTATUS MySystemThreadHandler(DevObj, Irp)

{
        PIRP newIrp = IoAllocateIrp(newDevObj);
        ……
        IoCallDriver(newDevObj, newIrp);
        KeWaitForSingleObject(…):

        /* 处理返回的目录项 */
        ……
        /* 完成原用户的IRP请求 */
       IoCompleteRequest(irp, IO_NO_INCREMENT);

}

代码在逻辑上并没有问题,MySystemThreadHandler()正确获取目录项内容并实施对目标项的逐一检查,之后再完成用户所发的Irp请求,Irp的状态被系统(I/O Manager)修改为完成状态,此过程中并没有异常。但最早发出IRP_MJ_DIR_CTRL / IRP_MN_QUERY_DIRECTORY请求的用户线程却被永远地挂起来了,原Irp内容依然有效,虽然其状态已被标记为“完成”。

调试发现,由于系统线程优先级稍高,MySystemThreadHandler()完成Irp的操作竟然先于MyQueryDirectory()返回至I/O Manager。

执行顺序如下:
时间1:MyQueryDirectory() 添加一个新任务至系统线程队列
时间2:MyQueryDirectory() 被系统挂起了
时间3:系统线程执行MySystemThreadHandler()来处理时刻时间1所添加的任务
时间4:MySystemThreadHandler()执行完毕
时间5:原用户线程MyQueryDirectory()被唤醒,并返回STATUS_PENDING给I/O Manager

看到此序列,对于熟悉Irp操作规则的开发者来说,问题已经明了:显然是对此Irp的第二阶段用户线程环境相关的处理没有进行(Stage 2),也就是说,添加Kernel Apc的部分被跳过了。

一般Irp的完成要经过两个过程(可参见本文末所附文档链接):
Stage 1: IofCompleteRequest(): 完成与请求线程环境无关的操作
Stage 2: IopCompleteRequest(): 与Kernel Apc中执行,用以完成与请求线程相关的操作

之所以Stage 2的IopCompleteRequest会被跳过,有两方面的原因:

  1. Windows内核在处理IRP_MJ_DIR_CTRL/IRP_MN_QUERY_DIRECTORY请求时会默认加上标志位IRP_DEFER_IO_COMPLETION。据我所知,仅对DeviceIoControl操作不加此标志。当此标志位被设置的同时,如果Irp的Pending位没有设置,IoCompleteRequest()(此函数会调用IofCompleteRequest())则会直接返回,从面不在继续处理第二阶段(stage 2)。
  2. 原用户线程IoCallDriver()返回之后,由于返回值为STATUS_PENDING,I/O Manager将不在调用IopCompleteRequest()

所以二者同时跳过Stage 2,导致Irp的Stage 2处理不会再有机会执行,从而原用户线程也得不到被唤醒的机会了。

解决办法却是异常简单,在MyQueryDirectory()调用IoCallDriver()之前,或MySystemThreadHandler()调用IoCompleteRequest()之前将Irp设为PendingReturened非状态即可,此操作由系统支持函数IoMarkIrpPending()来做的。

最后,不妨再复习一下OSR介绍Irp操作的经典之作吧:《Secrets of the Universe Revealed! - How NT Handles I/O Completion

Continue reading » · Rating: · Written on: 06-28-12 · No Comments »

Windows XP的非正常启动

跑在虚拟机里的XP系统连不上Windbg了,启动进系统后查看配置文件C:\boot.ini,看起来一切正常,内容如下:

timeout=10
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows XP" /noguiboot /debug /debugport=com1 /baudrate=115200
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows XP" /noguiboot

随后又通过“我的电脑”查看启动选项(右键点击桌面上的“My Computer”,-> Properties –> Advanced –> Settings ),发现“Time to display list of operating systems “/“显示操作系统列表的时间”一项竟是灰色的,但在boot.ini中设置却是:10秒。于是在“我的电脑”设置中改动显示时间为10秒后,重新启动,结果发现启动界面多了一个选项:

Windows (default) 或 Windows (默认值)

等启动进系统后再看c:\boot.ini,这才焕然大悟(注意黑体部分):

timeout=10
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows XP" /noguiboot /debug /debugport=com1 /baudrate=115200
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows XP" /noguiboot

[boot loader]
timeout=10

原来还是c:\boot.ini的问题。将c:\boot.ini改成如下内容后系统便又恢复至正常:

[boot loader]
timeout=10
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows XP" /noguiboot /debug /debugport=com1 /baudrate=115200
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows XP" /noguiboot

Continue reading » · Rating: · Written on: 06-26-12 · No Comments »

阴沟里翻船之KeSetEvent

KeSetEvent是个使用频率很高的内核支持函数,但经常使用未必意味着确实了解它。上周就曾遇到一件怪事,系统线程在调用KeSetEvent后线程IRQL竟然从PASSIVE_LEVEL提升至DISPATCH_LEVEL,以至后续的操作出错:Bug Check 0xA: IRQL_NOT_LESS_OR_EQUAL。

先看看它的函数声明:

LONG
KeSetEvent(
    IN PRKEVENT Event,
    IN KPRIORITY Increment,
    IN BOOLEAN Wait
);

比较简单,一共只有3个参数:

  • Event: 准备被激活的事件
  • Increment: 预备给被唤醒线程临时提升线程优先级的增量。一般情况下均为0,但针对不同的子系统及对响应快慢的不同要求,会有不同的取值。如网络相关:IO_NETWORK_INCREMENT=2;键盘、鼠标涉及用户界面交互部分:取值为6 (IO_KEYBOARD_INCREMENT, IO_MOUSE_INCREMENT);声音相关,IO_SOUND_INCREMENT=8
  • Wait: 这个参数名字起的太不妥贴,非常容易误导。我就曾以为将此参数设为TRUE表示KeSetEvent将等待睡眠在此Event上的线程被唤醒,直到KeWaitForXXXObject返回。现在看来这种自以为是的默认是全然没有根据的。先看DDK中怎么说这个参数吧:
  • Specifies whether the call to KeSetEvent is to be followed immediately by a call to one of the KeWaitXxx routines. If TRUE, the KeSetEvent call must be followed by a call to KeWaitForMultipleObjects, KeWaitForMutexObject, or KeWaitForSingleObject.

    由此来看,此参数就是为了避免不必要的锁开销及可能的线程调度开销。你可能会认为,调用者先调用KeSetEvent(Event, 0, TRUE),然后直接跟上了KeWaitForXXXObject(Event …),那岂不是多此一举?控制权本就在自己手中,何必再搞个假放权真夺权的无用功呢?再说即便是有用功,也有点任人唯亲或肥水不流外人田的嫌疑吧?

    但反过来想想,如果KeSetEvent和KeWaitForXXXObject所操作的Event对象不是同一个的话,那所有的疑惑便都得到解答了:放的是权A,要夺的则是权B ,但无论如何,作为特权一层,还是可以享受些优待的,至少进入权B争夺之门时不用再去排队了。至于什么时间能得到权B,那是调度器说了才算的,你或许不由得去想,若去公关调度器话,那后门该如何走呢?!

    至此,就Wait参数的使用上,应该不会再有”To be or not to be, that’s a question”的疑惑了吧?

说了这么多,但对KeSetEvent为什么会导致IRQL的提升还没有解答,但其实答案已经明了:KeSetEvent执行时首先会调用KiLockDispatcherDatabase()来提升IRQL以阻止调度器的调度,在Wait = TRUE的情况下,返回时KeSetEvent会保持着高IRQL,而降低IRQL的工作将由后续的KeWaitForXXXObject来做。考虑到两个函数所操作的Event对象并不一定是同一个,所以二者都要通线程结构(KTHREAD)来进行通讯。

最后再说说返回值吧,DDK中是这样说的:If the previous state of the event object was signaled, a nonzero value is returned. 这句话说得还要让你玩“我猜猜”,其隐含的意思就是说如果此Event先前没有被激活,则返回0值。所以,简言之,KeSetEvent的返回值就是此Event先前的状态:Signaled or Not Signaled !

Continue reading » · Rating: · Written on: 06-25-12 · 2 Comments »

也不怕巷子深

常说酒香不怕巷子深,但对于臭味熏天的绍兴臭豆腐,也同样不怕巷子深。

从绍兴餐馆的极品臭豆腐,到老街各色的油炸臭豆腐,无一不是飘着浓浓臭意,可内里却又透出香味来,不断引诱出你肚子里的馋虫,直至肚子撑得实在是没了空间,可嘴里却还是不停地流出口水来。

怪不得鲁迅先生能如此笔锋犀利,口诛笔伐而又不能倦,应有臭豆腐之效。

Continue reading » · Rating: · Written on: 06-24-12 · No Comments »

龙门古镇雨中行

端午假期,古镇游客却出乎意料地少,也许是下雨的缘故。小镇老房子,老街保存相当完整,里面小巷纵横,和八卦村有得一拚; 居民悠闲自在,生活气息浓厚。

image

image

Continue reading » · Rating: · Written on: 06-23-12 · No Comments »