Logo address

Partition (区分け) (2)

2023/11/16

はじめに

本来ならば Web のページには解ったことを書くべきなのであるが、パーティションについて書いていると、基本的なことで解らなくなった。ネット上にも解決に必要な情報が見つからない。

問題意識をはっきりさせるために簡単なところから始めよう。ドライブのパーティションの設定状態を見る、あるいは新たにパーティションを設定するコマンドは Plan9 においても fdisk である。同名の fdisk は昔から MS-DOS に存在した、また Linux にも存在する。次の出力は Plan9 の例である。ドライブは 128 MB の usb stick である。(これを選んだ理由は、僕の持っているものの中で、一番小さなドライブだから)

hebe# ls -l /shr/usb/*
--rw-r----- M 1227964 arisawa arisawa 104857600 Jan  1  1970 /shr/usb/sdU66a68/9fat
--rw-rw-r-- M 1227964 arisawa arisawa         0 Jan  1  1970 /shr/usb/sdU66a68/ctl
--rw-r----- M 1227964 arisawa arisawa 127401984 Jan  1  1970 /shr/usb/sdU66a68/data
--rw-r----- M 1227964 arisawa arisawa       512 Jan  1  1970 /shr/usb/sdU66a68/nvram
--rw-r----- M 1227964 arisawa arisawa 127385600 Jan  1  1970 /shr/usb/sdU66a68/plan9
--rw-r----- M 1227964 arisawa arisawa         0 Jan  1  1970 /shr/usb/sdU66a68/raw
--r--r--r-- M      16 arisawa arisawa         0 Oct 17 07:24 /shr/usb/usbevent
hebe# disk/fdisk /shr/usb/sdU66a68/data
cylinder = 262144 bytes
 * p1                   0 486       (486 cylinders, 121.50 MB) PLAN9
>>> q
hebe#

cylinder 数 (= 486)、 及び cylinder 容量 (=262144 B) から disk 容量 121.5 MB は次のように得る:

> 486*262144/1024^2
121.5

Lua による容量計算

区分けの単位が cylinder であることに注目したい。これが今回の主なテーマである。

cylinder と言うのはヘッドの移動を伴わないで読み書きできる記録媒体の集合である。コンピュータにとってヘッドの移動は負荷が大きい。従ってできるだけヘッドの移動はやりたくないのである。可能ならばパーティションは cylinder を跨がないようにしたい。しかし大きなパーティションでは無理な相談である。せめて、パーティションの開始位置を cylinder 内のデータの開始位置に設定しましょうと言うのが、区分けの単位を cylinder にしていることの気持ちであろう。そのためには cylinder size に関する情報が必要なことは明らかである。OS は、この情報をどのようにして手に入れているのか?

記憶媒体がハードディスクからフラッシュメモリーに置き換えられつつあり、head なるものは存在しなくなっているが、それでも記憶領域をブロックに分け、ブロック単位に読み書きが行われておりハードディスクのシリンダーと類似の問題が存在する。

MBR partition entry

ドライブの先頭セクター(512B)には MBR が存在し特殊な役割を担っている、MBR の終わりの方(アドレス 0x1be から始まる 64B)でパーティションの入り口の案内をしている。"partition entry" と呼んでいるが、これは言わば目次のような存在である。MBR が考えられた当初はパーティションは4つもあれば充分だと考えられたのであろう、この中に4つ分の entry が準備されている。次はその例である。(数字は16進数)

