Flexible Block Groups (flex_bg)

昨天晚上研究了一下e2fsprogs中关于flex_bg的实现。flex_bg,即Flexible Block Groups,是EXT4文件系统引入的一个feature。简言之就是将之前EXT3中分散在各个group中的bitmaps (block bitmap, inode bitmap)及inode table分别集中起来管理。集中起来至少有两个好处:

  • 减少了磁盘寻道操作:将频繁访问的block group资源放到有磁盘上一块连续区域
  • 可以一次性分配更多block给一个extent/run:以前的group将磁盘空间划分为众多不连续的空间片段,从而导致一个分配请求最大能只申请到一个group所管理的blocks。以最常用的4K BLOCK_SIZE来说,一个group最多能管理4K*8=32K个block(128M),但除去group本身的metadata (bitmap blocks: 2  inodes table: (32768 * 128  + 4095)/4096 = 1024),还能剩下31742个block空闲,如果此group包含spare_super,还要减去super_block所占用的一个block。

明白了上面的问题,再来看flex_bg的实现就比较容易了。在创建EXT4卷时,mke2fs会根据用户指定的flex block group大小(flex_bg_size必须为,2的幂,单位为group),将最前的flex_bg_size个groups集中起来管理。看下面的例子:

实验用得是320G的硬盘,只有一个分区:

[root@srv ~]# fdisk -l /dev/sdc

Disk /dev/sdc: 320.1 GB, 320072931328 bytes
255 heads, 63 sectors/track, 38913 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xe4afe4af

   Device Boot      Start         End      Blocks   Id  System
/dev/sdc1               1       38914   312568832    7  HPFS/NTFS

指定flex_bg_size为256个group:

[root@srv ~]# mke2fs -j -O flex_bg,extents,uninit_bg -G 256 -I 256 /dev/sdc

再用debugfs来查看新建EXT4卷group descriptions:

[root@srv ~]# debugfs /dev/sdc1

debugfs:  stats
Filesystem volume name:   <none>
Last mounted on:          <not available>
Filesystem UUID:          5be014f5-5a27-4cf1-81dc-d1f55e71dfdd
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 uninit_bg
Filesystem flags:         signed_directory_hash
Default mount options:    (none)
Filesystem state:         clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              19537920
Block count:              78142208
Reserved block count:     3907110
Free blocks:              76867144
Free inodes:              19537909
First block:              0
Block size:               4096
Fragment size:            4096
Reserved GDT blocks:      1005
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         8192
Inode blocks per group:   512
Flex block group size:    256
Filesystem created:       Fri Jul  8 23:02:47 2011
Last mount time:          n/a
Last write time:          Fri Jul  8 23:06:48 2011
Mount count:              0
Maximum mount count:      20
Last checked:             Fri Jul  8 23:02:47 2011
Check interval:           15552000 (6 months)
Next check after:         Wed Jan  4 23:02:47 2012
Lifetime writes:          4904 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:      63bc1c54-cf76-4546-99a3-7aca37c86fc1
Journal backup:           inode blocks
Directories:              2
Group  0: block bitmap at 1025, inode bitmap at 1281, inode table at 1537
           4089 free blocks, 8181 free inodes, 2 used directories, 8181 unused inodes
           [Checksum 0x2e4a]
Group  1: block bitmap at 1026, inode bitmap at 1282, inode table at 2049
           0 free blocks, 8192 free inodes, 0 used directories, 8192 unused inodes
           [Inode not init, Checksum 0x4578]
Group  2: block bitmap at 1027, inode bitmap at 1283, inode table at 2561
           4095 free blocks, 8192 free inodes, 0 used directories, 8192 unused inodes
           [Inode not init, Checksum 0xa897]

……

Group 255: block bitmap at 1280, inode bitmap at 1536, inode table at 142337
           32768 free blocks, 8192 free inodes, 0 used directories, 8192 unused inodes
           [Inode not init, Block not init, Checksum 0xcd10]
