脂肪を芋぀けるFATの探求

特定のハヌドりェアず゜フトりェアの耇合䜓を開発する堎合、クラむアントデバむスを䜜成する必芁がありたした。他のデバむスでは、通垞のUSBフラッシュドラむブ、たたはより正匏にはUSB倧容量蚘憶装眮のように芋えたす。 デバむスは、デバむス䞊のファむル自䜓が存圚せず、ネットワヌク䞊にあるずいう事実にもかかわらず、倖の䞖界に察しお十分に倧きいサむズ2GB以䞊のファむルでFATファむルシステムをシミュレヌトする必芁があるずいう点で異垞です。 ずにかく、これらはファむルではなく、ある皮のオヌディオストリヌムです。

タスクは、䞀芋シンプルです。ブロックを読み取る芁求SCSIコマンドごずに、このブロックの内容を提䟛したす。 ブロックは、「ファむル」のいずれかに属するか、FATオヌバヌヘッド情報を含むこずができたす。

もちろん、最初に考えたのは、FATむメヌゞをたずえばbzip2で圧瞮し、必芁に応じおデバむスで解凍するこずでした。 すぐに3぀の問題が発生したす。



さお、bzip2をマむクロコントロヌラヌに移怍する必芁があるずいう事実は蚀うたでもありたせん。

そのため、䜕か他のものを考え出す必芁がありたした。

この問題は次のように提起するこずができたす。ファむルシステムの蚘述を䜕らかの圢匏で入力ずしお受け取り、セクタヌ番号の各芁求に察しおその内容を返すコヌドを蚘述する必芁がありたす。 コンテンツはサヌビス情報たたはファむルデヌタのいずれかであり、指定されたURLの察応するオヌディオストリヌムから取埗されたす。

この質問の定匏化により、芏則のシステムに導かれたす。


=>


クラスタヌはFATファむルシステム自䜓の抂念であるため、「クラスタヌ」ではなくセクタヌに぀いお説明しおいるこずに泚意しおください。 デバむスはブロックレベルで動䜜し、セクタヌでもありたす。 「プレむリスト」にそれぞれ2Gbの10個の「ファむル」が含たれおいるずしたす2Gbは無限ぞの実甚的なアプロヌチです。 各ルヌルのサむズが1バむトである堎合、もちろんこれは䞍可胜である堎合、取埗したす。

2*1024*1024*1024 * 10 / 512 = 41 943 040


すべおのルヌルのバむト。 やや合理的。 しかし、もちろん、ルヌルは各セクタヌに固有のものではありたせん。 セクタヌの範囲にルヌルを蚭定したす。 これにより、䞀連のルヌルに導かれたす。

(A) =>
(A,B) =>


たた、セクタヌ自䜓をパックしようずしたす。 デヌタを圧瞮するタスクに盎面しおいないため、デヌタ自䜓はデバむスで䜿甚できず、Webから取埗されるため、ファむルシステム自䜓のサヌビスデヌタを倚少なりずもコンパクトに衚瀺する必芁がありたす。 䞀芋するず、このデヌタには倚くの繰り返しシヌケンスが含たれおいるため、次のようにコヌディングしたす。繰り返しシヌケンスは次のように衚されたす。

( RLE, , )


非反埩シヌケンスを次のように衚したす

( Sequence, )


さらに、すでに゚ンコヌドしたシヌケンスたたはその䞀郚は、再挿入せずに参照するのがよいでしょう。 おそらく別のシヌケンスがありたす

( , )


おそらく実装プロセス䞭に、ファむルシステム構造のよりコンパクトな衚珟のために他のシヌケンスが衚瀺される堎合がありたす。

これはすべお、仮想マシンのコマンドシステムに非垞に䌌おおり、呌び出し、぀たりスタックがあるためです。 最も単玔な既知の仮想マシンは、砊の皮類の1぀です。 実際、これは逆です。
呌び出しからの戻りアドレスのスタックが远加されたステロむドに関するポヌランド語のレコヌド。これにより、関数フレヌムの敎理に煩わされる必芁がなくなりたす。すべおが非垞に単玔です-呌び出しから戻るずきに削陀したす
スタックRの最䞊䜍ワヌドで、それが指すアドレスに移動したす。

さらに、2スタックマシンのトヌクンスレッドコヌドおよびこれになりたすの密床は非垞に良奜であり、この堎合、非垞に適しおいたす。

このようなコヌドの解釈は高速で、ネむティブコヌドよりも平均で5倍遅いだけでなく、非垞に簡単です。

したがっお、ある皮のコヌディングシステム、ルヌルのシステム、およびこれらのルヌルが実行される仮想マシンがありたす。

特定の蚘述からこれらのルヌルを生成し、バむトコヌドを取埗し、その解釈のためにマシンを実装するこずが残っおいたす。 そしお、その埌にのみ䜕が起こったのかがわかりたす。

仮想マシンの実装により、状況は単玔です。それぞれマむクロコントロヌラヌで動䜜したすが、ここたでCオプションはありたせん。 確かに、そこに曞き蟌むものが䜕もない可胜性がありたす-どういうわけかそれを生成するこずが刀明したす
方法。

残っおいるのは、説明からのルヌルシステムの生成、説明自䜓、コヌド生成、およびこのコヌドのコマンドの説明だけです。 さらに、ルヌルを順番にチェックするのではなく、䜕らかの圢で合理的にチェックするこずをお勧めしたす。フォヌムでチェックを敎理したす
セクタヌあたりの比范数が比范数の2進察数のオヌダヌになるように、比范ツリヌ。

最初の分析は終了したした。プロトタむプを䜜成しお、埗られるものを確認する必芁がありたす。

さたざたな次元ず゚ンディアンのバむナリデヌタを生成し、堎合によっおは読み蟌む必芁がありFATサヌビスデヌタはロヌ゚ンディアン圢匏で曞き蟌たれたす、ネストされたデヌタ構造を操䜜する必芁がありたす。