00001b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 01 | ................
00001c0: 01 00 39 0F 60 E5 20 00 00 00 E0 CB 03 00 00 00 | ..9.`. .........
00001d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
00001e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
00001f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA | ..............U.
この例では4つの entry のうち、最初の entry 16B 以外は "00" で満たされている。最初の entry は:
	80 01 01 00 39 0F 60 E5 20 00 00 00 E0 CB 03 00
である。この内容は次の通り:

offset size meaning this case
0 1 active or not (80 or 00) 80
1 3 starting CHS address 01 01 00
4 1 partition type 39
5 3 ending CHS address 0F 60 E5
8 4 starting LBA 20 00 00 00
12 4 partition size E0 CB 03 00
つまり address はセクター位置を表す表現である。ここでは2種類のアドレス表現(CHS 方式と LBA 方式)が混在している。CHS 方式は (cylinder,header,sector) の3っの数字の組でドライブの中での sector の位置を表す表現法で、ドライブ容量が大きくなった現在では機能しなくなっている。そこで LBA (logical block address) 方式が考え出された。これはドライブ内のデータを 512B を単位としたまとまり (logical block あるいは単に sector と呼ばれることも多い) に一列に並べて、順に 0,1,2,... と番号を振る方式である。LBA 0 に MBR が存在することになる。現在は LBA が標準的に使われるようになっている。

セクター位置を表すのに歴史的に 1,2,3,... と 1 から開始して数えられてきた。この習慣は現在においても残っている。(C,H,S) の3つ組でアドレスを表す場合には、次のようになる。

	C: 0,1,2,...,Cmax
	H: 0,1,2,...,Hmax
	S: 1,2,...,Smax
(C,H,S) で表したアドレスを小さい方から並べていけば LBA アドレスと対応する。
簡単のために Smax=3,Hmax=1 とすると
LBA CHS
0 (0,0,1)
1 (0,0,2)
2 (0,0,3)
3 (0,1,1)
4 (0,1,2)
5 (0,1,3)
6 (1,0,1)
7 (1,0,2)
8 (1,0,3)
... ...
となる。CHS から LBA は
	Hnum = Hmax + 1; Snum = Smax
とおいて
	LBA = (C*Hnum + H)*Snum + S - 1
で求まる。逆に LBA から CHS は
	S = LBA % Snum + 1
	H = (LBA // Snum) % Hnum
	C = (LBA // Snum) // Hnum
で求まる。(Python3 でも Lua4 でも %// の使い方は同じである)

ややこしいのは partition entry に含まれている CHS と関係する 3B のデータの扱いである。

"starting CHS address" とは素直に受け取れば、アドレスの開始を表している。しかし "ending CHS address" とは何か? 素直に受け取ればアドレスの終わりを表していると思いたい。しかし fdisk のプログラムを読んでいるとそのようには受け取れないのである。この3つ組は (Cmax,Hmax,Smax) を知るために使われているようだ。論理的には別物でも、実際的にはそうなのでしょう。だって利用可能なセクターが存在するのに、そこにアクセスするアドレスを持たないなどというのはバカげたことだから。

かくして Cnum.Hnum,Snum で cylinder, header, sector の個数を表せば

	Cnum = Cmax +1, Hnum = Hmax +1, Snum = Smax
と言うことになる。また Cend,Hend,Send で cylinder, header, sector の ending address を表せば
	Cend = Cmax, Hend = Hmax, Send = Smax
である。

partition entry 16B のうち、後半 8B が 00 であるか否かによって、このパーティションが LBA address 方式が使われていないか使われているかを判断すべきであろう。(他の解釈の余地はないだろうから)
この例ではアドレスは LBR 方式だと考えてよい。最初のパーティションの開始位置は "80 1F 00 00" から判る。これは little endian の 16 進数ゆえ、0x1f80 (=8064) (LBA) を意味する。これに512を掛けて Byte アドレス 4128768 (=0x3f0000) を得る。

サイズは "80 C0 B9 03" より

	0x03b9c080*512 --> 32002605056
(32GB) を得る。

fdisk に表示された 29.80 GB は GiB 単位で表された近似値である。正確なサイズは

hebe# ls -l sdU36d24
--rw-rw-r-- M 1082425 arisawa arisawa           0 Jan  1  1970 sdU36d24/ctl
--rw-r----- M 1082425 arisawa arisawa 32006733824 Jan  1  1970 sdU36d24/data
--rw-r----- M 1082425 arisawa arisawa 32002605056 Jan  1  1970 sdU36d24/dos
--rw-r----- M 1082425 arisawa arisawa           0 Jan  1  1970 sdU36d24/raw
hebe#
で得られ、先の計算と一致している。

パーティションタイプの欄はこのケースでは 0C となっている。文献によると 0C は LBA 対応の FAT32 パーティションであると説明されている([1])。つまり、ここは DOS 用のパーティションですよと言っている。この欄は言わばパーティションに添えられた看板である。フォーマッタはパーティションタイプを見てフォーマットしているはずである(よほどへそ曲がりでは無い限り)。タイプ 00 の意味がはっきりしない。パーティションなのだが、まだ看板が掛かっていないと言っているのか? それともパーティションではないと言っているのか? パーティションではないなら、entry の 16B をすべて 00 にするのが無難なのであろう。

現在ではドライブの容量が大きくなって、もっと多くのパーティションやもっと大きなパーティションを使いたいとのニーズがあるらしい。パーティションを4つから増やすには、拡張パーティションを導入すれば足りる。また 512✕ 232(= 2TB) を超える大きなパーティションを持ちたい場合には GPT 方式が使える。GPT 方式については別の機会に回す。僕の個人的なニーズからすれば1、旧来通りの MBR 方式があれば足りている。


注1: 僕はたくさんのファイルを持っている方だと思うが 500GB もあれば一生使い切れそうにもない。1TB を超えるファイルを持つニーズは個人には存在しないと思う。特殊なサービスを行う企業のニーズである。

[1] マスターブートレコード
https://ja.wikipedia.org/wiki/マスターブートレコード

PBS (Partition Boot Sector)

各パーティションの先頭 512B には PBS が存在する。ここにはパーティションに関する基本的な情報が書かれている。このディスクでは次のようになっていた。

03f0000: EB 58 90 4D 53 57 49 4E 34 2E 31 00 02 40 A0 04 | .X.MSWIN4.1..@..
03f0010: 02 00 00 00 00 F8 00 00 20 00 10 00 80 1F 00 00 | ........ .......
03f0020: 80 C0 B9 03 D0 1D 00 00 00 00 00 00 02 00 00 00 | ................
03f0030: 01 00 08 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
03f0040: 00 00 29 A3 12 1E 3A 54 4F 53 48 49 42 41 20 20 | ..)...:TOSHIBA
03f0050: 20 20 46 41 54 33 32 20 20 20 FA 33 C9 8E D1 BC |   FAT32   .3....
03f0060: F8 7B 8E C1 BD 78 00 C5 76 00 1E 56 16 55 BF 22 | .{...x..v..V.U."
03f0070: 05 89 7E 00 89 4E 02 B1 0B FC F3 A4 8E D9 BD 00 | ..~..N..........
03f0080: 7C C6 45 FE 0F 8B 46 18 88 45 F9 38 4E 40 7D 25 | |.E...F..E.8N@}%
03f0090: 8B C1 99 BB 00 07 E8 97 00 72 1A 83 EB 3A 66 A1 | .........r...:f.
03f00a0: 1C 7C 66 3B 07 8A 57 FC 75 06 80 CA 02 88 56 02 | .|f;..W.u.....V.
03f00b0: 80 C3 10 73 ED BF 02 00 83 7E 16 00 75 45 8B 46 | ...s.....~..uE.F
03f00c0: 1C 8B 56 1E B9 03 00 49 40 75 01 42 BB 00 7E E8 | ..V....I@u.B..~.
03f00d0: 5F 00 73 26 B0 F8 4F 74 1D 8B 46 32 33 D2 B9 03 | _.s&..Ot..F23...
03f00e0: 00 3B C8 77 1E 8B 76 0E 3B CE 73 17 2B F1 03 46 | .;.w..v.;.s.+..F
03f00f0: 1C 13 56 1E EB D1 73 0B EB 27 83 7E 2A 00 77 03 | ..V...s..'.~*.w.
03f0100: E9 FD 02 BE 7E 7D AC 98 03 F0 AC 84 C0 74 17 3C | ....~}.......t.<
03f0110: FF 74 09 B4 0E BB 07 00 CD 10 EB EE BE 81 7D EB | .t............}.
03f0120: E5 BE 7F 7D EB E0 98 CD 16 5E 1F 66 8F 04 CD 19 | ...}.....^.f....
03f0130: 41 56 66 6A 00 52 50 06 53 6A 01 6A 10 8B F4 60 | AVfj.RP.Sj.j...`
03f0140: 80 7E 02 0E 75 04 B4 42 EB 1D 91 92 33 D2 F7 76 | .~..u..B....3..v
03f0150: 18 91 F7 76 18 42 87 CA F7 76 1A 8A F2 8A E8 C0 | ...v.B...v......
03f0160: CC 02 0A CC B8 01 02 8A 56 40 CD 13 61 8D 64 10 | ........V@..a.d.
03f0170: 5E 72 0A 40 75 01 42 03 5E 0B 49 75 B4 C3 03 18 | ^r.@u.B.^.Iu....
03f0180: 01 27 0D 0A 49 6E 76 61 6C 69 64 20 73 79 73 74 | .'..Invalid syst
03f0190: 65 6D 20 64 69 73 6B FF 0D 0A 44 69 73 6B 20 49 | em disk...Disk I
03f01a0: 2F 4F 20 65 72 72 6F 72 FF 0D 0A 52 65 70 6C 61 | /O error...Repla
03f01b0: 63 65 20 74 68 65 20 64 69 73 6B 2C 20 61 6E 64 | ce the disk, and
03f01c0: 20 74 68 65 6E 20 70 72 65 73 73 20 61 6E 79 20 |  then press any
03f01d0: 6B 65 79 0D 0A 00 00 00 49 4F 20 20 20 20 20 20 | key.....IO
03f01e0: 53 59 53 4D 53 44 4F 53 20 20 20 53 59 53 7E 01 | SYSMSDOS   SYS~.
03f01f0: 00 57 49 4E 42 4F 4F 54 20 53 59 53 00 00 55 AA | .WINBOOT SYS..U.