Group 256: block bitmap at 8388608, inode bitmap at 8388864, inode table at 8389120
           0 free blocks, 8192 free inodes, 0 used directories, 8192 unused inodes
           [Inode not init, Checksum 0x42cd]

……

Group 2384: block bitmap at 75497552, inode bitmap at 75497808, inode table at 75538944
          23296 free blocks, 8192 free inodes, 0 used directories, 8192 unused inodes
          [Inode not init, Checksum 0xd1d2]

可以看出,group 0-255的block bitmap,inode bitmap及inodes table是连在一起的,如block bitmap是从1025开始到1280,inode bitmap则从1281开始,直到1536结束。inodes talbe也同样。后面从group 256开始,又是常规的不连续方式。

不妨再做个实验,将指定flex_bg_size设为4096,将所有group(共2385个)都包含进flexible block group:

[root@srv ~]# mke2fs -j -O flex_bg,extents,uninit_bg -G 4096 -I 256 /dev/sdc

启动debugfs来查看group descriptions:

[root@srv ~]# debugfs /dev/sdc1

Filesystem volume name:   <none>
Last mounted on:          <not available>
Filesystem UUID:          ab2057e4-2510-4c25-bd72-c2867bebb294
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 uninit_bg
Filesystem flags:         signed_directory_hash
Default mount options:    (none)
Filesystem state:         clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              19537920
Block count:              78142208
Reserved block count:     3907110
Free blocks:              76867144
Free inodes:              19537909
First block:              0
Block size:               4096
Fragment size:            4096
Reserved GDT blocks:      1005
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         8192
Inode blocks per group:   512
Flex block group size:    4096
Filesystem created:       Fri Jul  8 23:14:02 2011
Last mount time:          n/a
Last write time:          Fri Jul  8 23:17:59 2011
Mount count:              0
Maximum mount count:      39
Last checked:             Fri Jul  8 23:14:02 2011
Check interval:           15552000 (6 months)
Next check after:         Wed Jan  4 23:14:02 2012
Lifetime writes:          4904 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:      f6f4dbbc-3673-4b58-b11c-2fbae02d7ee3
Journal backup:           inode blocks
Directories:              2
Group  0: block bitmap at 1025, inode bitmap at 5121, inode table at 9217
           7511 free blocks, 8181 free inodes, 2 used directories, 8181 unused inodes
           [Checksum 0xee4f]
Group  1: block bitmap at 1026, inode bitmap at 5122, inode table at 9729
           0 free blocks, 8192 free inodes, 0 used directories, 8192 unused inodes
           [Inode not init, Checksum 0x255d]
Group  2: block bitmap at 1027, inode bitmap at 5123, inode table at 10241
           4095 free blocks, 8192 free inodes, 0 used directories, 8192 unused inodes
           [Inode not init, Checksum 0x047e]

……

Group 2384: block bitmap at 3409, inode bitmap at 7505, inode table at 1265665
           23296 free blocks, 8192 free inodes, 0 used directories, 8192 unused inodes
           [Inode not init, Checksum 0x8406]

看得出,整个flexible block group被分隔三部分,第一部分是所有的block bitmap,第二部分是所有的inode bitmap,最后是所有的inodes table。所有的表项都是连续存放于磁盘上的。

无厘头

凡是不确定的东西,你都会觉得难,但其实它可能根本就是无厘头!今天就遇到两件无厘头的事:

第一件是个很无厘头的数据损坏的bug。说它是无厘头,是因为还没搞清楚它为什么会发生,虽然找到了解决方案并且解决方案非常简单。问题大体如下:针对cached i/o,如果写数据超出文件大小,我们会为此文件再分配空间,然后直接调用CcCopyWrite交给Cache Manager来处理就行。这里会涉及文件大小的更改,问题就出在这里。以前的版本是在CcCopyWrite之前就直接将文件大小改好了,数据写入一切正常。后来觉得不妥当,遂改到调用CcCopyWrite之后根据返回结果来更改文件大小,结果就发生了数据的损坏。但研究来研究去,并没有发现CcCopyWrite函数有对文件大小有什么判断。