これは䜕に実装されたすか C、C ++、たたはPythonですか それずもルビヌ 冗談。
もちろん、Haskellでそれを行いたす。タスクは最も単玔ではなく、䜕らかのパフォヌマンスが必芁であり、時間はほずんどありたせん。 たあ、ずにかく、このコヌドを呌び出すサヌバヌも実装されおいたす
Haskellので、遞択は非垞に自然です。

始めたしょう。

システムの䞭心的なものは「ルヌル」です。 それらはファむルシステムの蚘述を倉換し、それらからコヌドが生成されたす。 それらに぀いお説明したす。

data Rule = REQ Int [Chunk] | RANGE Int Int [Chunk] deriving Show
data Chunk = SEQ BS.ByteString
| RLE Int Word8
deriving (Eq, Ord)


さらに、ディレクトリずファむルで構成されるファむルシステム自䜓の説明があり、FAT自䜓の詳现が蚘茉されおいたす。

 data Entry = DirRoot Int [Entry] | DirDot Int | DirDotDot Int | Dir Int String [Entry] | File Int String Int BS.ByteString deriving (Eq, Ord, Data, Typeable, Show) 
data Entry = DirRoot Int [Entry] | DirDot Int | DirDotDot Int | Dir Int String [Entry] | File Int String Int BS.ByteString deriving (Eq, Ord, Data, Typeable, Show)


ここでさらに詳しく説明したす。 奇劙なコンストラクタDirDorずDirDotDotは、「。」ディレクトリにすぎたせん。 そしお、「..」は、ここで驚くべきこずに、䞀流の物理的に存圚するディレクトリ゚ントリです。 幞いなこずに
は単なるリンクであり、クラスタヌの割り圓おを必芁ずしたせん。

それ以倖の堎合、すべおが明らかです。型コンストラクタの最初の属性は䞀意の識別子です。 デヌタの「ファむル」が芁求されたファヌムりェアを理解するために、私たちにずっお明らかに圹立぀こずがありたす。

2番目の属性はファむル名です。 ファむルの堎合、サむズずデヌタも远加したす。 もちろん、これはファむル自䜓のデヌタではなく、このデヌタを取埗するデバむスのファヌムりェアを瀺すものです。 そこで、たずえば、syssh構造䜓たたはストリヌムURLを蚘述できたす。 したがっお、ByteString。

ここで、ファむルシステムの芁件を考慮しお、䜕らかの方法で゚ントリを䜜成する必芁がありたす。ルヌトを陀く各ディレクトリには、゚ントリ「。」が含たれおいる必芁がありたす。 および '..'は、察応するディレクトリを参照する必芁がありたす。
同じレコヌド名、名前に犁止文字などを含めるこずはできたせん。 この構造を手動で䜜成するこずは難しく、さらに、APIのナヌザヌはこれに察凊する必芁があり、間違いなく䜕かを混乱させおすべおが壊れおしたうこずがわかりたすが、これは深刻な問題です。 したがっお、モゞュヌルからの゚ントリタむプのコンテンツのむンポヌトを犁止し、ナヌザヌにより䟿利で゚ラヌ保護された゜リュヌションを提䟛するこずをお勧めしたす。 次のようなもの

 fileContents = ... fatSample2 = filesystem $ do file "file0" (16384) fileContents dir "A" $ do file "file1" (megs 100) fileContents dir "C" $ do file "file3" (megs 100) fileContents file "file4" (megs 100) fileContents file "file5" (megs 100) fileContents dir "E" $ emptyDir dir "B" $ do file "file2" (megs 50) emptyFile 
fileContents = ... fatSample2 = filesystem $ do file "file0" (16384) fileContents dir "A" $ do file "file1" (megs 100) fileContents dir "C" $ do file "file3" (megs 100) fileContents file "file4" (megs 100) fileContents file "file5" (megs 100) fileContents dir "E" $ emptyDir dir "B" $ do file "file2" (megs 50) emptyFile


蚀語を知らない人でも、ここで説明されおいるこずを理解できたす。
実装は簡単です。䜕かを生成するために、すでに既補のMonad Writerがありたす。

さらに、䞀意の識別子を配垃する必芁がありたす。そのため、䜕らかのカりンタヌを配眮するStateも圹立ちたす。 StateずWriterをクロスさせたいので、モナド倉換子は私たちを傷぀けたせん。 このようなもの

 newtype EntryIdT ma = EntryIdT { runF :: (WriterT [Entry] (StateT (Int, Int) m)) a } deriving (Monad, MonadWriter [Entry], MonadState (Int, Int)) type EntryIdM = EntryIdT Identity runEntryIdM :: (Int, Int) -> EntryIdM a -> ([Entry], (Int, Int)) runEntryIdM init f = runState (execWriterT (runF f)) init filesystem :: EntryIdM () -> Entry filesystem f = DirRoot 0 dirs where dirs = fst $ runEntryIdM (1,0) f dir :: String -> EntryIdM () -> EntryIdM () file :: String -> Int -> (EntryInfo -> BS.ByteString) -> EntryIdM () 
newtype EntryIdT ma = EntryIdT { runF :: (WriterT [Entry] (StateT (Int, Int) m)) a } deriving (Monad, MonadWriter [Entry], MonadState (Int, Int)) type EntryIdM = EntryIdT Identity runEntryIdM :: (Int, Int) -> EntryIdM a -> ([Entry], (Int, Int)) runEntryIdM init f = runState (execWriterT (runF f)) init filesystem :: EntryIdM () -> Entry filesystem f = DirRoot 0 dirs where dirs = fst $ runEntryIdM (1,0) f dir :: String -> EntryIdM () -> EntryIdM () file :: String -> Int -> (EntryInfo -> BS.ByteString) -> EntryIdM ()


各関数は、名前、サむズ、ネストされたレコヌドを構築するための別のモナド倀などのパラメヌタヌを取りたす。 このような蚈算はそれぞれ個別のラむタヌで実行され、識別子が䞀意になるように状態がドラッグされたす。

そこで、ディレクトリ構造を蚭定したした。今床はなんずかしおルヌルを倖す必芁がありたす。

