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个多小时的时间!>

ERESOURCE加锁函数

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

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

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

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

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

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

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

KEVENT,ERESOURCE及Thread Boost

在唤醒KEVENT时,函数KeSetEvent的第二个参数(KPRIORITY Increment)是用来临时提高被唤醒线程的调试优先级的,此举可以让睡眠在此KEVENT上的线程尽快得到处理以提高系统整体性能。

同样ERESOURCE也有类似的机制,在放锁时也会调高等待列表中处于饥饿状态的线程的优先级。

不过除此之外,ERESOURCE还有一种反向的Boost机制,即让等待者在试图获取锁时调高此锁当前拥有者线程的调度优先级,以加快属主线程的处理并尽可能早的释放锁资源。

反向操作却有可能遭遇这样一个问题:作为锁拥有者的线程未必是同步的,很有可能在获取ERESOURCE锁之后就因为异步处理事件(如Workitem,异步I/O请求)在不释放锁的情况下就退出(锁的释放将由异步事件来做),在此情况下ERESOURCE的属主线程指针就变成非法的了。为此Windows内核提供了一种机制来阻止问题的发生:线程等结构体一般都是至少8字节对齐的(最后3位均为0),通过将最后两位设为1的方式(|0x03)来甄别此指针数据是用户自己所指定的还是默认的系统线程,以此来避免访问已经释放的内存。

从Win7之后,FastFat及Ntfs都有对ERESOURCE的OwnerThread的处理,主要是异步的Noncached i/o事件的处理,其目的就是处理线程在获取ERESOURCE之后退出的情况。

FastMutex的陷阱

相对ERESOURCE来说,FastMutex的使用比较方便,不用考虑禁APC问题,也不用考虑FastMutex结构体的释放,用完即可以三不管。对于竞争不激烈且读写不明显的情况下,用FastMutex还是不错的。

不过使用过程中要注意其隐藏的陷阱:

1,ExAcquireFastMutex 会提升IRQL至APC_LEVEL,且保存原来的IRQL用以在ExReleaseFastMutex时恢复。所以在嵌套获取FastMutex时要特别注意放锁一定要依加锁顺序反向操作,不然会导致IRQL紊乱。为此系统特别提供了Unsafe操作函数(ExAcquireFastMutexUnsafe及ExReleaseFastMutexUnsafe),这两个函数不会操作IRQL,但使用上却有前提条件。

2,FastMutex的实现是通过原子锁与Gate对象来实现的,在没有竞争的情况下,通过原子锁可以速实现加锁及解锁,此外Gate对象是不可重入的,不像Event等(Dispatch Object)及ERESOURCE等可重入的锁(同一个线程可以同时加锁多次不会死锁)。

至于FastMutex为什么要提升IRQL至APC_LEVEL的原因,和文件系统调用FsRtlEnterFileSystem禁APC的原因是一样的,直接目的是防止当前线程被挂起(Suspend)。设想在没有禁止APC的情况下调用ExAcquireFastMutexUnsafe加锁之后线程随时可能被挂起,一旦被挂起的话系统中试图获取该锁的其它线程则只能等待,不管此线程是否高优先级或关键的系统任务。同理可以理解Event,ERESOURCE等为什么也需要禁止APC的原因。

除了FastMutex之外,Windows内核还实现了另外两种Mutex: Regular Mutex及Guarded Mutex。不过从Windows 8开始,GuardedMutext和FastMutex的实现是一样的,二者并无差别。Regular Mutex通过KeInitializeMutex初始化,加锁及放锁是通过KeWaitForXXXObject及KeReleaseMutex来操作的。我们知道 Event也是通过KeWaitForXXXObject来加锁的,但是Regular Mutex的加锁操作却会禁止当前线程的Kernel APC,Event却不会,所以在操作Event之前要显示地进入临界区或禁止APC操作以防止可能的死锁发生。

需要注意的是禁止Kernel APC并不会影响i/o操作,但是FastMutex及GauardedMutex不止是禁止Kernel APC,还会禁止Special APC,这样的话i/o操作是不允许的,因为i/o完成例程是在Special APC中做的。这些细微差别在使用中必须考虑到。

LDM & ldmtool

在Windows系统中创建了一个带区动态卷,其实就是用两个磁盘以RAID0方式整合也一个大磁盘。但启动进入Ubuntu (14.04) 系统后并不能正确识别。下图是我在测试系统中创建了一个带区卷E:(由2个盘构成)和一个RAID5卷F:(由4个盘构成):

LDM-RAID-Volumes