数独

第二件是一个数独游戏。以前也摆过这道题,但因为不确定性太多,无法下子,然后就不了了之。今天又摆上了,到后面只剩下了1,3,5和8,可能的摆法 很多,就随便摆了摆,结果就通了。呵呵,原来是怎么摆都成,而不是就只有唯一的一个确定的方案!这道题实际上是第一题,应该是最容易的。但我当初玩时却在想,为什么第一题这么难,倒是后面的比较容易,难就难在太纠结于不确定上面了。

FastIo

针对FastIoRead及FastIoWrite的实现,很多文件系统驱动都简单地调用系统提供的File System Runtime Routine: FsRtlCopyRead和FsRtlCopyWrite。

但这样做会有一个隐患,以Ext2Fsd来说,实际的文件大小是存储在inode.i_size中的,不是在FSRTL_COMMON_FCB_HEADER中(或FSRTL_ADVANCED_FCB_HEADER,前者超集)。Cache Manager内部只会处理FSRTL_COMMON_FCB_HEADER中的FileSize,从而导致Fcb中的FileSize和内部的inode.i_size大小不一致。这个 bug还是测试在EXT2卷上用DDK编译/build程序时发现的。

解决办法就是实现自己的FastIoWrite,如果写请求超出文件大小则拒绝此请求,然后走正常的IRP流程。其实,结合上篇blog: AdvanceOnly- FileEndOfFileInformation,还有另外一种方案,即在处理FileEndOfFileInformation w/ AdvanceOnly 时来更新内部的inode.i_size至最新大小,但此方法有待验证。

AdvanceOnly – FileEndOfFileInformation

IRP_MJ_SET_INFORMATION有个特殊的参数:AdvanceOnly,DDK中说明如下:

IrpSp->Parameters.SetFile.AdvanceOnly

Specifies conditions for the setting of each information value. If TRUE, each information value should be set only if greater than the current value. If FALSE, each information value should be set only if less than the current value.

实际实现中,此参数只针对FileEndOfFileInformation才有意义。FileEndOfFileInformation case是用来处理文件数据大小的,即更改文件数据长度。但当AdvanceOnly为TRUE的时候,情形并不如此,此时的真实目的是更新ValidDataLength (VDL),并不是EndOfFile,这是最容易让人误解的地方。

下面是一个典型的调用实例,堆栈分析如下 (XP, i386):

0: kd> kb
ChildEBP RetAddr  Args to Child             
ba4e3bbc b16dfd58 89a462c0 899f8ad8 ba4e3c0c Ext2Fsd!Ext2SetFileInformation+0x2a2
ba4e3bcc b16dfe67 89a462c0 0b2b27d2 899f8ad8 Ext2Fsd!Ext2DispatchRequest+0x9a
ba4e3c0c 804f0095 898d4af8 88b66c40 88b66c40 Ext2Fsd!Ext2BuildRequest+0x89
ba4e3c1c b9ed709e 8988b008 899de848 00000000 nt!IopfCallDriver+0x31
ba4e3c48 804f0095 899f8ad8 88b66c40 88b66c40 fltMgr!FltpDispatch+0x152
……
ba4e3cb4 804f0095 89a8ead8 88b66c40 89a8ead8 fltMgr!FltpDispatch+0x11f
ba4e3cc4 804e2f43 00000000 88cbd008 806e59f0 nt!IopfCallDriver+0x31
ba4e3cf8 804e59aa 88ccd4f8 ba4e3d20 80559690 nt!CcSetValidData+0xa5
ba4e3d34 804e8081 89e31098 80564720 89e31998 nt!CcWriteBehind+0x258
ba4e3d7c 805389bd 89e31098 00000000 89e31998 nt!CcWorkerThread+0x12f
ba4e3dac 805cf84c 89e31098 00000000 00000000 nt!ExpWorkerThread+0xef
ba4e3ddc 8054632e 805388ce 00000000 00000000 nt!PspSystemThreadStartup+0x34
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