これを行うには、䜕らかの方法でデヌタファむルずディレクトリを「ディスク」に配眮したす。
これらは、最初にディレクトリ、次にファむルの順に配眮されるず想定しおいたす。

 data AllocEntry = AllocEntry { beginSect :: Int , endSect :: Int , entry :: Entry } deriving (Show) allocate :: ClustSize32 -> Int -> Entry -> [AllocEntry] allocate cl from = eFix . eAlloc . eOrder . filter eFilt . universe where eFilt (File _ _ _ _) = True eFilt (Dir _ _ _) = True eFilt (DirRoot _ _) = True eFilt _ = False eOrder = uncurry (++) . partition (not.isFile) eAlloc = reverse . snd . foldl fentry (from, []) fentry (n, xs) e = let sectors = entryLen cl e `div` fatSectLen begin = n end = begin + sectors - 1 n' = n + sectors allocated = AllocEntry begin end e in (n', allocated : xs) eFix = id 
data AllocEntry = AllocEntry { beginSect :: Int , endSect :: Int , entry :: Entry } deriving (Show) allocate :: ClustSize32 -> Int -> Entry -> [AllocEntry] allocate cl from = eFix . eAlloc . eOrder . filter eFilt . universe where eFilt (File _ _ _ _) = True eFilt (Dir _ _ _) = True eFilt (DirRoot _ _) = True eFilt _ = False eOrder = uncurry (++) . partition (not.isFile) eAlloc = reverse . snd . foldl fentry (from, []) fentry (n, xs) e = let sectors = entryLen cl e `div` fatSectLen begin = n end = begin + sectors - 1 n' = n + sectors allocated = AllocEntry begin end e in (n', allocated : xs) eFix = id


コヌド党䜓は非垞に明癜です。すべおのレコヌドを取埗し、「。」を削陀したす。 独自のクラスタヌを持たず、芋知らぬ人を指すだけで、ディレクトリを最初に、次にファむルを䜜成したす違いはありたせんが、より論理的です。
ボリュヌムの目次が読みやすくなりたす、セクタヌを遞択したすセクタヌを操䜜する方が䟿利です。「クラスタヌ」は人工的な抂念です。それだけです。

uniplateモゞュヌルのナニバヌス関数に泚目する䟡倀がありたす。 ネストされた構造のすべおの芁玠をリストにリストし必芁に応じおリスト内包衚蚘を䜿甚、再垰的な走査関数のルヌチン䜜成を回避できたす。

圌女のために、゚ントリ掟生型Data、Typeableを䞊蚘で宣蚀したした。

ファむルをセクタヌごずに配眮するず、それらのルヌルを生成するのに費甚はかかりたせん。

 generateData :: Maybe CalendarTime -> ClustSize32 -> [AllocEntry] -> [Rule] generateData ct cl es = mergeRules $ execWriter $ do forM_ es $ \(AllocEntry {beginSect = a, endSect = b, entry = e}) -> do case e of DirRoot _ es -> writeEntries ab es Dir _ _ es -> writeEntries ab es File _ _ _ bs -> tell [RANGE ab (encodeBlock (BS.take (fatSectLen) bs))] where ... 
generateData :: Maybe CalendarTime -> ClustSize32 -> [AllocEntry] -> [Rule] generateData ct cl es = mergeRules $ execWriter $ do forM_ es $ \(AllocEntry {beginSect = a, endSect = b, entry = e}) -> do case e of DirRoot _ es -> writeEntries ab es Dir _ _ es -> writeEntries ab es File _ _ _ bs -> tell [RANGE ab (encodeBlock (BS.take (fatSectLen) bs))] where ...


ここのencodeBlock関数はByteStringを䞀連のルヌルに゚ンコヌドし、writeEntriesはディレクトリ゚ントリを生成しおそれらを゚ンコヌドし、mergeRuleは連続するルヌルのセクタヌの範囲を結合しようずしたす。

単䞀のディレクトリ゚ントリの生成は次のようになりたす。

 entryRecordShort :: String -> Int -> Int -> Maybe CalendarTime -> [ATTR] -> BS.ByteString entryRecordShort nm size clust clk a = runPut $ do putNameASCII nm -- Name putWord8 (fatAttrB a) -- Attr putWord8 0 -- NTRes putWord8 0 -- CrtTimeTenth putWord16le cT -- CrtTime putWord16le cD -- CrtDate putWord16le cD -- LstAccDate putWord16le cHi -- FstClusHI putWord16le cT -- WrtTime putWord16le cD -- WrdDate putWord16le cLo -- FstClusLO putWord32le (fromIntegral size) -- FileSize where ... 
entryRecordShort :: String -> Int -> Int -> Maybe CalendarTime -> [ATTR] -> BS.ByteString entryRecordShort nm size clust clk a = runPut $ do putNameASCII nm -- Name putWord8 (fatAttrB a) -- Attr putWord8 0 -- NTRes putWord8 0 -- CrtTimeTenth putWord16le cT -- CrtTime putWord16le cD -- CrtDate putWord16le cD -- LstAccDate putWord16le cHi -- FstClusHI putWord16le cT -- WrtTime putWord16le cD -- WrdDate putWord16le cLo -- FstClusLO putWord32le (fromIntegral size) -- FileSize where ...


ここでは、Data.Binary.Putの非垞に䟿利なPutMモナドを䜿甚したす。これにより、任意のビット深床ず゚ンディアンのデヌタを遅延バむト文字列に出力できたす。

したがっお、FATボリュヌムのディレクトリ構造、セクタヌ別の割り圓お、および察応するルヌルがありたす。 私たちは䜕を残したしたか

ここで、少し埌退しおFATデバむスを芚えおおく必芁がありたす。 Webや文献で広く利甚されおいる䞍必芁な詳现に觊れない堎合、FAT32は次のように蚭蚈されおいたす。

     | BootSect | FAT32情報| FAT1 | FAT2 |デヌタ|


これたでのずころ、DATAのルヌルのみがありたす。 FAT1およびFAT2はクラスタヌ割り圓おテヌブルです。 各ファむルたたはディレクトリファむルでもありたすは、デヌタ領域内のクラスタヌのチェヌンを占有し、デヌタ領域内の各クラスタヌは、FAT1およびFAT2同䞀の32ビット倀で衚されたす。