这两个由Windows系统创建的动态卷,是LDM格式(Logical Disk Manager),Linux系统中不能正确识别。最初我尝试用mdadm强制加载带区卷,分别试了64K及128K chunk,但全部无效,尝试办法如下:
root@ubuntu:~# mdadm --build /dev/md0 --level=0 --chunk=128 --raid-devices=2 /dev/sd[cd]3
mdadm: array /dev/md0 built and started.
root@ubuntu:~# mdadm --detail /dev/md0
/dev/md0:
        Version :
  Creation Time : Thu Jun 25 16:08:11 2015
     Raid Level : raid0
     Array Size : 41680640 (39.75 GiB 42.68 GB)
   Raid Devices : 2
  Total Devices : 2

          State : clean
Active Devices : 2
Working Devices : 2
Failed Devices : 0
  Spare Devices : 0

     Chunk Size : 128K

    Number   Major   Minor   RaidDevice State
       0       8       35        0      active sync   /dev/sdc3
       1       8       51        1      active sync   /dev/sdd3
root@ubuntu:~# tune2fs -l /dev/md0
tune2fs 1.42.9 (4-Feb-2014)
tune2fs: Bad magic number in super-block while trying to open /dev/md0
Couldn't find valid filesystem superblock.
root@ubuntu:~# mdadm -S /dev/md0
mdadm: stopped /dev/md0
root@ubuntu:~# mdadm --build /dev/md0 --level=0 --chunk=128 --raid-devices=2 /dev/sd[dc]3
mdadm: array /dev/md0 built and started.
root@ubuntu:~# mdadm --detail /dev/md0
/dev/md0:
        Version :
  Creation Time : Thu Jun 25 16:08:57 2015
     Raid Level : raid0
     Array Size : 41680640 (39.75 GiB 42.68 GB)
   Raid Devices : 2
  Total Devices : 2

          State : clean
Active Devices : 2
Working Devices : 2
Failed Devices : 0
  Spare Devices : 0

     Chunk Size : 128K

    Number   Major   Minor   RaidDevice State
       0       8       51        0      active sync   /dev/sdd3
       1       8       35        1      active sync   /dev/sdc3
root@ubuntu:~# tune2fs -l /dev/md0
tune2fs 1.42.9 (4-Feb-2014)
tune2fs: Bad magic number in super-block while trying to open /dev/md0
Couldn't find valid filesystem superblock.
root@ubuntu:~# mdadm -S /dev/md0
mdadm: stopped /dev/md0
root@ubuntu:~#

后来几经搜索,发现解决方案原来很简单,只需要安装ldmtool。

root@ubuntu:~# parted -l
Model: VMware, VMware Virtual S (scsi)
Disk /dev/sda: 85.9GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos

Number  Start   End     Size    Type     File system     Flags
1      1049kB  1000MB  999MB   primary  ext2            boot
2      1000MB  81.0GB  80.0GB  primary  xfs
3      81.0GB  85.9GB  4898MB  primary  linux-swap(v1)


Model: VMware, VMware Virtual S (scsi)
Disk /dev/sdb: 42.9GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos

Number  Start   End     Size    Type     File system  Flags
1      1049kB  42.9GB  42.9GB  primary  ext4


Model: VMware, VMware Virtual S (scsi)
Disk /dev/sdc: 21.5GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name                          Flags
1      17.4kB  1066kB  1049kB               LDM metadata partition
2      1066kB  134MB   133MB                Microsoft reserved partition  msftres
3      134MB   21.5GB  21.3GB               LDM data partition


Model: VMware, VMware Virtual S (scsi)
Disk /dev/sdd: 21.5GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name                          Flags
1      17.4kB  1066kB  1049kB               LDM metadata partition
2      1066kB  134MB   133MB                Microsoft reserved partition  msftres
3      134MB   21.5GB  21.3GB               LDM data partition


Model: VMware, VMware Virtual S (scsi)
Disk /dev/sde: 64.4GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name                          Flags
1      17.4kB  1066kB  1049kB               LDM metadata partition
2      1066kB  134MB   133MB                Microsoft reserved partition  msftres
3      134MB   64.4GB  64.3GB               LDM data partition


Model: VMware, VMware Virtual S (scsi)
Disk /dev/sdf: 64.4GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name                          Flags
1      17.4kB  1066kB  1049kB               LDM metadata partition
2      1066kB  134MB   133MB                Microsoft reserved partition  msftres
3      134MB   64.4GB  64.3GB               LDM data partition