可以看出,调用者是Lazy-writer线程。CcWriteBehind将dirty cache写入磁盘后会调用CcStValidData来更新当前文件的ValidDataLength。Cache Manager在每个文件的SHARED_CACHE_MAP中在保存着此文件最大的ValidDataLength (变量ValidDataGoal,由脏页的最大偏移计算出),并在每次写入cache后告知文件系统。

NTFS文件系统中有VDL的概念,但FAT及EXT2都没有,只有AllocationSize及FileSize描述。Ext2Fsd是直接将VDL等同于FileSize来处理的。

慎用MmSetAddressRangeModified

MmSetAddressRangeModified用来设置PFN为dirty/modified,并将PTE的dirty位清除。但除此之外,还有个不明显的副作用,看下面的分析:

1: kd> !pte 0xfffff880`0c9e6000
                                 VA fffff8800c9e6000
PXE @ FFFFF6FB7DBEDF88     PPE at FFFFF6FB7DBF1000    PDE at FFFFF6FB7E200320    PTE at FFFFF6FC40064F30
contains 000000003FE84863  contains 000000003FE83863  contains 0000000014516863  contains 000000000FAD7963
pfn 3fe84      ---DA--KWEV  pfn 3fe83      ---DA--KWEV  pfn 14516      ---DA--KWEV  pfn fad7       -G-DA--KWEV

PTE entry 状态为dirty,并且是可写的(writable)。 再看调用MmSetAddressRangeModified后的状态:

1: kd> !pte 0xfffff880`0c9e6000
                                 VA fffff8800c9e6000
PXE @ FFFFF6FB7DBEDF88     PPE at FFFFF6FB7DBF1000    PDE at FFFFF6FB7E200320    PTE at FFFFF6FC40064F30
contains 000000003FE84863  contains 000000003FE83863  contains 0000000014516863  contains 000000000FAD7921
pfn 3fe84      ---DA--KWEV  pfn 3fe83      ---DA--KWEV  pfn 14516      ---DA--KWEV  pfn fad7       -G--A--KREV

PTE entry的dirty位已被清除,但是此pte已被设成了readonly状态了。所以如果再有写操作,必然会导致page fault发生。

这就是我曾遇到的一个Ext2Fsd的bug:Ext2Fsd为了将page cache锁定,创建了MDL并重新映射到系统空间(调用MmMapLockedPagesSpecifyCache)。新映射的va具有dirty及writable属性,故此va在spinlock (DISPATCH_LEVEL)下进行写操作不会导致任何异常。但在提交改动过程中,Ext2Fsd调用了MmSetAddressRangeModified,调用后MmSetAddressRangeModified会将此pte设置为readonly,如果下一次的写操作正好在spinlock下(DISPATCH_LEVEL),将会导致BSOD: DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1),如果在获取spinlock前曾执行过写操作(IRQL < DISPATCH_LEVEL),则会正常触发page fault,然后MmAccessFault会重置pte为writeable,并设置dirty位,此后如果再进入DISPATCH_LEVEL,对此va进行写操作便不会触发page fault了。这就构成了一定的随机性和隐蔽性,给调试带来了很大的麻烦。

明白了问题所在,不妨再做个实验:如果手工将此pte设为writeable的,再进行写操作,cpu应该直接置pte为dirty,而不必调用OS(即page fault)。

对va 0xfffffa60`04ae7000 调用MmSetAddressRangeModified后,

1: kd> !pte 0xfffffa60`04ae7000
                                 VA fffffa6004ae7000
