Windows内核开发陷阱 – NTSTATUS

STATUS_REPARSE不是“失败”

曾经在过滤驱动中想针对REPARSE的情况做特殊处理,实现代码如下:

if (NT_SUCCESS(status)) {
    /* file created */
} else if (STATUS_REPARSE == status) {
    /* to be repared  */
} else {
    ...
}

结果代码运行没达有预期效果,手工调试时才发现代码进入了NT_SUCCESS(status)分支。STATUS_REPARSE竟然表示是“成功”的!

这件事后才真正注意到status的问题,虽然在学习内核及驱动编程的时候书上都有相关介绍,但这些基础章节并没有引起重视就直接跳过了。但是像STATS_REPARSE这样的特例并不是少数,如STATUS_PENDING、STATUS_TIMEOUT等。

NTSTATUS的定义

再回顾一下NTSTATUS的定义: NTSTATUS是一个32位长的有符号整型数,即LONG型。其格式如下:

  3                   2                   1                   0
1 0|9|8|7 6 5 4 3 2 1 0 9 8 7 6|5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
---+-+-+-----------------------+-------------------------------
S  |C|N|    Facility           |               Code              
---------------------------------------------------------------

各部分的解释如下:

位数 注解
S 2 Severity: 分4个等级(Success, Informational, Warning, Error)
C 1 1:用户自定义的值 0:默认为系统定义(Microsoft)
N 1 保留位,始终为0值
Facility 12 错误类别或子系统的划分
Code 16 具体错误代码

Severity的4个等级的定义如下:

00 - Success
01 - Informational
10 - Warning
11 - Error

NT_XXXX宏分析

引用WDK代码(ntdef.h):

//
// Generic test for success on any status value (non-negative numbers
// indicate success).
//

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)

//
// Generic test for information on any status value.
//

#define NT_INFORMATION(Status) ((((ULONG)(Status)) >> 30) == 1)

//
// Generic test for warning on any status value.
//

#define NT_WARNING(Status) ((((ULONG)(Status)) >> 30) == 2)

//
// Generic test for error on any status value.
//

#define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3)

由定义可以看出至少两点:

  1. NT_SUCCESS的定义和其它三者是有区别的,并不是根据Severity的值来判断的。NT_SUCCESS(status)其实包含了Severity == 0及Severity == 1的情形。

  2. NT_SUCCESS与NT_ERROR不是单纯的非黑即白的关系。新手常犯的错误就是主观上认为NT_SUCCESS()与!NT_ERROR(),或者NT_ERROR()与!NT_SUCCESS()是等价。

如果想确保一个irp确实是“成功”完成了,就要严格用STATUS_SUCCESS来判断,而不是NT_SUCCESS()。类似地,用NT_ERROR()可以保证代码的执行确实出现了错误,但是NT_WARNING()的情况即被漏掉了。

总之,必要的基本功始终是不可少的,像熟悉开发场景相关的返回值并弄懂这些返回值的确切含义等此类的苦活累活是必须要做且要做好的。

参考链接

  1. [MS-ERREF]: Windows Error Codes
  2. Windows Driver Kit (WDK)

<本文用时2小时,字体及表格调整花时较多。>

Windows内核开发陷阱: Nt vs Zw

在Windows平台上做过开发的人对Native API都不会陌生。但以Nt开头与以Zw开头的Native API到底有没有差别以及都有什么使用场景等一直以来都是让人感觉比较困扰的问题。另外内核态也有同样的一组Nt及Zw开头的内核支持函数,这两者间又是怎么一回事呢,这其中会有会有开发上有陷阱呢?!

我们先来解析用户层,然后研究下内核层其各自的实现:

用户层的Nt vs Zw

对用户层程序来说Nt与Zw开头函数实际上是一样的,只是同一个函数的两个名称而已。比如下面是对ntdll!NtCreateFile及ntdll!ZwCreateFile(Win7 X64)的反汇编:

1: kd> u ntdll!ZwCreateFile
ntdll!NtCreateFile:
00000000`77480400 4c8bd1          mov     r10,rcx
00000000`77480403 b852000000      mov     eax,52h
00000000`77480408 0f05            syscall
00000000`7748040a c3              ret

1: kd> u ntdll!ZwClose
ntdll!NtClose:
00000000`7747ffa0 4c8bd1          mov     r10,rcx
00000000`7747ffa3 b80c000000      mov     eax,0Ch
00000000`7747ffa8 0f05            syscall
00000000`7747ffaa c3              ret