Model: VMware, VMware Virtual S (scsi)
Disk /dev/sdg: 64.4GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name                          Flags
1      17.4kB  1066kB  1049kB               LDM metadata partition
2      1066kB  134MB   133MB                Microsoft reserved partition  msftres
3      134MB   64.4GB  64.3GB               LDM data partition


Model: VMware, VMware Virtual S (scsi)
Disk /dev/sdh: 64.4GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name                          Flags
1      17.4kB  1066kB  1049kB               LDM metadata partition
2      1066kB  134MB   133MB                Microsoft reserved partition  msftres
3      134MB   64.4GB  64.3GB               LDM data partition

root@ubuntu:~# ldmtool scan
[
  "3ffea520-1b24-11e5-8565-000c293dbd64"
]
root@ubuntu:~# ldmtool show diskgroup 3ffea520-1b24-11e5-8565-000c293dbd64
{
  "name" : "WIN-FB20IXK90MU-Dg0",
  "guid" : "3ffea520-1b24-11e5-8565-000c293dbd64",
  "volumes" : [
    "Volume2",
    "Volume1"
  ],
  "disks" : [
    "Disk1",
    "Disk2",
    "Disk3",
    "Disk4",
    "Disk6",
    "Disk5"
  ]
}
root@ubuntu:~# ldmtool show volume 3ffea520-1b24-11e5-8565-000c293dbd64 Volume1
{
  "name" : "Volume1",
  "type" : "RAID5",
  "size" : 376688640,
  "chunk-size" : 128,
  "hint" : "F:",
  "partitions" : [
    "Disk1-01",
    "Disk2-01",
    "Disk3-01",
    "Disk4-01"
  ]
}
root@ubuntu:~# ldmtool show volume 3ffea520-1b24-11e5-8565-000c293dbd64 Volume2
{
  "name" : "Volume2",
  "type" : "striped",
  "size" : 83353600,
  "chunk-size" : 128,
  "hint" : "E:",
  "partitions" : [
    "Disk5-01",
    "Disk6-01"
  ]
}
root@ubuntu:~# ldmtool show partition 3ffea520-1b24-11e5-8565-000c293dbd64 Disk1-01
{
  "name" : "Disk1-01",
  "start" : 2014,
  "size" : 125562880,
  "disk" : "Disk1"
}
root@ubuntu:~# ldmtool show partition 3ffea520-1b24-11e5-8565-000c293dbd64 Disk2-01
{
  "name" : "Disk2-01",
  "start" : 2014,
  "size" : 125562880,
  "disk" : "Disk2"
}
root@ubuntu:~# ldmtool show partition 3ffea520-1b24-11e5-8565-000c293dbd64 Disk3-01
{
  "name" : "Disk3-01",
  "start" : 2014,
  "size" : 125562880,
  "disk" : "Disk3"
}
root@ubuntu:~# ldmtool show partition 3ffea520-1b24-11e5-8565-000c293dbd64 Disk4-01
{
  "name" : "Disk4-01",
  "start" : 2014,
  "size" : 125562880,
  "disk" : "Disk4"
}
root@ubuntu:~# ldmtool show partition 3ffea520-1b24-11e5-8565-000c293dbd64 Disk5-01
{
  "name" : "Disk5-01",
  "start" : 2014,
  "size" : 41676800,
  "disk" : "Disk5"
}
root@ubuntu:~# ldmtool show partition 3ffea520-1b24-11e5-8565-000c293dbd64 Disk6-01
{
  "name" : "Disk6-01",
  "start" : 2014,
  "size" : 41676800,
  "disk" : "Disk6"
}

用ldmtool解析并创建动态卷只需要一个简单命令

root@ubuntu:~# ldmtool create all
[
  "ldm_vol_WIN-FB20IXK90MU-Dg0_Volume2",
  "ldm_vol_WIN-FB20IXK90MU-Dg0_Volume1"
]

root@ubuntu:~# ls -l /dev/mapper/
total 0
crw------- 1 root root 10, 236 Jun 25 16:29 control
lrwxrwxrwx 1 root root       7 Jun 25 16:29 ldm_part_WIN-FB20IXK90MU-Dg0_Disk1-01 -> ../dm-1
lrwxrwxrwx 1 root root       7 Jun 25 16:29 ldm_part_WIN-FB20IXK90MU-Dg0_Disk2-01 -> ../dm-2
lrwxrwxrwx 1 root root       7 Jun 25 16:29 ldm_part_WIN-FB20IXK90MU-Dg0_Disk3-01 -> ../dm-3
lrwxrwxrwx 1 root root       7 Jun 25 16:29 ldm_part_WIN-FB20IXK90MU-Dg0_Disk4-01 -> ../dm-4
lrwxrwxrwx 1 root root       7 Jun 25 16:29 ldm_vol_WIN-FB20IXK90MU-Dg0_Volume1 -> ../dm-5
lrwxrwxrwx 1 root root       7 Jun 25 16:29 ldm_vol_WIN-FB20IXK90MU-Dg0_Volume2 -> ../dm-0