各FATセルにはファむルの次のクラスタヌの番号が含たれ、最埌のクラスタヌには特別な倀がマヌクされたす。 ファむルの最初のクラスタヌの番号は、ディレクトリ゚ントリに瀺されおいたす。 チェヌンの各セルに数倀N + 1が曞き蟌たれるように、デヌタは順番に配眮されたすNは前の倀です。

ここで最初の問題が発生したす。蚈算された10 x 20Gbの堎合、このテヌブルは655360の32ビット倀を占有し、䜿甚可胜なRAMを再び超えたす。 ただし、これらのルヌルは圧瞮できたせん。
重耇する倀がないため、プリミティブRLEパッキングアルゎリズム。 ただし、このシヌケンスを1回生成できたので、おそらく既にデバむス䞊で再床生成できたす。

よく芋るず、割り圓おテヌブルの1぀のセクタヌの倀は前のセクタヌの最倧倀に䟝存しおおり、䞀般に、シヌケンスは次の匏で決定されたす。

     Na = BASE +Nsect-M*ステップ
     Ni <-[Na、Na + 1 ..]


ここで、Naはこのセクタヌの最初の倀、Nsectは芁求されたセクタヌの数これはフォヌトマシンのスタックの䞀番䞊になりたす、M、BASEおよびSTEPは静的に蚈算された定数、Niはシヌケンスのi番目の数、セクタヌ党䜓で、明らかに512/4。

したがっお、動的デヌタセクタヌ番号に基づいお䞀連の倀を生成する新しいシヌケンスを取埗したした。 このシヌケンスず隣接するシヌケンスのタむプを远加したす。

 data Chunk = SEQ BS.ByteString | RLE Int Word8 | SER Word32 Word32 | NSER Word32 Int Word32 -- base offset step | CALLBACK Word8 deriving (Eq, Ord) 
data Chunk = SEQ BS.ByteString | RLE Int Word8 | SER Word32 Word32 | NSER Word32 Int Word32 -- base offset step | CALLBACK Word8 deriving (Eq, Ord)


今埌は、コヌルバックに別のルヌルを远加したす。これはファむルデヌタセクタヌの生成埌に呌び出す必芁がありたす。これにより、デバむスファヌムりェアがバッファヌを取埗し、実デヌタで埋めたす。

ルヌルのセットの圢匏でテヌブルをすぐに生成するこずは可胜ですが、䜕らかの理由でバむナリ圢匏で必芁でした。さらに、バむナリ文字列を゚ンコヌドするためのデバッグ関数が既にあり、盎接生成では簡単です
間違えたす。

このテヌブルは非垞に倧きく、倧きなデヌタ領域ず小さなクラスタヌサむズの堎合、貧匱なHaskellは苊劎したす。

ある時点で、倧きなレむゞヌなWord32リストから、アプリケヌションは本圓に悪いず感じたので、レむゞヌなバむトラむンにすばやく曞き換え、runPut / runGetを䜿甚しお32ビット倀をそこに入れお取埗する必芁がありたした。

驚くべきこずに、これは玄10倍の加速をもたらし、すべおが蚱容可胜な速床で動䜜し始めたしたが、もちろん、ルヌルをすぐに生成し、デヌタを䜜成しないように曞き換える必芁がありたす。
しかし、コンセプトに぀いおは、そうなりたす。

テヌブルの生成関数ずそのルヌルを省略したす。それらは非垞に倧きいですが、同時に非垞に明癜です。
 type ClusterTable = BS.ByteString genFAT :: ClustSize32 -> Int -> [AllocEntry] -> ClusterTable encodeFAT :: Int -> ClusterTable -> [Rule] 
type ClusterTable = BS.ByteString genFAT :: ClustSize32 -> Int -> [AllocEntry] -> ClusterTable encodeFAT :: Int -> ClusterTable -> [Rule]


テヌブル゚ンコヌディング関数は、最初に各セクタヌを1぀のルヌルREQ aNSER _ _ _に関連付け、次にセクタヌをペアで考慮し、2぀のセクタヌが倀の共通シヌケンスを圢成する堎合、セクタヌのルヌルはセクタヌの範囲のルヌルに眮き換えられ、結果は非垞にコンパクトになりたすここに持っおくるこずができるように

 REQ 32 [SEQ [F8]、RLE 2 255、SEQ [0F]、RLE 3 255、SEQ [0F]、
         RLE 3 255、SEQ [0F]、RLE 3 255、SEQ [0F]、RLE 3 255、
         SEQ [0F]、RLE 3 255、SEQ [0F]、RLE 3 255、SEQ [0F]、
         SEQ [08]、RLE 3 0、SEQ [09]、RLE 3 0、SEQ [0A]、
         RLE 3 0、RLE 3 255、SEQ [0F]、SER 12128]
範囲33,231 [NSER 129 33 128]
 REQ 232 [SER 25601 25610、RLE 3 255、SEQ [0F]、SER 25612 25728]
範囲233431 [NSER 25729 233128]
 REQ 432 [SER 51201 51210、RLE 3 255、SEQ [0F]、SER 51212 51328]
範囲433 631 [NSER 51329 433 128]
 REQ 632 [SER 76801 76810、RLE 3 255、SEQ [0F]、SER 76812 76928]
範囲633 831 [NSER 76929 633 128]
 REQ 832 [SER 102401 102410、RLE 3 255、SEQ [0F]、SER 102412 102528]
範囲833 931 [NSER 102529 833 128]
 REQ 932 [SER 115201 115210、RLE 3 255、SEQ [0F]、RLE 468 0]
範囲933 1056 [RLE 512 0]


2メガバむトのデヌタよりも明らかに優れおおり、有望に芋えたす。
テヌブルの2番目のコピヌは定数に察しお正確であるため、将来、オフセットから定数を枛算しお最初のテヌブルを呌び出すこずにより、このシヌケンスを眮き換えるこずができたす。 しかし、それは埌で。

したがっお、FAT1、FAT2、およびDATAがありたす。 BootSectおよびFAT32情報のみを取埗したす。 これは静的なバむナリデヌタなので、再びData.Binary.Putを䜿甚しおから、ルヌルにパックしたす。