PXE @ FFFFF6FB7DBEDFA0     PPE at FFFFF6FB7DBF4C00    PDE at FFFFF6FB7E980128    PTE at FFFFF6FD30025738
contains 000000007FFC4863  contains 000000007FFC3863  contains 00000000539A2863  contains 00000000149AD921
pfn 7ffc4      ---DA--KWEV  pfn 7ffc3      ---DA--KWEV  pfn 539a2      ---DA--KWEV  pfn 149ad      -G--A—KREV

手工修改 0xfffffa60`04ae7000为writeable,不必置dirty标志:
1: kd> dq FFFFF6FD30025738 l1
fffff6fd`30025738  00000000`149ad921

1: kd> eb FFFFF6FD30025738 23
1: kd> !pte 0xfffffa60`04ae7000
                                 VA fffffa6004ae7000
PXE @ FFFFF6FB7DBEDFA0     PPE at FFFFF6FB7DBF4C00    PDE at FFFFF6FB7E980128    PTE at FFFFF6FD30025738
contains 000000007FFC4863  contains 000000007FFC3863  contains 00000000539A2863  contains 00000000149AD923
pfn 7ffc4      ---DA--KWEV  pfn 7ffc3      ---DA--KWEV  pfn 539a2      ---DA--KWEV  pfn 149ad      -G--A--KWEV

在进行写操作前可以对KiPageFault或MmAccessFault设置断点,然后进行实验。看断点会不会触发,操作后再检查一下pte的dirty标志是不是已经设置了。具体实验结果,就留给读者自己去验证了。

在DISPATCH_LEVEL中操作paged va无论是已将其page锁定还是重新映射过,都有点如履薄冰的感觉,特别是对file cache,Cache Manager的不少内部操作都会更改pte的属性或调用MmSetAddressRangeModified,到处都可能有陷阱。所以最保险的方式还是不用spinlock 微笑

翻墙

“翻墙”也该该算是中国特色之一了,差不多所有中国互联网用户都曾尝试过。原因很简单,就起因于“Wall”的存在,而且还是个“The Great Firewall”。

墙里没有秋千,墙里也没有佳人,墙里只是个超大的局域网,但相对于世界来说,又是个小圈子。我们所有国人都被限制在一个小圈子里。对全世界开放的所有资源中,只能访问一小部分。好比,当外国人在大舞台上表演的时候,我们只能在小屋里唱KTV,而且还不能随便点歌。

这就是这个国家所造就的最大不公平。这个不公平的后果就是我们在起跑线上就输给了我们的竞争者,我们所看到的世界始终是残缺的,而我们总会慢半拍才能看到最新的资讯。虽然他们有YouTube,我们有Youku,他们有google,我们有baidu,但Youku能替代YouTube,baidu能替代google吗?当你的外国朋友们都在忙着交换facebook链接的时候,你却像个外星人似的一脸茫然,因为你从来没听说过,也不知道它是个什么玩意。这并不是你孤陋寡闻,而是www.facebook.com被墙在外面了,而且我们主流媒体也在刻意过滤着所有的信息。

所以我们翻墙,而且不得不为之!

最早的翻墙主要依赖于国外代理服务器,很多网站都会定期发布一个代理服务器列表。很有耐心地我们不得不一个一个地去尝试,总能找到个好用的。那个时候我在用httpport。

现在大家都开始用VPN了,ssh tunnel也是个不错的办法。我两者都在用。下载大文件时我一般选择VPN。但一连VPN,所有网络访问都要走VPN了,连国内网站还要走个大圈,所以很多时候Firefox+foxyproxy+myentunnel方式,更省事快捷。

有时也用ssh tunnel方式,但仅限于指定网站和端口的服务。如公司VPN会封掉smtp端口,用这种方式可以将本地端口转到smtp server 上。

其实还有种方式我用得也相当多,就是通过https://www.ggssl.com看网页面快照。