syscall

用户层ntdll!ZwCreateFile或NtCreateFile会通过syscall切换到内核,最早的Windows系统是通过软中断0x2e来完成模式切换的。如何切换以及切换至何处是由CPU的MSR寄存器所决定的,由操作系统在启动时完成的初始化设定。

针对Win7 X64测试系统来说,此处的syscall指令将跳转由MSR_LSTAR所指向的固定地址:

0: kd> rdmsr 0xc0000082
msr[c0000082] = fffff800`03082ec0
0: kd> u fffff800`03082ec0
nt!KiSystemCall64:
fffff800`03082ec0 0f01f8          swapgs
fffff800`03082ec3 654889242...000 mov   qword ptr gs:[10h],rsp
fffff800`03082ecc 65488b242...000 mov   rsp,qword ptr  gs:[1A8h]
fffff800`03082ed5 6a2b            push    2Bh
fffff800`03082ed7 65ff34251...000 push    qword ptr gs:[10h]
fffff800`03082edf 4153            push    r11
fffff800`03082ee1 6a33            push    33h
fffff800`03082ee3 51              push    rcx
......
fffff800`02c7bfce 6690            xchg    ax,ax
fffff800`02c7bfd0 fb              sti
fffff800`02c7bfd1 48898be0010000  mov     qword ptr [rbx+1E0h],rcx
fffff800`02c7bfd8 8983f8010000    mov     dword ptr [rbx+1F8h],eax
nt!KiSystemServiceStart:
fffff800`02c7bfde 4889a3d8010000  mov     qword ptr [rbx+1D8h],rsp
......

nt!KiSystemCall64主要的工作是:

  1. 参数及用户栈的保存(内核态处理完打好返回用户层)
  2. 内核栈切换、内核线程的初始化设定等
  3. 准备工作做好后,最重要的是调用相应的内核服务(比如nt!NtXXX)

内核层的Nt vs Zw

对内核层来说,Zw与Nt函数虽然参数及功能上都是一致的,但二者对参数的处理及对调用环境的认同是不一样的。以nt!NtClose及nt!ZwClose的代码为例(Win7 X64):

1: kd> u nt!NtClose
nt!NtClose [d:\w7rtm\minkernel\ntos\ob\obclose.c @ 430]:
fffff800`0338e2a0 48895c2408      mov     qword ptr [rsp+8],rbx
fffff800`0338e2a5 57              push    rdi
fffff800`0338e2a6 4883ec20        sub     rsp,20h
fffff800`0338e2aa 65488....010000 mov   rax,qword ptr gs:[188h]
fffff800`0338e2b3 48833....e9ff00 cmp     qword ptr [fffff800`03227450],0
fffff800`0338e2bb 488bd9          mov     rbx,rcx
fffff800`0338e2be 0fb6b8f6010000  movzx   edi,byte ptr [rax+1F6h]
fffff800`0338e2c5 0f8520a70200    jne     nt!NtClose+0x2a74b (fffff800`033b89eb)
fffff800`0338e2cb 400fb6d7        movzx   edx,dil
fffff800`0338e2cf 488bcb          mov     rcx,rbx
fffff800`0338e2d2 488b5c2430      mov     rbx,qword ptr [rsp+30h]
fffff800`0338e2d7 4883c420        add     rsp,20h
fffff800`0338e2db 5f              pop     rdi
fffff800`0338e2dc e91ffdffff      jmp     nt!ObpCloseHandle (fffff800`0338e000)

看nt!NtClose没有针对栈做任何准备工作而是直接进入了任务的处理:即调用nt!ObpCloseHandel。类似NtCreateFile也是类似的流程。

下面再来看nt!ZwClose:

1: kd> u nt!ZwClose
nt!ZwClose [o:\w7rtm.obj.amd64fre\minkernel\ntos\ke\mp\objfre\amd64\sysstubs.asm @ 267]:
fffff800`03073640 488bc4          mov     rax,rsp
fffff800`03073643 fa              cli
fffff800`03073644 4883ec10        sub     rsp,10h
fffff800`03073648 50              push    rax
fffff800`03073649 9c              pushfq
fffff800`0307364a 6a10            push    10h
fffff800`0307364c 488d059d300000  lea     rax,[nt!KiServiceLinkage (fffff800`030766f0)]
fffff800`03073653 50              push    rax
fffff800`03073654 b80c000000      mov     eax,0Ch
fffff800`03073659 e9e2670000      jmp     nt!KiServiceInternal (fffff800`03079e40)