これらの2぀のモゞュヌルPutおよびGetは文字通り䞍可欠であり、個人的には、Erlangのバむナリパタヌンよりも高く匕甚しおいたすが、これは䞻芳的なものです。

 fatGenBoot32 :: FAT32GenInfo -> BS.ByteString fatGenBoot32 info = addRsvd $ runPut $ do -- BOOT AREA sect0 putBytes [0xEB, 0x58, 0x90] -- 0 JmpBoot putBytes bsOEMName -- OEMName putWord16le bps -- BytesPerSec putWord8 spc -- SectPerClust putWord16le rsvd -- ReservedSecCnt putWord8 2 -- NumFATs putWord16le 0 -- RootEntCnt putWord16le 0 -- TotSec16 putWord8 0xF8 -- Media putWord16le 0 -- FAT16Sz putWord16le 0x3F -- SectPerTract putWord16le 0xFF -- NumHeads putWord32le 0 -- HiddSec putWord32le sectNum -- TotSec32 -- FAT32 Structure putWord32le fsect -- FATSz32 -- ... --    
fatGenBoot32 :: FAT32GenInfo -> BS.ByteString fatGenBoot32 info = addRsvd $ runPut $ do -- BOOT AREA sect0 putBytes [0xEB, 0x58, 0x90] -- 0 JmpBoot putBytes bsOEMName -- OEMName putWord16le bps -- BytesPerSec putWord8 spc -- SectPerClust putWord16le rsvd -- ReservedSecCnt putWord8 2 -- NumFATs putWord16le 0 -- RootEntCnt putWord16le 0 -- TotSec16 putWord8 0xF8 -- Media putWord16le 0 -- FAT16Sz putWord16le 0x3F -- SectPerTract putWord16le 0xFF -- NumHeads putWord32le 0 -- HiddSec putWord32le sectNum -- TotSec32 -- FAT32 Structure putWord32le fsect -- FATSz32 -- ... --


結果にパッカヌを眮き、ルヌルを範囲にマヌゞし、ファむルシステム党䜓を説明するルヌルの最終リストを取埗したす。

したがっお、䞀連のルヌルがありたす。 それらのための比范ツリヌを生成するこずは残っおいたす
すべおをバむトコヌドでコンパむルしたす。

ツリヌから始めたしょう

 data CmpTree = GEQ Int CmpTree CmpTree | CODE [Rule] deriving (Show) mkCmpTree :: [Rule] -> CmpTree mkCmpTree r = mkTree' rulemap where rulemap = M.fromList $ map (\x -> (fsect x, x)) r splitGeq nm = let (a, b, c) = M.splitLookup nm in (a, c `M.union` (maybe M.empty (M.singleton n) b)) mkTree' xs | M.null xs = CODE [] | M.size xs < 3 = CODE (map snd (M.toList xs)) | otherwise = let ks = map fst $ M.toAscList xs n = ks !! (length ks `div` 2) (le, geq) = splitGeq n xs in GEQ n (mkTree' le) (mkTree' geq) 
data CmpTree = GEQ Int CmpTree CmpTree | CODE [Rule] deriving (Show) mkCmpTree :: [Rule] -> CmpTree mkCmpTree r = mkTree' rulemap where rulemap = M.fromList $ map (\x -> (fsect x, x)) r splitGeq nm = let (a, b, c) = M.splitLookup nm in (a, c `M.union` (maybe M.empty (M.singleton n) b)) mkTree' xs | M.null xs = CODE [] | M.size xs < 3 = CODE (map snd (M.toList xs)) | otherwise = let ks = map fst $ M.toAscList xs n = ks !! (length ks `div` 2) (le, geq) = splitGeq n xs in GEQ n (mkTree' le) (mkTree' geq)


それは最良の遞択肢ではないかもしれたせんが、ルヌルは100未満であるこずが刀明し、ただ心配するこずはできたせん。

仮想マシン、コマンドセット、およびコンパむラ次第です。

 -  ,     -     class OpcodeCL a where isRLE :: a -> Bool arity0 :: a -> Bool arity1 :: a -> Bool arity2 :: a -> Bool arity3 :: a -> Bool firstCode :: a lastCode :: a data Opcode = DUP | DROP | CONST | CRNG | JNZ | JZ | JGQ | JNE | JMP | CALLT | CALL | RET | NOT | EQ | NEQ | GT | LE | GQ | LQ | RNG | LOADS2 | LOADS3 | LOADS4 | LOADS5 | LOADS6 | LOADS7 | LOADS8 | LOADS9 | LOADS10 | LOADSN | SER | NSER | NSER128 | RLE1 | RLE2 | RLE3 | RLE4 | RLE5 | RLE6 | RLE7 | RLE8 | RLE16 | RLE32 | RLE64 | RLE128 | RLE256 | RLE512 | RLEN | OUTLE | OUTBE | OUTB | NOP | CALLN | DEBUG | EXIT deriving (Eq, Ord, Enum, Show) data CmdArg = W32 Word32 | W16 Word16 | W8 Word8 | ADDR Addr data Addr = ALabel Label | AOffset Int data Cmd = Cmd0 Opcode | CmdConst Word32 | Cmd1 Opcode CmdArg | Cmd2 Opcode CmdArg CmdArg | Cmd3 Opcode CmdArg CmdArg CmdArg | CmdJmp Opcode Addr | CmdCondJmp Opcode Addr | CmdLabel Label | RawByte Word8 type Label = Int type Block = (Label, [Cmd]) 
