Google による FUSE の代替品である SDCardFS と、その実装によって I/O オーバーヘッドがどのように削減されるかについて詳しく説明します。
数か月前、Google は「」というものを追加しました。SDカードFS」を Linux カーネルの公式 AOSP ブランチに追加します。 当時、この動きに気づいたのは 一部のカーネル開発者、しかしそれ以外はほとんどのユーザーのレーダーの下で飛んでいきました。 私を含むほとんどのユーザーが、Android OS とそのカーネルの内部で何が起こっているのかを実際には知らないという事実を考慮すると、驚くべきことではありません。
ただし、最近のエピソードでは、 Android 開発者のバックステージ ポッドキャストでは、このトピックに対する関心が再び高まっています。 Chet Haase (Google のシニア ソフトウェア エンジニア) がホストを務めるこのポッドキャストでは、カーネルに加えられた最近および今後の変更について調査しました。 番組には、Android チームで働く Linux カーネル開発者、Rom Lemarchand が出演しました。 二人は主に、A/B アップデートに対応するためにどのような変更が加えられたかについて話し合いましたが、エピソードの最後の 5 分間でルマルシャン氏は、チームが取り組んでいる「次の大きなこと」について話しました。 SDカードFS.
正直に言うと、私はこのポッドキャストを聞いてから SDCardFS の存在を知りました。 もちろん、この話題に興味を持ったのは私だけではありません。 最近の Reddit スレッド が示した。 しかし、私はポッドキャストで提供された基本的な説明に満足できず、いくつかの疑問を払拭しようと努めました。 誤った情報が広まっているため、私は自分で調査し、関連知識を持つ数人の専門家に相談しました。 案件。
この記事に知識を提供し、私の質問に答えるために時間を割いてくださったソフトウェア開発者の Michal Kowalczyk に深く感謝します。
「外部」は実は内部にある
すぐに、明らかにしなければならない誤解がいくつかあるはずです。そうでないと、記事の残りの部分が非常に混乱することになります。 SD カードと Android スマートフォンの歴史について話し合うと役に立ちます。
Android スマートフォンの初期の頃、ほぼすべてのデバイスはストレージとして microSD カードの使用に依存していました。 これは、当時の携帯電話の内部ストレージ容量がごくわずかしか搭載されていなかったためです。 ただし、アプリケーションの保存に使用される SD カードは、少なくとも内部フラッシュ メモリがデータを読み書きできる速度と比較すると、優れたユーザー エクスペリエンスを提供しないことがよくあります。 そのため、外部データ ストレージとして SD カードの使用が増加することが、Google にとってユーザー エクスペリエンスの懸念事項になりつつありました。
外部ストレージ デバイスとして SD カードが早期に普及したため、Android のストレージ命名規則は、すべてのデバイスに実際の物理的な microSD カード スロットがあるという事実に基づいていました。 ただし、SD カード スロットを備えていないデバイスでも、実際の内部ストレージ チップを指すために /sdcard ラベルが使用されていました。 さらに混乱を招くのは、物理 SD カードと大容量ストレージ チップの両方をストレージとして利用するデバイスが、SD カードに基づいてパーティションに名前を付けることが多いという事実です。 たとえば、これらのデバイスでは、/sdcard マウント ポイントは実際の内部ストレージ チップを指しますが、/storage/sdcard1 のようなものは物理的な外部カードを指します。
したがって、microSD カードは実際には外部ストレージであると考えられていますが、命名規則により、実際に物理カードが使用されるようになってからもずっと「SDCard」という名前が使われ続けてきました。 アプリケーション データとそのメディアが 2 つのパーティション間で分離されていたため、ストレージとのこの混乱はアプリケーション開発者にとって頭痛の種でもありました。
初期の内部ストレージ チップのストレージ容量が少ないため、(/data パーティションがいっぱいのため) アプリケーションをインストールできなくなったことにユーザーがイライラすることがありました。 一方、大容量の microSD カードは、メディア (写真、音楽、映画など) のみを保持するように追いやられていました。 当時私たちのフォーラムを閲覧したユーザーなら、Link2SD と Apps2SD という名前を覚えているかもしれません。 これらは、ユーザーがアプリケーションとそのデータをすべて物理 SD カードにインストールできるようにする (ルート) ソリューションでした。 しかし、これらは完璧なソリューションには程遠いため、Google が介入する必要がありました。
有名な話ですが、Google は非常に早い段階で SD カードのプラグインを廃止しました。 Nexus One は、microSD カード スロットを備えた唯一の Nexus デバイスであり続けます (Nexus ブランドは事実上消滅しているため、今後もこのままです)。 Nexus S では、すべてのアプリケーション データとメディアを保存するための統合パーティションは /data パーティション 1 つだけになりました。 かつて /sdcard マウント ポイントとして知られていたものは、現在では単に仮想ファイル システム ( ヒューズ プロトコルは後述します)データ パーティション - /data/media/0 にあります。
互換性を維持し、混乱を軽減するために、Google はメディアを保持するために、現在は仮想化されたこの「sdcard」パーティションを引き続き使用しました。 しかし、この「sdcard」仮想パーティションは実際には /data 内に配置されているため、その中に保存されているものはすべて内部ストレージ チップのストレージ領域としてカウントされます。 したがって、アプリケーション (/data) とメディア (/data/media) にどのくらいのスペースを割り当てるかを検討するのは OEM 次第でした。
Googleはメーカーが自社の例に倣ってSDカードを廃止することを期待していた。 ありがたいことに、時間が経つにつれて、携帯電話メーカーはコスト効率を維持しながらこれらのコンポーネントをより大容量で調達できるようになり、SD カードの必要性が薄れ始めました。 しかし、開発者や OEM が調整に必要な労力を軽減するために、命名規則が存続しています。 現在、「外部ストレージ」というときは、 2つのことのどちらか1つ: 実際のリムーバブル microSD カード、または /data/media にある仮想「SDCard」パーティション。 後者は、実際的に言えば、 実際は内部ストレージですですが、Google の命名規則では、ユーザーがこのデータにアクセスできる (コンピューターに接続されている場合など) という事実により区別されています。
現在、「外部ストレージ」というときは、 2つのことのどちらか1つ: 実際のリムーバブル microSD カード、または /data/media にある仮想「SDCard」パーティション。
Android の仮想ファイルシステムの歴史
「sdcard」が仮想ファイルシステムとして扱われるようになったということは、Google が望むあらゆるファイルシステムとしてフォーマットできることを意味しました。 Nexus S と Android 2.3 以降、Google は「sdcard」を VFAT (仮想 FAT) としてフォーマットすることを選択しました。 VFAT をマウントすると、ほぼすべてのコンピュータが携帯電話に保存されているデータにアクセスできるようになるため、この動きは当時としては理にかなっていました。 ただし、この初期実装には 2 つの大きな問題がありました。
1 つ目は主にエンド ユーザー (あなた) に関するものです。 デバイスをコンピュータに接続するには、USB 大容量ストレージ モードを使用してデータを転送します。 ただし、これには、コンピュータがデータにアクセスする前に、Android デバイスが仮想パーティションをアンマウントする必要がありました。 ユーザーが接続中にデバイスを使用したい場合、多くの機能が使用不可として表示されます。
の メディア転送プロトコルの導入 (MTP) はこの最初の問題を解決しました。 接続すると、コンピュータはデバイスを「メディア ストレージ」デバイスとして認識します。 携帯電話からファイルのリストを要求すると、MTP はコンピュータがデバイスからダウンロードできるファイルのリストを返します。 ファイルの削除が要求されると、MTP は要求されたファイルをストレージから削除するコマンドを送信します。 実際に「sdcard」をマウントする USB 大容量ストレージ モードとは異なり、MTP を使用すると、ユーザーは接続したままデバイスを使用し続けることができます。 さらに、Android スマートフォンに存在するファイル システムは、コンピューターがデバイス上のファイルを認識するためには重要ではなくなりました。
第 2 に、VFAT は Google が必要とする強力な権限管理を提供していないという事実がありました。 初期の頃、多くのアプリケーション開発者は、ファイルを保存する場所について統一された認識がなく、「SD カード」をアプリケーション データの捨て場として扱っていました。 多くのアプリケーションは、単純にアプリ名でフォルダーを作成し、そこにファイルを保存します。
当時存在していたほぼすべてのアプリケーションには、 WRITE_EXTERNAL_STORAGE アプリケーション ファイルを外部ストレージに書き込む権限。 しかし、さらに問題だったのは、ほぼすべてのアプリケーションで READ_EXTERNAL_STORAGE 許可 - 自分のデータ ファイルを読み取るためだけです。 これは、アプリケーションが外部ストレージのどこにでも保存されているデータに簡単にアクセスできることを意味します。 そして、多くのアプリでは均等化する必要があるため、そのような許可はユーザーによって付与されることがよくありました。 関数。
Google はこれを明らかに問題視しました。 権限管理の背後にある全体的な考え方は、アプリがアクセスできるものとできないものを分離することです。 ほぼすべてのアプリに機密性の高いユーザー データへの読み取りアクセスが許可されている場合、その権限は無意味です。 したがって、Google は新しいアプローチが必要であると判断しました。 そこでFUSEが登場します。
ユーザー空間のファイルシステム (FUSE)
Android 4.4 以降、Google は仮想「sdcard」パーティションを VFAT としてマウントしないことを決定しました。 代わりに、Google は FUSE を使用して、「sdcard」仮想パーティション上で FAT32 をエミュレートし始めました。 SDカードプログラム呼び出しで FUSE による FAT-on-sdcard スタイルのディレクトリ権限をエミュレートする、アプリケーションが外部ストレージに保存されているデータにアクセスし始める可能性があります。 許可を必要とせずに. 実際、API レベル 19 以降、次の場所にあるファイルにアクセスするために READ_EXTERNAL_STORAGE は必要なくなりました。 外部ストレージ上 - FUSE デーモンによって作成されたデータ フォルダーがアプリのパッケージ名と一致する場合。 FUSE が処理します 外部ストレージ上のファイルの所有者、グループ、モードを合成する アプリケーションがインストールされているとき。
FUSE は、特権のないユーザーでも仮想ファイルシステムを作成できるため、カーネル内モジュールとは異なります。 Google が FUSE を実装した理由は非常に単純です。Google が望んでいたことが実現され、すでに実現されていました。 よく理解され文書化されている Linuxの世界では。 引用するには Googleの開発者がこの件について語る:
「FUSE は優れた安定した API であるため、カーネルのバージョン間を移行する際に必要なメンテナンス作業は基本的にゼロです。 カーネル内ソリューションに移行した場合、安定したカーネル バージョンごとに一連のパッチを維持するためにサインアップすることになります。」 - Google のソフトウェア エンジニア、Jeff Sharkey 氏
しかし、FUSE のオーバーヘッドが他の問題の中でも特にパフォーマンスに影響を及ぼしていることがかなり明らかになってきました。 この件に関して私が話をした開発者、Michal Kowalczyk 氏は、 素晴らしいブログ投稿を書きました 1 年以上前に、FUSE に関する現在の問題について詳しく説明しました。 より技術的な詳細は彼のブログで読むことができますが、私は彼の発見を(彼の許可を得て)より一般的な言葉で説明します。
ヒューズの問題
Android では、「sdcard」ユーザースペース デーモンは FUSE を利用して、起動時にエミュレートされた外部ストレージ ディレクトリに /dev/fuse をマウントします。 その後、sdcard デーモンは FUSE デバイスをポーリングして、カーネルからの保留中のメッセージがないかどうかを確認します。 ポッドキャストを聞いた方は、FUSE が I/O 操作中にオーバーヘッドを引き起こすと Lemarchand 氏が言及しているのを聞いたことがあるかもしれません。これが本質的に何が起こるかです。
現実の世界では、このパフォーマンスの低下は次のような影響を及ぼします。 どれでも 外部ストレージに保存されたファイル。
問題 #1 - I/O オーバーヘッド
「test.txt」という名前の単純なテキスト ファイルを作成し、それを /sdcard/test.txt に保存するとします ( 念のために言っておきますが、実際には /data/media/0/test.txt は、現在のユーザーがプライマリ ユーザーであると仮定しています。 デバイス)。 このファイルを読み取り (コマンド cat) たい場合、システムはオープン、読み取り、クローズの 3 つのコマンドを発行することが予想されます。 実際、Kowalczyk 氏が以下を使用して実証しているように、 跡、それが起こります:
ただし、ファイルは sdcard デーモンによって管理される外部ストレージに配置されているため、実行する必要がある追加操作が多数あります。 Kowalczyk 氏によると、基本的に 8 つの追加手順が必要です。 これら 3 つの個別のコマンドはそれぞれ:
- ユーザー空間アプリケーションは、カーネル内の FUSE ドライバーによって処理されるシステム コールを発行します (最初の strace 出力に表示されます)。
- カーネルの FUSE ドライバーがユーザースペース デーモン (SDカード) に新しいリクエストを通知します
- ユーザースペースデーモンが /dev/fuse を読み取ります
- ユーザースペースデーモンはコマンドを解析し、ファイル操作を認識します(例: 開ける)
- ユーザー空間デーモンが実際のファイルシステム (EXT4) にシステムコールを発行します。
- カーネルは物理的なデータ アクセスを処理し、データをユーザー空間に送り返します。
- ユーザースペースはデータを変更し (または変更せず)、それを /dev/fuse 経由でカーネルに再度渡します。
- カーネルは元のシステム コールを完了し、データを実際のユーザー空間アプリケーション (この例では cat) に移動します。
これは次のようです たくさん 単一の I/O コマンドを実行するだけでオーバーヘッドが軽減されます。 そして、あなたは正しいでしょう。 これを実証するために、Kowalczyk 氏は 2 つの異なる I/O テストを試みました。1 つは大きなファイルのコピー、もう 1 つは多数の小さなファイルのコピーです。 彼は、これらの操作を処理する FUSE (FAT32 としてマウントされた仮想パーティション上) の速度と、 カーネル (EXT4 としてフォーマットされたデータ パーティション上) を調べたところ、FUSE が実際に重要な貢献をしていることがわかりました。 オーバーヘッド。
最初のテストでは、両方のテスト条件で 725MB のファイルをコピーしました。 彼は、FUSE 実装が大きなファイルを転送することを発見しました。 17% 遅くなる.
2 回目のテストでは、それぞれのサイズが 5KB の 10,000 個のファイルをコピーしました。 このシナリオでは、FUSE の実装は終了しました 40秒遅くなる 基本的に 50MB 相当のデータをコピーします。
現実の世界では、このパフォーマンスの低下は次のような影響を及ぼします。 どれでも 外部ストレージに保存されたファイル。 これは、/sdcard に大きなファイルを保存するマップ アプリ、大量の音楽ファイルを保存する音楽アプリ、カメラ アプリや写真などのアプリを意味します。 外部ストレージが関与する実行中の I/O 操作は、FUSE のオーバーヘッドの影響を受けます。 ただし、FUSE の問題は I/O オーバーヘッドだけではありません。
問題 #2 - 二重キャッシュ
データのキャッシュは、データ アクセスのパフォーマンスを向上させる上で重要です。 重要なデータをメモリに保存することで、Linux カーネルは必要なときにそのデータをすぐに呼び出すことができます。 ただし、FUSE の実装方法により、Android は必要な 2 倍の量のキャッシュを保存します。
Kowalczyk 氏が実証しているように、10 MB のファイルは正確に 10 MB としてキャッシュに保存されることが期待されますが、代わりにキャッシュ サイズまで増加します。 約20MBの差です。 Linux カーネル ストアはページ キャッシュを使用してデータを保存するため、RAM が少ないデバイスでは問題が発生します。 メモリ。 Kowalczyk 氏は、次のアプローチを使用してこの二重キャッシュの問題をテストしました。
- 既知のサイズのファイルを作成します (テスト用、10MB)
- /sdcard にコピーします
- ページキャッシュを削除する
- ページキャッシュの使用状況のスナップショットを取得します。
- テストファイルを読む
- ページ キャッシュの使用状況の別のスナップショットを取得します
彼が発見したのは、テストの前に、カーネルによってページ キャッシュに 241MB が使用されていたということでした。 テスト ファイルを読み取ると、ページ キャッシュに 251MB が使用されることが予想されました。 代わりに、カーネルが使用していることを発見しました。 263MB ページキャッシュの場合 - 概要 予想の2倍. これが発生する理由は、最初に I/O 呼び出し (FUSE) を発行したユーザー アプリケーションによってデータがキャッシュされ、次に SDcard デーモン (EXT4 FS) によってキャッシュされるためです。
問題 #3 - FAT32 の実装が不完全
Android コミュニティではあまり知られていない、FAT32 をエミュレートする FUSE の使用に起因する問題がさらに 2 つあります。
1つ目は、 不正なタイムスタンプ. ファイル (写真など) を転送したときにタイムスタンプが間違っていることに気付いた場合、それは Android の FUSE 実装が原因です。 この問題には、 のために存在した 年. より具体的に言うと、この問題には次のような問題が含まれます。 utime() ファイルのアクセス時間と変更時間を変更できるようにするシステム コール。 残念ながら、標準ユーザーとして sdcard デーモンに対して行われた呼び出しには、このシステム コールを実行するための適切な権限がありません。 これには回避策がありますが、次のことが必要です。 ルートアクセス権がある.
ファイル (写真など) を転送したときにタイムスタンプが間違っていることに気付いた場合、それは Android の FUSE 実装が原因です。
次の問題は、次のようなものを使用している企業にとってより懸念される問題です。 スマートSDカード. FUSE が登場する前は、アプリ メーカーは O_DIRECT フラグ カードに組み込まれたマイクロコントローラーと通信するため。 FUSE を使用すると、開発者はファイルのキャッシュされたバージョンにのみアクセスでき、マイクロコントローラーによって送信されたコマンドを確認することはできません。 これは、付加価値のある microSD カードと通信する一部の企業/政府/銀行アプリにとって問題となります。
SDCardFS の FUSE のダンプ
一部の OEMS はこれらの問題を早い段階で認識し、FUSE に代わるカーネル内ソリューションを探し始めました。 たとえばサムスンが開発したのは、 SDカードFS これはWrapFSに基づいています。 このカーネル内ソリューションは、FUSE と同じように FAT32 をエミュレートしますが、I/O オーバーヘッド、二重キャッシュ、および上で述べたその他の問題を回避します。 (はい、その点をもう一度言いますが、 Google が現在実装しているこのソリューションは、Samsung の研究に基づいています).
Google 自体も、FUSE に関連する欠点を最終的に認識しました。そのため、Samsung が開発したカーネル内 FAT32 エミュレーション レイヤーに移行し始めています。 同社は、記事でも述べたように、 Android 開発者のバックステージ ポッドキャストでは、カーネルの次期バージョンですべてのデバイスで SDCardFS を利用できるようにすることに取り組んでいます。 現在、彼らの進捗状況を確認できます AOSPでの作業.
として Google開発者は以前説明しましたカーネル内ソリューションの実装における最大の課題は、パッケージ名をどのようにマップするかです。 パッケージが外部ストレージ内の独自のデータにアクセスするために必要なアプリケーション ID。 権限。 しかし、この発言は 1 年前に行われたもので、チームは SDCardFS を「次の目玉」と呼ぶ段階に達しました。 彼らはすでにそれを確認しています 恐ろしいタイムスタンプエラー FUSE から離れたおかげでこの問題は修正されたため、FUSE の放棄によってもたらされるすべての変更を確認できることを楽しみにしています。
事実確認の誤解
この記事をここまで読んだのであれば、ここまでの内容をよく理解していただいたことに敬意を表します。 この記事を書くときに私自身が抱いたいくつかの疑問を明確にしたいと思いました。
- SDCardFS には 実際のSDカードとは関係ありません. /sdcard の I/O アクセスを処理するため、そのように名付けられています。 覚えているかもしれませんが、/sdcard はデバイスの「外部」ストレージ (アプリがメディアを保存する場所) を指す古いラベルです。
- SDCardFS は 従来のファイルシステムではない FAT32、EXT4、F2FS など。 これは、下位のエミュレートされたファイル システム (この場合、/sdcard 上の FAT32) にコマンドを渡すスタッカブル ラッパー ファイル システムです。
- MTPに関しては何も変わりません. (Google がより良いプロトコルを決定するまで) 引き続き MTP を使用してコンピュータとの間でファイルを転送します。 ただし、少なくともタイムスタンプ エラーは修正されます。
- 前に述べたように、Google が「外部ストレージ」に言及するとき、彼らは (あらゆる意味で) 外部ストレージについて話しているかのどちらかです。 目的) 内部 /sdcard 仮想 FAT32 パーティション、または実際の物理的なリムーバブル microSD について話しています。 カード。 用語は混乱を招きますが、それが私たちが印象に残っていることです。
結論
FUSE から離れ、カーネル内 FAT32 エミュレーション レイヤー (SDCardFS) を実装することで、Google は 大幅な I/O オーバーヘッド、二重キャッシュの排除、FUSE のエミュレーションに関連するいくつかのあいまいな問題の解決 FAT32。
これらの変更はカーネルに対して行われるため、Android の主要な新しいバージョンを追加しなくてもロールアウトできます。 一部のユーザーは、これらの変更が Android 8 で正式に実装されることを期待していますが、その可能性はあります。 Pixel デバイスでの将来の OTA に備えて、Google が取り組んでいる Linux カーネル バージョン 4.1 を導入します。 の上。
一部の人にとって、SDCardFS は新しい概念ではありません。 実際、サムスンのデバイスは何年も前からこの機能を利用しています (結局、サムスンが開発したのは彼らです)。 昨年 AOSP に SDCardFS が導入されて以来、一部のカスタム ROM およびカーネル開発者は、SDCardFS を自分たちの作業に実装することを選択しています。 CyanogenMOD は一時、実装を検討しましたが、ユーザーが写真に問題を抱えたため、それを撤回しました。 しかし、うまくいけば、Google がこのプロジェクトを主導することで、将来のすべてのデバイスを使用する Android ユーザーが、FUSE の廃止によって導入された改善点を利用できるようになります。