1: kd> u nt!KiServiceInternal
nt!KiServiceInternal:
fffff800`02c7be40 4883ec08        sub     rsp,8
fffff800`02c7be44 55              push    rbp
fffff800`02c7be45 4881ec58010000  sub     rsp,158h
fffff800`02c7be4c 488da...000000  lea     rbp,[rsp+80h]
fffff800`02c7be54 48899dc0000000  mov     qword ptr [rbp+0C0h],rbx
fffff800`02c7be5b 4889bdc8000000  mov     qword ptr [rbp+0C8h],rdi
fffff800`02c7be62 4889b5d0000000  mov     qword ptr [rbp+0D0h],rsi
fffff800`02c7be69 fb              sti
fffff800`02c7be6a 65488b1c2588010000 mov   rbx,qword ptr gs:[188h]
fffff800`02c7be73 0f0d8bd8010000  prefetchw [rbx+1D8h]
fffff800`02c7be7a 0fb6bbf6010000  movzx   edi,byte ptr [rbx+1F6h]
fffff800`02c7be81 40887da8        mov     byte ptr [rbp-58h],dil
fffff800`02c7be85 c683f601000000  mov     byte ptr [rbx+1F6h],0
fffff800`02c7be8c 4c8b93d8010000  mov     r10,qword ptr [rbx+1D8h]
fffff800`02c7be93 4c8995b8000000  mov     qword ptr [rbp+0B8h],r10
fffff800`02c7be9a 4c8d1d3d010000  lea     r11,[nt!KiSystemServiceStart (fffff800`02c7bfde)]
fffff800`02c7bea1 41ffe3          jmp     r11

从代码上来nt!ZwClose前面做的工作则有些类似syscall,如果将syscall称作是硬模式切换的话,那nt!ZwCreate所做的可以称算是软模式切换。前者 是从用户态切换到内核态,涉及到栈及context的切换,转换成本很高。而后者则是从内核态到内核态的切换,其实仅是调用关系,均在同一个内核栈上。

nt!ZwXXX函数最终都会切换到相应的nt!NtXXX函数上,因为系统设定所有的任务都是由系统服务nt!NtXXX来实际处理。

从上面的对比不难看出,nt!ZwXXX函数做了一些看似没必要的切换工作,针对 内核驱动来说貌似完全没有必要。比如打开一个文件用nt!NtCreateFile或nt!IoCreateFile等函数更能节省稀缺的内核栈资源的使用,从这点上来看确实如此。

但二者细微的差别却可能导致严重的资源泄露问题。比如一个内核句柄,即在打开文件时指定了OBJ_KERNEL_HANDLE标志,在使用完毕要释放的时候,如果正巧是在用户context(进程上下文),比如IRP_MJ_CREATE或IRP_MJ_CLEANUP等irp的处理程序中,如果调用nt!NtClose的话,则会导致此句柄的泄露,然后此文件会一直处于打开(被占用)的状态。

其原因是nt!NtClose在处理句柄关闭时默认此句柄属于前一个模式空间(PreviousMode)。相反nt!ZwClose正因为多做了“软模式切换”,其切换过程中会修改PreviousMode为KernelMode,因为nt!ZwClose被调用时的线程已经处于内核线程模式(由用户线程通过syscall切换过来的),此时内核句柄用nt!ZwClose就能够正确关闭。

那么我们这里又有了一个新问题:如果反过来用nt!ZwClose来关闭一个本属于用户进程空间的句柄,会不会出错?

答案是不会的。因为nt!ObpCloseHandle在释放句柄前会针对内核句柄做检查,只有在满足全部条件的情况才会当作内核句柄来处理:

#define KERNEL_HANDLE_MASK ((ULONG_PTR)((LONG)0x80000000))

#define IsKernelHandle(H,M)                           \
    (((KERNEL_HANDLE_MASK & (ULONG_PTR)(H)) ==        \
       KERNEL_HANDLE_MASK) &&                         \
     ((M) == KernelMode) &&                           \
     ((H) != NtCurrentThread()) &&                    \
     ((H) != NtCurrentProcess()))

Object Manager通过句柄的第31位来区分是不是内核句柄,所以即使PreviousMode是KernelMode的情形也只会操作当前用户进程的句柄表(CurrentProcess->ObjectTable)而不是内核句柄表(nt!ObpKernelHandleTable)。

小结

不妨简单一点,用这样几个“简单粗暴”的公式来理解它们之间的关系,以NtClose/ZwClose为例:

  • ntdll!ZwClose(用户层) = ntdll!NtClose (用户层)
  • ntdll!ZwClose = syscall(硬切换) + nt!ZwClose(内核层)
  • nt!ZwClose = 软切换 + nt!NtClose(内核层)

魔鬼总在细节之中 (Devils in the details) !

<一直认为对这部分掌握得比较熟,但真正动笔的时候才发现很多不确定的地方。理解只有够深度够准确够熟练才是真正的理解!本文用时2.5小时。>

参考链接

  1. Nt vs. Zw - Clearing Confusion On The Native API
  2. MSDN: PreviousMode
  3. MSDN: Using Nt and Zw Versions of the Native System Services Routines
  4. MSDN: What Does the Zw Prefix Mean?
  5. Windows 7 x64 下的 System Call
  6. 看雪学院:windows 7 x64 KiSystemCall64 笔记

用算法来规划行动

老生常谈且不靠谱的新年计划

新年立志虽然相当的不靠谱,但立几个又何妨,反正2017年已经来了!所以在2016年的最后一晚就给自己订了个计划,其中之一是坚持每周写一篇blog。但是2017年第一周刚刚过完,我的2017 No. 1 blog却只有半个影子。实实在在地给“一周后1/4的人会放弃”的统计结论又添加了一个佐证。

当初订好计划后随意从长长的todo list中挑选了一个感兴趣的议题:手工解析AES加密算法的流程。只是从2号起出差至北京以来不少事务等等等等均成了拖延的最好理由。好在当初计划制定的不严谨,作为程序员的本能总能找出其中漏洞,比如“写一篇blog”并不表示一定要写技术相关的,再者即使写技术相关的也不一定要写类似“解析AES算法”这样苦逼的赃又累的技术活。妥协谁不会!?

不过还是好奇探究了一下相关的统计:显示只有约8%的人会坚持到1年,这些坚持下来的牛人之所以在能立于牛A与牛C之间,原因就是他们不妥协。

扎克伯格的新年计划

小扎的一举一动都有轰动效应,每年伊始他所公布的新年计划都是头条中的头条:

2009年挑战每天戴领带上班 2010年挑战学习汉语2011年挑战只吃自己亲手屠宰的动物 2012年挑战每天写代码 2013年挑战每天跟除脸书员工之外的不同的人见面 2014年挑战每天写封感谢信 2015年挑战每个月读两本书 2016年挑战开发一款私人专属人工智能助手以及全年跑步365英里(587KM)

这些计划看起来都非常的“扎克伯格”,有一篇写他的文章说的一针见血,让我兴奋了好一阵子,其标题就解释了扎克伯格为什么能成为“扎克伯格”的原因:扎克伯格:真正决定人生高度的,是你做事的速度决定你人生高度的从来不是做事的完美程度,而是做事的速度 做事的速度 做事的速度。当下就是最好的时机,下一秒、下下一秒、或下下下一秒就已经晚了。

你的动能决定了你所能冲击的高度。动作越快,动能越大,就在你出发的那一刻,你所能冲击的高度便已确定!

用算法来规划行动

妥协者最好的借口莫过于“三思而后行”,然后又用急性子的常犯的错误来反证自己的正确,比如:急急忙忙写了一行代码,结果却引入了若干个新Bug,之后再花几天时间解决掉这些Bug,虽然全是“无用功”,可不也是顿觉成就感爆棚,不也是KPI亮闪闪的程序<折腾>人生?

万维钢老师在《精英日课》第64期中提到过一个有趣的结论:将37%期限的时间专门用来各种纠结与横纵向比较,当37%的时限一过就马上决定,然后依决定采取行动!

这是《Algorithms to Live By: The Computer Science of Human Decisions》一书中所给出的答案!

要成为一个理性的人,要在一个不确定的世界中做到尽量好,就要遵循这样的规则,给自己一个适当的纠结期,纠结期一过马上就做决定。只有行动才能有结果,只有快速行动才能争取到有利的位置和修正错误的机会。

参考链接

  1. Mark Zuckerberg on Facebook截图
  2. 扎克伯格公布2017新年计划
  3. 看看扎克伯格历年来的“新年计划”
  4. 扎克伯格:真正决定人生高度的,是你做事的速度
  5. 万维钢:今天你立志了没
  6. 万维钢:数学家告诉你什么时候结束单身
  7. Marlkdown语法介绍

<第一次用Markdown文法写文章,边写边查语法,再加上查验及组织资料等,这篇短小的blog竟然用了2个多小时的时间!>

括苍之巅-柴古唐斯越野记

这是最好的比赛,这是最坏的比赛;
这是智慧的决定,这是愚蠢的决定;
这是信仰的时刻,这是怀疑的时刻;
这是光明的一天,这是黑暗的一天;
这是希望之水,这是绝望之山;
人们面前绵延青山接连天际,
人们面前白雾阻隔咫尺难见;
人们正在直登天堂;人们正在直下地狱。

总之,那时印象分明就是眼前,喧嚣着的如潮水的人群,无聊至极的一般疯子,个个脸上洋溢着电光一样的兴奋和一发不可收的癫狂。说它好,越野就是有这般魔力;说它不好,所有的人已经着了它的魔。

---- 新的一天从找不到起点开始!

想当然的就以为出发点就是公园边上的城门(实为崇和门),就是没有提前查看下出发点,就是不愿停下来确认轨迹,就是不愿去取藏在背包深处的手机,就愿意相信和一位交流都不通畅的老外所认准的他老婆在微信上共享的一个小点,就是乐意和着人流狂奔,从崇和门奔到最西边的望江门,然后又沿城墙外奔到最南边的兴善门。就是对此类的偷懒没辙!

一路上边跑边问,结果路人包括出租司机竟没有一个知道兴善门的。等到我跑到起点,离发枪已经5分钟了,大部队早已没了踪影。在我连闯三个临海古城门的时候,乾昌和茂飞大概正在愉快的拍照和把妹吧,叹好机会总是一去不复还!

以最快的速度寄好装备、芯片检录,顺着赛会的旗帜快跑,这时后面传来一声吆喝,原来是另外一位没找对门的跑友,还是台州本地的,我们边笑边跑边聊,一路上没有看到志愿者和其他队员,心存怀疑但没有停下脚步,直到看到了亲爱的志愿者们,心里的石头才算放下。小跑一会,追上了正慢走等我的乾昌和茂飞,兄弟见面格外亲切。真基情才是好基友!

----  这只是一场和关门时间的赛跑!

SP(兴善门)-CP2(兰辽农场) 21KM 海拔:+1200/-400 用时:3:42

从古城墙下来跑没多远,便进入山野小径,连续的上坡,此时刚开始已然有些乏力感,好在人流拥挤,道路湿滑,大部队只能蛇形蜿蜒慢行,我倒是得以缓口气。想着这几个月一直在出差忙工作,没能系统训练,不免有些担心。担心也罢,只得硬着头皮,以自己最适宜的节奏匀速行进,不特意去跟茂飞和乾昌他们,逐渐和他们渐行渐远。后面的路还很长,我跑的还算是比较耐心,因为自己清楚自己不仅仅输在了起跑线上,而是起跑之前的训练,现在最重要的是保持节奏。

然而就在离CP2兰辽农场还有约5公里的地方,后面有人赶上来催促大家快点,说CP2的关门时间马上就到,我看表已是9:10,离关门时间9:50还只有40分钟,还不知后面这5公里是怎样的路况。跑了这么多次比赛还是头一遭碰到马上被关的情形,目前的速度其实并不慢,但还是不够快。之后又听到解释说主办方特意收紧前几个检查点的关门时间就是为了最大化的刷掉能力弱的队员,因为后面的爬升和夜跑更多更难更险。难不成这么快就被刷下去了!?

此时已管不得节奏,只专注于地面路况和前面人的脚跟,翻过一个石头又一个石头,越过一个人又一个人,整队伍的人都在拼,开始基本全是平路或缓坡,然后就是一路下降,但下坡路上全是杂乱的大石头和在石头上龟行的拥堵的人群,我择机从右侧的石头堆上飞过,走到人群的空档,然后一跑飞奔直降CP2兰辽农场,看时间正好9:50。至打卡处得知关门时间是10点。见到了乾昌和茂飞,三人嘴不停手不停边吃边拿,回望慌乱的人群不断的向检查点涌来,有点幸灾乐祸,心想这一关总算过了。

20160416-柴古-31199814120270419120160416-柴古-22718329911458752420160416-柴古-cp3

CP2(兰辽农场)-CP3(上白岩村) 8KM 海拔:+490/-500 用时:1:54

CP2过得不容易,CP2-CP3这段算是此行最容易的一站,像是暴风骤雨前的片刻平静,因为后面的路只会越来越虐。CP2出发后,一路上紧跟乾昌和茂飞,路上下了阵急雨,鞋上全是泥,越走越重,比较辛苦。约11:45左右到达CP3,此时到关门时间也仅仅提前了15分钟。

此站是30KM的终点,人声鼎沸,全是欢乐的声音,好幸福!只是对我们跑82KM的来说行程刚刚过了1/3。

CP3(上白岩村)-CP4(盆化寮) 6KM 海拔:+530/-180 用时:1:40

30KM的人撤离之后路上的拥堵状况马上就好转了,只是中午是我传统的最差状态时间段,特别是吃了一肚子香蕉和一碗稠粥之后,上坡路段更是抬不动腿了,右大腿肌肉感觉明显乏力。和茂飞及乾昌的距离越走越远,我索性不着急了,不紧不慢按节奏走,边走边调整状态。60KM收队的老伯则健步如飞地从我后面赶超,边走边聊,拿着对讲乱呼一通,让我好不气恼,自叹不如。没想到的是,此老伯竟一路跟到了CP6,直到CP6出发不久才终于将他老人家给甩开。

因为道路塌方,CP4调整了位置,补给较少,只有冷食、凉开水,加上天气又转恶劣,开始刮风下雨,冷风吹身上不禁要打寒颤。简单补给后和好兄弟茂飞乾昌又开始下了下一站的征程。

CP4(盆化寮)-CP5(黄家寮) 12KM 海拔:+500/-1000 用时:3:15

20160416-柴古-spyuq5at8453tyb9j0rhtj1ysp33ow2920160416-柴古-nrgjofds0o8t1emi40ynh9ew4yvz33di

出发后就是缓慢上升,好在上升路段主要集中在前面,后面和60KM的线路分道后则是一路下降,一路上没就见其他人,我跑的倒也相当惬意,状态逐渐回升中,不过茂飞和乾昌状态更好,茂飞竟然说跑到现在还没有感觉,而我感觉已经残废了一半,太打击人了,遂劝他们先走,我在后面正好可以悠哉悠哉。最后1/3的路段基本沿溪流走,一会左一会右,要不断穿过溪流。在第一个过溪点看到了绳子,应该是主办方所说的要溯溪的地方,我索性脱了鞋子在冷水中泡了一会腿,感觉相当棒。晾脚的时候后来终于跟上来三人小团队,聊谈得知他们从徐州来,想到同是徐州人的老渔民正在跑江南60KM,不知怎样了。和徐州小团队共走了一段路,直到快CP5时才分开,我先行一步到达CP5,茂飞乾昌也刚到没多久。

天马上要黑了,CP5点开始检查装备,将所有的东西都掏了出来,检查完毕后又塞回去,费了不少时间。CP5有热面条,吃的相当惬意,味口相当好,吃完面又喝了一碗热汤,刚刚好,正准备出发,没想到此时丹丹到了,正赶在关门时间之前,乾昌正开吃,大家遂决定等等一起出发。等了一会身体有些发冷,又吃了一碗热面条,绝对吃撑了。

CP5停留了30多分钟,以至后来的时间相当紧张,差点被关门。

CP5(黄家寮)-CP6(跑马坪) 10KM 海拔:+1200/-600 用时:4:15

这段路可谓是最绝望的一段,出发后就是艰难的上升,我们三人陪丹丹走了一段,然后便快步先行,走了一段根本不是路的路,先是在圆木堆上走平衡,然后又在横七竖八的树阵里走梅花桩,叹主办方这样为虐而虐的路线设计,又遐想闫龙飞跑到这里时皱起眉头的样子(到终点后才行知闫大神因为伤病没来跑柴古)。梅花桩虽然难走但并不费力,后面却是耗尽体力的艰难爬升,我在心里数着自己的呼吸,从0到100不断循环往复,终于达成这段不间断的海拔上升,可等到我们终于爬到山顶才发现前面还有座更高更黑更恐怖的山头在等我们,上面零星有头灯的灯光闪烁着光环,像梦境,像幻觉,但冰冷的现实又将我们拉了回来,饥饿感与疲倦感阵阵袭来,我和茂飞坐着休息了会,吃了个能量胶,这是此行我第一次吃自己带的补给,也是仅有一次。休息的时候,乾昌上来后没有停留继续上山,看起来体能还相当好;收队的老伯吵着他的对讲也走了过去,后面紧跟着还有一队的人马。我和茂飞休息一会后继续赶路,然后听到前方有人喊“前面是平路了”,终于不用下降了,我俩像打了鸡血,一阵加速跑,就好像前面有金子要抢似的。

走得已经麻木了,关门时间马上就到,我觉得时间已经不够了,茂飞问我要不要跑一跑,但我实在跑不起来,特别像这种漫长上坡,快步走都觉得腿部力量有些不支。被关门倒也是个最好的解脱,真的不想再走了,也走不动了。思想的扰动开始跳跃,但脚步并没有停,还是艰难地一步步迈向前方,还在不断地赶超,只是越走越累,越走越绝望。对面有志愿者走过来,说还有1公里,但远处闪烁的头灯让我觉得他说只有1公里真是荒唐的可笑,分别还有“10公里”的距离。

走着走着,前面的一个人给我让路让我先行,我对他说了句“加油,马上就到”之后,忽然觉得脚步一阵轻松,又开始健步如飞,状态回来了?!一阵急步,看到了高处的影子原是风力发电机的高塔,隐约听到了远处传来的嘈杂的人声,很快望见了CP6的灯光,我又飞起来了!

21:30:我手表上显示的时间,工作人员正给我打卡,正好是关门时间,赶上了,TNND,终于赶上了!

CP6(跑马坪)-CP7(米筛浪) 4KM 海拔:+270/-150 用时:0:55

喝了一碗热面条后,帮茂飞整理水袋,忙中出错,搞了好一会子才搞定,此时丹丹也上来了,但因为晚了5分钟多被关了,但她还是决定继续走完全程。出发时已是21:38,到CP7的时间已经相当紧张,但越是时间紧张却越是出各种岔子。天空非常黑,伸手辨不出五指,头灯发出的昏昏的光只能勉强照亮脚前的方圆之地,根本看不到路边红色的路标布条,只能凭着感觉走,来回了几趟以确认路线,耽误了不少时间,后来前面又传来路线不对的喊声,我跑过去又跑回来找路。此时收队的老伯又上来了,肯定的确认路线是对的,原来是风将挡住岔路的信标吹开了,正好挡住了正路,让我们不知可否了好一阵子。

我径直冲到了最前面,钻入丛林,上窜下窜,左转右绕,进入无人之境,心无旁骛,眼中只有脚下的路,远处移动的光点和主办方放置的闪烁的青蛙灯就是我的方向,向前,向前,再向前;快,快,更快!

远远的听到喊加油的声音,还看到有照向天空的光柱,CP7应该快到了,希望就在前方。

22:27分终于到达CP7,志愿者一边给我打卡一边说我这提前的3分钟太值了!82KM组为安全调整了后面的路线,不走原计划的20公里,直接走完60KM组的10公里到终点即可完赛。

CP7(米筛浪)-EP(南岙村) 10KM 海拔:+50/-1400 用时:2:08

得知后面的路线基本全是下降,并且只有10公里,基本放下心来,确定顺利完赛不是问题,并且状态正酣。在休息点喝了点热水,未做过长停留,和一行人一起出发。没多久,乾昌等三人说没有看到路标折返了,大家一同确认路线无误后继续下山。部分路段路标间隔较大,好在岔路不多并且主要是横切公路下山。4公里后开始看到青蛙灯,确认路线没有问题,和后面的队友逐渐拉开距离。台阶路相当湿滑,不小心曾屁降一次,路况基本可控,下坡和认路都是我的强项,一路飞奔,没想到前面还有不少82KM的队员,零零闪闪,有人脚伤走的比较慢,有的队伍边聊边走,大家看到我都不约而同的让路,让我好生感激,简单寒暄后便分道扬镳。

下山的8公里路感觉特长,一路下降,总也走不到头。等下到公路后一阵喜悦,心中想马上就到了,但2公里的公路跑起来并不短,虽然临近村子有路灯,但路标难辨,据后面的志愿者说路标被人给拆了,我一边嘀咕着一边慢跑,直接看到志愿者,志愿者,真亲人呀!还记得我沿马路拐过弯跑到快到终点的马路上时,在前方很远的一位志愿者就开始大喊加油,并挥舞着手中的闪光棒,好似比我还要激动的样子!感谢你们,真心感谢你们,是你们让这场比赛更精彩,是你们让这场超马更圆满!

00:43,冲线!比赛结束!感觉真好!

感谢茂飞和乾昌的一路陪伴和鼓励,感谢辛勤的志愿者们,感谢主办方搞了一场让人难忘而又觉得遗憾的越野赛事。

装备小结

随身穿戴:

1,空顶跑步帽 (的卡侬)
2,快干T恤 (NIKE DRI-FIT)
3,快干7分裤 (NIKE)
4,小腿压缩护套 (ZAMST)
5, 五指加厚运动袜 (injinji)
6,越野跑鞋 (LASPORTIVA MUTANT)
7,越野包 (ULTIMATE DIRECTION AK 2.0 Race Vest)
8,登山杖(BD Z-POLES 112177)
9,全指手套
10,头灯(BD Spot, Fenix HL60R)
11,轻量冲锋衣(TNF)
12,Sunnto AMBIT(心率带+FootPod)
13,头巾(扎手腕上,擦鼻涕专用,哈哈)

食品:

1,能量胶8个(吃2个)
2,盐丸10个(约每7公里一个,共吃了7个,晚上没吃)
3,MoveFree 2片(赛前吃)
4,水2瓶

备用物品:

1,SKINS S400保暖衣一身
2,简易雨衣
3,口哨、现金、纸巾
4,创可贴,红霉素药膏,纱布,包扎带
5,全指手套(备份)
6,急救毯
7, 手机及密封袋

经验检讨

1,对比赛线路基本没研究,关门时间、路况等一点概念都没有
2,未提前探明起点,以致第二天误点,并且多跑了4公里的路,白耗体力
3,头灯电池电量不足,新买的Fenix HL60R未充电(好在撑过了最难走的一段),
     BD头灯放包里被按压误开以致晚上使用时亮度不够
4,检查点的休息时间过长,以致后面时间过于紧张
5,赛后才发现错过了检查点的好多好吃的 :)

20160416-柴古-68758342399766806720160416-柴古证书20160416-柴古成绩

 

The best way out is always through !
     ---- by Robert Frost

南京山地马拉松

去年跑过一次,今天再次参加,本次成绩为458,比去年的520快了20多分钟。从2月到3月一直没有系统训练,工作出差等严重打乱了训练计划,体力储备不够以致最后3公里两腿肌肉抽筋,只得以走走、停停、跑跑的节奏完赛。

certs_owycfpkcswrbpgb920160325-南京山地

和美女冠军东丽合影等待发枪国学队合影

ERESOURCE加锁函数

最常用的加锁函数只有两个:

  • ExAcquireResourceExclusiveLite:试图获取排它锁
  • ExAcquireResourceSharedLite:试图获取共享锁

除此之外,针对共享锁的获取,还有另外两个函数:

  • ExAcquireSharedStarveExclusive:可以早于等待中的排它请求者
  • ExAcquireSharedWaitForExclusive:不能早于排它的请求者

二者都是试图获取共享锁,和ExAcquireResourceSharedLite只是有些微的小差别。但这两个函数使用的并不多,只限于少见的特殊情况:

ERESOURCE本身是可以重入的,如果当前线程已经获得此锁的话,若再次试图获取,ExAcquireResourceSharedLite将会直接准予此请求,但ExAcquireSharedWaitForExclusive则不会,一定要等到此锁等待列表中所有的排它锁的请求者完成之后才会准予当前调用者。当然如果当前线程之前所获得的ERESOURCE锁是排它锁的话,将会直接准予通行,不然会导致死锁发生。

ExAcquireSharedStarveExclusiveExAcquireResourceSharedLite的差别只在一点:若已是共享锁状态,ExAcquireSharedStarveExclusive将忽略排它锁等待列表中的所有请求者,直接准予当前线程,以尽可能的减少当前线程的等待时间。