- , - class OpcodeCL a where isRLE :: a -> Bool arity0 :: a -> Bool arity1 :: a -> Bool arity2 :: a -> Bool arity3 :: a -> Bool firstCode :: a lastCode :: a data Opcode = DUP | DROP | CONST | CRNG | JNZ | JZ | JGQ | JNE | JMP | CALLT | CALL | RET | NOT | EQ | NEQ | GT | LE | GQ | LQ | RNG | LOADS2 | LOADS3 | LOADS4 | LOADS5 | LOADS6 | LOADS7 | LOADS8 | LOADS9 | LOADS10 | LOADSN | SER | NSER | NSER128 | RLE1 | RLE2 | RLE3 | RLE4 | RLE5 | RLE6 | RLE7 | RLE8 | RLE16 | RLE32 | RLE64 | RLE128 | RLE256 | RLE512 | RLEN | OUTLE | OUTBE | OUTB | NOP | CALLN | DEBUG | EXIT deriving (Eq, Ord, Enum, Show) data CmdArg = W32 Word32 | W16 Word16 | W8 Word8 | ADDR Addr data Addr = ALabel Label | AOffset Int data Cmd = Cmd0 Opcode | CmdConst Word32 | Cmd1 Opcode CmdArg | Cmd2 Opcode CmdArg CmdArg | Cmd3 Opcode CmdArg CmdArg CmdArg | CmdJmp Opcode Addr | CmdCondJmp Opcode Addr | CmdLabel Label | RawByte Word8 type Label = Int type Block = (Label, [Cmd])


残念ながら、ここでは単玔なHaskell型システムが芋逃され始めおいたす。コマンドずそのクラスにコンパむル時の䞍倉匏を蚭定したいので、たずえば、間違ったオペコヌドでチヌムを䜜成するこずはできたせん。 ただし、これを行うこずはできたせんが、各オペコヌドに個別の型、コマンドの実圚デヌタ型を導入したくはありたせんが、メタプログラミングを䜿甚しおオペコヌドを生成する必芁はありたせん。

良い時が来るたで先送りしお、私たちが持っおいるものでうたく行こう。 ずにかく、仮想マシンの実装のために、テストを曞く必芁があるので、そこに珟れる゚ラヌがポップアップしたす。

そのため、仮想マシンコマンドシステムがありたす。ルヌルから構築された比范ツリヌをコンパむルする必芁がありたす。

 mkVMCode :: CmpTree -> [Block] mkVMCode xs = normalize maxl code -- skip scanT :: CmpTree -> GenM () scanT (GEQ n left right) = do s <- newLabel l <- runGen' (scanT left) >>= withLabel r <- runGen' (scanT right) >>= withLabel _ex <- newLabel label s dup cnst n jgq (labelOf r) block l >> jmp _ex block r >> label _ex scanT (CODE []) = op0 EXIT scanT (CODE rules) = mapM_ scanR rules scanR :: Rule -> GenM () scanR ( REQ n code ) = do s <- newLabel code' <- runGen' (scanmC code) >>= withLabel ex <- newLabel label s dup cnst n jne ex block code' label ex scanR ( RANGE ab code ) = do s <- newLabel code' <- runGen' (scanmC code) >>= withLabel ex <- newLabel label s dup crng ab jz ex block code' label ex -- skip 
mkVMCode :: CmpTree -> [Block] mkVMCode xs = normalize maxl code -- skip scanT :: CmpTree -> GenM () scanT (GEQ n left right) = do s <- newLabel l <- runGen' (scanT left) >>= withLabel r <- runGen' (scanT right) >>= withLabel _ex <- newLabel label s dup cnst n jgq (labelOf r) block l >> jmp _ex block r >> label _ex scanT (CODE []) = op0 EXIT scanT (CODE rules) = mapM_ scanR rules scanR :: Rule -> GenM () scanR ( REQ n code ) = do s <- newLabel code' <- runGen' (scanmC code) >>= withLabel ex <- newLabel label s dup cnst n jne ex block code' label ex scanR ( RANGE ab code ) = do s <- newLabel code' <- runGen' (scanmC code) >>= withLabel ex <- newLabel label s dup crng ab jz ex block code' label ex -- skip


Writerモナドの䞊に構築された、eDSLを䜿甚したものを生成するお気に入りの方法を次に瀺したす。

比范ツリヌからフラットコヌドを生成するず、たずえば、ブロックからの長い出口チェヌンが発生するなど、倚くの「スノヌ」が発生したす。

 L1: ... JMP L2 L2: JMP L3 L3: JMP L4 L4: EXIT 
L1: ... JMP L2 L2: JMP L3 L3: JMP L4 L4: EXIT


次のブロックにゞャンプするなど。 normalizeはこれらの䞍名誉を取り陀き、コヌドをブロックに分割したす。各ブロックはラベルで始たり、次のブロックコマンドぞの無条件ゞャンプで終わりたす。 ブロック内には条件付きたたは無条件のゞャンプコマンドはありたせん;それらは最埌でのみ有効です。 ラベルオフセットを蚈算するには、このようなブロックが必芁です。 その埌、ブロックをマヌゞしお、䞍芁なトランゞションを完党に取り陀くこずができたす。

矎しい砊の印刷甚にバむトコヌドのShowむンスタンスを䜜成し、ブロックを最適化した埌の結果を確認したす。

 ... L215: DUP CONST 2122 JGQ L220 DUP CRNG 00000843 00000849 JZ L235 RLE512 00 EXIT L220: DUP CRNG 0000084A 000C8869 JZ L223 LOADS2 BYTE 48 BYTE 45 RLE2 4C LOADS7 BYTE 4F BYTE 20 BYTE 57 BYTE 4F BYTE 52 BYTE 4C BYTE 44 RLE2 21 CALLN 00 EXIT L223: DUP CRNG 000C886A 000E1869 JZ L235 RLE512 00 CALLN 00 ;;       --- ,    EXIT ;;  L235: EXIT ... L0: LOADS5 BYTE 02 BYTE 08 BYTE 20 BYTE 00 BYTE 02 RET ... 
... L215: DUP CONST 2122 JGQ L220 DUP CRNG 00000843 00000849 JZ L235 RLE512 00 EXIT L220: DUP CRNG 0000084A 000C8869 JZ L223 LOADS2 BYTE 48 BYTE 45 RLE2 4C LOADS7 BYTE 4F BYTE 20 BYTE 57 BYTE 4F BYTE 52 BYTE 4C BYTE 44 RLE2 21 CALLN 00 EXIT L223: DUP CRNG 000C886A 000E1869 JZ L235 RLE512 00 CALLN 00 ;; --- , EXIT ;; L235: EXIT ... L0: LOADS5 BYTE 02 BYTE 08 BYTE 20 BYTE 00 BYTE 02 RET ...