root@ubuntu:~# ls -l /dev/dm*
brw-rw---- 1 root disk 252, 0 Jun 25 16:29 /dev/dm-0
brw-rw---- 1 root disk 252, 1 Jun 25 16:29 /dev/dm-1
brw-rw---- 1 root disk 252, 2 Jun 25 16:29 /dev/dm-2
brw-rw---- 1 root disk 252, 3 Jun 25 16:29 /dev/dm-3
brw-rw---- 1 root disk 252, 4 Jun 25 16:29 /dev/dm-4
brw-rw---- 1 root disk 252, 5 Jun 25 16:29 /dev/dm-5
root@ubuntu:~#
root@ubuntu:~# tune2fs -l /dev/dm-0
tune2fs 1.42.9 (4-Feb-2014)
Filesystem volume name:   <none>
Last mounted on:          <not available>
Filesystem UUID:          22a45231-ede2-4c60-9c93-ee841ae2f2ee
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index filetype extent flex
_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
Filesystem flags:         signed_directory_hash
Default mount options:    user_xattr acl
Filesystem state:         clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              2605056
Block count:              10419200
Reserved block count:     520960
Free blocks:              10210674
Free inodes:              2605045
First block:              0
Block size:               4096
Fragment size:            4096
Reserved GDT blocks:      1021
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         8192
Inode blocks per group:   512
RAID stride:              16
RAID stripe width:        32
Flex block group size:    16
Filesystem created:       Thu Jun 25 15:58:29 2015
Last mount time:          n/a
Last write time:          Thu Jun 25 15:58:31 2015
Mount count:              0
Maximum mount count:      -1
Last checked:             Thu Jun 25 15:58:29 2015
Check interval:           0 (<none>)
Lifetime writes:          132 MB
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:               256
Required extra isize:     28
Desired extra isize:      28
Journal inode:            8
Default directory hash:   half_md4
Directory Hash Seed:      a472f2f6-a27b-4362-91e2-dce4ad48a700
Journal backup:           inode blocks
root@ubuntu:~# tune2fs -l /dev/dm-5
tune2fs 1.42.9 (4-Feb-2014)
Filesystem volume name:   <none>
Last mounted on:          <not available>
Filesystem UUID:          f0655359-b2e0-4732-8df0-b0ab24918ec1
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index filetype extent flex
_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
Filesystem flags:         signed_directory_hash
Default mount options:    user_xattr acl
Filesystem state:         clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              11771904
Block count:              47086080
Reserved block count:     2354304
Free blocks:              46299313
Free inodes:              11771893
First block:              0
Block size:               4096
Fragment size:            4096
Reserved GDT blocks:      1012
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         8192
Inode blocks per group:   512
RAID stride:              16
RAID stripe width:        48
Flex block group size:    16
Filesystem created:       Thu Jun 25 15:13:55 2015
Last mount time:          n/a
Last write time:          Thu Jun 25 15:14:21 2015
Mount count:              6
Maximum mount count:      -1
Last checked:             Thu Jun 25 15:14:21 2015
Check interval:           0 (<none>)
Lifetime writes:          133 MB
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:               256
Required extra isize:     28
Desired extra isize:      28
Journal inode:            8
Default directory hash:   half_md4
Directory Hash Seed:      88ddd8a2-506a-4e9b-9109-ce9c171b2607
Journal backup:           inode blocks

至此就可以自由访问 /dev/dm-0和/dev/dm-5两个设备卷了。不过还要说的是,ldmtool是通过DM (Device Mapper) 产生的RAID卷(和LVM使用的是同样的机制),而不是最常用的MD方式(即Multiple Devices),特别对于RAID5/RAID6此类的数据卷,MD Raid做了较多的优化,除性能更好之外,稳定性应该也好于Device Mapper方式。

参考阅读:
1,http://manpages.ubuntu.com/manpages/trusty/man1/ldmtool.1.html
2,https://github.com/mdbooth/libldm
3,https://notesbytom.wordpress.com/2012/06/14/linux-support-for-windows-dynamic-disks/
4,http://stackoverflow.com/questions/8427372/windows-spanned-disks-ldm-restoration-with-linux/22108676#22108676 <ldmtool出现之前的办法 Smile>