この先頭部には次のような情報が含まれている:

EB 58 90	JmpBoot
4D 53 57 49 4E 34 2E 31	OEM name
00 02	BytesPersec
40	SecPerClus
A0 04	RsvdSecCnt
02	NumFATs
00 00	RootEntCn
00 00	TotSec16
F8	Media
00 00	FATSz16
20 00	SecPerTrk
10 00	NumHeads
80 1F 00 00	HiddSec
80 C0 B9 03	TotSec32
D0 1D 00 00	FATSz32
00 00	ExtFlags
00 00	FSVer
02 00 00 00	RootClus
01 00	FSInfo
08 00	BkBootSec
00 00 00 00 00 00 00 00 00 00 00 00	Reserved
00	DrvNum
00	Reserved
29	BootSig
A3 12 1E 3A	VolID
54 4F 53 48 49 42 41 20 20 20 20	VolLab
46 41 54 33 32 20 20 20	FilSysType

文献[2]に沿って整理されている。この文献は素晴らしい。

ここに書いた分析はあくまで FAT ファイルシステムに対してのものである。Partition の中の情報の整理の仕方は OS やファイルシステムによって異なる。そのために "partition type" が存在するのである。ブート時に Partition を読みに行く前に、MBR の段階で Partition type が判る構造になっている。

ハードディスクに関して解説されてきた概念が過去のものとなり、もはや意味を持たなくなっている。それでもかろうじて意味を保持しているのはシリンダーの持つ情報量(Byte 数)である。フラッシュドライでは物理的なシリンダーもトラックもヘッドもセクターも存在しないものの効率の良い seek 動作が可能な領域は存在する。この領域の容量をシリンダー容量と同一視するのである。

ハードディスクの場合には、

とすると
	scyl = nsec * ssec * nhead
の関係が成立している。フラッシュドライの場合にも形式的に同じ関係式を想定するのであろう。ssec は論理セクターとして形式的に 512B として考える。この場合 OS がデータを読み取る時に 512B の倍数を単位として読み取ることを意味している。nsecnhead は確定しない。これらの決定にはドライバーが関与している可能性が高いのであるが2、僕はまだ具体的なことは知らない。


注2: ドライバーの情報に依存できるのは一部の恵まれた OS だけである。
Plan9 のコードの中に、決定アルゴリズムが書かれていたと記憶しているが、見つかったら補充する。

[2] FATファイルシステムのしくみと操作法
http://elm-chan.org/docs/fat.html