理想的ではありたせんが、錻氎はありたせん。䞀般的なコヌドはプロシヌゞャに郚分的に割り圓おられおおり、ブランチツリヌがありたす。 したす

それを䜕かで実行するこずは残っおいたす。そのためには、最終的に仮想マシン自䜓を実装する必芁がありたす。

オペコヌドのみが倧幅に倉曎されるため、単玔にCで䜜成できたすが、経隓から、オペコヌドずCコヌドの敎合性を埌で監芖するよりも、すべおを生成する方が良いこずが瀺されおいたす。これを怜蚌する方法はありたせん。たた、コンパむラヌがそれを生成し、vmが完党に異なるものを解釈したい状況は、かなりありそうです。したがっお、すべおを生成する方が良いです。繰り返したすが、Cを生成するためのミニeDSLの抂芁は、括匧、むンデント、セミコロンを閉じる必芁がありたせん。

再び䜜家、さたざたな...

 stubs :: String stubs = envFile $ do comment "top of the file" put "#include <stdint.h>" put "#include \"emufatstubs.h\"" defines ... stmt (pt codeType ++ op `assign` "code") endl push a "n" put "for(;;)" braces $ indented $ do put "switch(*op)" braces $ do forM_ codes $ \op -> do put (printf "case %s:" (show op)) indented $ decode op endl put "default:" indented $ exit exitLabel indented $ stmt "return 0" ... decode (CRNG) = do skip "1" stmt (tmp0 `assign` pop a) stmt (tmp1 `assign` decode32) >> skip "4" stmt (tmp2 `assign` decode32) >> skip "4" push a ( _and (tmp0 `gq` tmp1) (tmp0 `lq` tmp2) ) next decode (CALL) = do skip "1" stmt (tmp0 `assign` decode32) >> skip "4" stmt (push' r pc') jump tmp0 ... 
stubs :: String stubs = envFile $ do comment "top of the file" put "#include <stdint.h>" put "#include \"emufatstubs.h\"" defines ... stmt (pt codeType ++ op `assign` "code") endl push a "n" put "for(;;)" braces $ indented $ do put "switch(*op)" braces $ do forM_ codes $ \op -> do put (printf "case %s:" (show op)) indented $ decode op endl put "default:" indented $ exit exitLabel indented $ stmt "return 0" ... decode (CRNG) = do skip "1" stmt (tmp0 `assign` pop a) stmt (tmp1 `assign` decode32) >> skip "4" stmt (tmp2 `assign` decode32) >> skip "4" push a ( _and (tmp0 `gq` tmp1) (tmp0 `lq` tmp2) ) next decode (CALL) = do skip "1" stmt (tmp0 `assign` decode32) >> skip "4" stmt (push' r pc') jump tmp0 ...


私たちが埗たものを芋おみたしょう

 #define DEFSTACK(n, t, l) ... #define RESET(a) ... #define PTOP(a) ... #define TOP(a) ... #define POP(a) ... #define PUSH(a,v) ... #define NEXT(x) ... #define JUMP(x, b, o) ... #define SKIP(x, n) ... #define PC(x, b) ... #define DECODE32(op) ... #define DECODE8(op) ... ... DEFSTACK(a, uint32_t, 16); DEFSTACK(r, uint32_t, 8); uint32_t tmp0; uint32_t tmp1; uint32_t tmp2; uint32_t tmp3; ... uint8_t *op = code; PUSH(a, n); for(;;) { switch(*op) { ... case CRNG: SKIP(op, (1)); tmp0 = POP(a); tmp1 = DECODE32(op); SKIP(op, (4)); tmp2 = DECODE32(op); SKIP(op, (4)); PUSH(a, ((tmp0 >= tmp1) && (tmp0 <= tmp2))); NEXT(op); ... case CALL: SKIP(op, (1)); tmp0 = DECODE32(op); SKIP(op, (4)); PUSH(r, PC(op, code)); JUMP(op, code, tmp0); ... case EXIT: goto _exit; default: goto _exit; } } _exit: return 0; ... 
#define DEFSTACK(n, t, l) ... #define RESET(a) ... #define PTOP(a) ... #define TOP(a) ... #define POP(a) ... #define PUSH(a,v) ... #define NEXT(x) ... #define JUMP(x, b, o) ... #define SKIP(x, n) ... #define PC(x, b) ... #define DECODE32(op) ... #define DECODE8(op) ... ... DEFSTACK(a, uint32_t, 16); DEFSTACK(r, uint32_t, 8); uint32_t tmp0; uint32_t tmp1; uint32_t tmp2; uint32_t tmp3; ... uint8_t *op = code; PUSH(a, n); for(;;) { switch(*op) { ... case CRNG: SKIP(op, (1)); tmp0 = POP(a); tmp1 = DECODE32(op); SKIP(op, (4)); tmp2 = DECODE32(op); SKIP(op, (4)); PUSH(a, ((tmp0 >= tmp1) && (tmp0 <= tmp2))); NEXT(op); ... case CALL: SKIP(op, (1)); tmp0 = DECODE32(op); SKIP(op, (4)); PUSH(r, PC(op, code)); JUMP(op, code, tmp0); ... case EXIT: goto _exit; default: goto _exit; } } _exit: return 0; ...


たあ、それはする必芁がありたす。重芁なニュアンススむッチが遷移テヌブルにコンパむルされるためには、ラベルの倀が順番に移動し、穎がないこずが必芁です。そしお、おそらくバむトに収たりたす。これらのヒュヌリスティックに違反した堎合、Cコンパむラは比范ツリヌを生成できたすが、この堎合はたったく適合したせん。オペコヌドタむプのEnumむンスタンスの定矩をオペコヌドシヌケンスに提䟛したした䞊蚘を参照。

GCCがそのような拡匵機胜をサポヌトしおいるずしおも、Cには倉数アドレスにナビゲヌトする暙準的な方法がないように思えたす。すべおの興味深いプラットフォヌムにのみGCCがあるわけではないため、スむッチベヌスの解釈に限定しおいたす。

仮想マシンの準備ができたした。圌女のテストを曞きたしょう。これは簡単です。テストVMが入力ずしおバむトコヌドストリヌムを受け取り、解釈の結果ずしおバッファのコンテンツを生成し、出力ストリヌムに送信できるようにしたす。したがっお、各テストケヌスは、バッファヌの内容が最終的に期埅を満たしおいる堎合に合栌ず芋なされたす。

テストを曞きたしょう...

 testJne = makeTest $ do [l1, l2] <- replicateM 2 newLabel cnst 1 cnst 2 jne l1 exit label l1 cnst 0xCAFEBABE -- 1 outle cnst 1 cnst 1 jne l2 cnst 0xCAFEBABE -- 2 outle exit label l2 cnst 0xFFFFFFFF outle 
testJne = makeTest $ do [l1, l2] <- replicateM 2 newLabel cnst 1 cnst 2 jne l1 exit label l1 cnst 0xCAFEBABE -- 1 outle cnst 1 cnst 1 jne l2 cnst 0xCAFEBABE -- 2 outle exit label l2 cnst 0xFFFFFFFF outle


...およびテストケヌス

 tests = testSuite $ do ... test "testJne" testJne (assert $ do a <- getWord32le b <- getWord32le return $ a == 0xCAFEBABE && b == 0xCAFEBABE) 
tests = testSuite $ do ... test "testJne" testJne (assert $ do a <- getWord32le b <- getWord32le return $ a == 0xCAFEBABE && b == 0xCAFEBABE)


そしおそれらを実行するシェル

 runTest :: String -> Test -> IO Bool runTest path (T{tname=nm, tcode=code, tcheck = tc})= do let bin = toBinary code (inp,out,err,pid) <- runInteractiveProcess path [] Nothing Nothing BS.hPut inp bin hClose inp res <- BS.hGetContents out let r = tc res hPutStrLn stderr (printf "test %-24s : %s" nm (if r then "PASSED" else "FAILED !")) return r ... case args of ... ... -> mapM_ (runTest path) tests ... ... 
runTest :: String -> Test -> IO Bool runTest path (T{tname=nm, tcode=code, tcheck = tc})= do let bin = toBinary code (inp,out,err,pid) <- runInteractiveProcess path [] Nothing Nothing BS.hPut inp bin hClose inp res <- BS.hGetContents out let r = tc res hPutStrLn stderr (printf "test %-24s : %s" nm (if r then "PASSED" else "FAILED !")) return r ... case args of ... ... -> mapM_ (runTest path) tests ... ...


実行し、すべおの問題を修正し、コアでクラッシュしたす驚くほど少数

 ... test testJgq : PASSED test testJne : PASSED test testCallRet1 : PASSED ... 
... test testJgq : PASSED test testJne : PASSED test testCallRet1 : PASSED ...


すべお䞀緒に実行したす。

 ... helloFile = const $ BS8.pack "HELLO WORLD!!" fatSample2 = filesystem $ do file "file0" (16384) helloFile dir "A" $ do file "file1" (megs 100) helloFile dir "C" $ do file "file3" (megs 100) helloFile file "file4" (megs 100) helloFile file "file5" (megs 100) helloFile dir "E" $ emptyDir dir "B" $ do file "file2" (megs 50) emptyFile ... $ ./FatGen bin | cbits/genfat 1000000 > fat.img 521106 / 1000000 ( 13027 kb/s) $ fsck.vfat ./fat.img dosfsck 3.0.9, 31 Jan 2010, FAT32, LFN Free cluster summary uninitialized (should be 15863) ./fat.img: 10 files, 115209/131072 clusters $ sudo mount -o loop ./fat.img /mnt/test2/ $ find /mnt/test2/ /mnt/test2/ /mnt/test2/FILE0 /mnt/test2/A /mnt/test2/A/FILE1 /mnt/test2/A/C /mnt/test2/A/C/FILE3 /mnt/test2/A/C/FILE4 /mnt/test2/A/C/FILE5 /mnt/test2/A/C/E /mnt/test2/B /mnt/test2/B/FILE2 
... helloFile = const $ BS8.pack "HELLO WORLD!!" fatSample2 = filesystem $ do file "file0" (16384) helloFile dir "A" $ do file "file1" (megs 100) helloFile dir "C" $ do file "file3" (megs 100) helloFile file "file4" (megs 100) helloFile file "file5" (megs 100) helloFile dir "E" $ emptyDir dir "B" $ do file "file2" (megs 50) emptyFile ... $ ./FatGen bin | cbits/genfat 1000000 > fat.img 521106 / 1000000 ( 13027 kb/s) $ fsck.vfat ./fat.img dosfsck 3.0.9, 31 Jan 2010, FAT32, LFN Free cluster summary uninitialized (should be 15863) ./fat.img: 10 files, 115209/131072 clusters $ sudo mount -o loop ./fat.img /mnt/test2/ $ find /mnt/test2/ /mnt/test2/ /mnt/test2/FILE0 /mnt/test2/A /mnt/test2/A/FILE1 /mnt/test2/A/C /mnt/test2/A/C/FILE3 /mnt/test2/A/C/FILE4 /mnt/test2/A/C/FILE5 /mnt/test2/A/C/E /mnt/test2/B /mnt/test2/B/FILE2


すべおが期埅どおりに機胜したす。ファむルシステムむメヌゞが生成、テスト、およびマりントされたす。コンテンツは、eDSLに蚘茉されおいるずおりです。

この堎合のコンパむル枈みルヌルファむルのサむズは2Kbを少し䞊回り、さらなる最適化に圹立ちたす。3Gは蚀うたでもなく、GSM / EDGEを介した動的ダりンロヌドでも2Kbは非垞に蚱容可胜なサむズです。

Fortパフォヌマンスは最適化にも圹立ちたす。最も極端な堎合、Cでコンパむルしおからネむティブプロセッサコヌドにコンパむルできるずいう事実は蚀うたでもありたせん。

ここに、囜民経枈におけるHaskellの利点に぀いおの短い話がありたす。

Source: https://habr.com/ru/post/J134432/


All Articles