面白い? 猫へようこそ!
ラッパーの実装を開始する前に、FUSEとは何かを理解する必要があります。
FUSE(ユーザー空間のファイルシステム)-ユーザー空間のファイルシステム。ユーザーは特権なしでカーネルコードを書き直すことなく、独自のファイルシステムを作成できます。 これは、ユーザー空間でファイルシステムコードを実行することで実現されますが、FUSEモジュールは現在のカーネルインターフェイスのブリッジのみを提供します。 FUSEは、バージョン2.6.14のメインLinuxコードツリーに公式に含まれていました。
つまり 実際、いくつかのメソッドを実装することにより、独自のファイルシステムを簡単に作成できます( 単純なFSの例 )。 これには数百万のアプリケーションがあります。たとえば、DropboxまたはGitHubのバックエンドでファイルシステムをすばやく作成できます。
または、そのような場合を考えてみましょう。すべてのユーザーファイルがデータベースに格納されているビジネスアプリケーションがありますが、クライアントは突然、すべてのファイルが置かれているサーバー上のディレクトリに直接アクセスする必要がありました。 もちろん、データベースとFSでファイルを複製することは最善の解決策ではありません。ここでは、仮想ファイルシステムが役立ちます。 FUSEラッパーを記述するだけで、ファイルにアクセスするとデータベース内のファイルにアクセスします。
Javaおよびネイティブコード
素晴らしいですが、FUSEの実装は「include the header file <fuse.h>」で始まり、ビジネスアプリケーションはJavaで記述されています。 明らかに、ネイティブコードと何らかの形で対話する必要があります。
ジュニ
標準ツールはJNIですが、特にFUSEを実装するにはネイティブコードからJavaクラスへのコールバックを作成する必要があることを考えると、プロジェクトに多くの複雑さが導入されます。 はい、そして「1回書き込み」は実際に苦しみますが、FUSEの場合、これは私たちにとってそれほど重要ではありません。
実際、JNIでFUSEのラッパーを実装するプロジェクトを見つけようとすると、長い間サポートされておらず、APIカーブを提供するいくつかのプロジェクトを見つけることができます。
Jna
別のオプションはJNAライブラリです。 JNA(Java Native Access)を使用すると、JNIを使用せずにネイティブコードに非常に簡単にアクセスでき、Javaコードの記述に制限されます。 すべてが非常に単純です。ネイティブコードに対応するインターフェイスを宣言し、「Native.loadLibrary」を介して実装を取得し、それだけで使用します。 JNAの別のプラスは、最も詳細なドキュメントです。 このプロジェクトは現在も活発に開発されています。
さらに、JNAでラッパーを実装するFUSEの優れたプロジェクトがすでにあります。
ただし、JNAには特定のパフォーマンスの問題があります。 JNAはリフレクションに基づいており、すべての構造をJavaオブジェクトに変換するネイティブコードからの移行は非常に高価です。 ネイティブ呼び出しがまれな場合、これはあまり目立ちませんが、ファイルシステムの場合はそうではありません。 fuse-jnaを高速化する唯一の方法は、大きなチャンクでファイルを読み取ろうとすることですが、これは常に機能するとは限りません。 たとえば、クライアントコードにアクセスできない場合、またはすべてのファイルが小さい場合-多数のテキストファイル。
明らかに、JNIのパフォーマンスとJNAの利便性を組み合わせたライブラリが表示されるはずでした。
Jnr
これが、JNR(Java Native Runtime)の出番です。 JNRはJNAと同様にlibffiに基づいていますが、リフレクションの代わりにバイトコード生成が使用されるため、パフォーマンスが大幅に向上します。
JNRに関する多くの情報はありません。最も詳細なものは、JVMLS 2013でのCharles Nutterのプレゼンテーションです ( プレゼンテーション )。 ただし、JNRはすでにかなり大きなエコシステムであり、JRubyで積極的に使用されています。 unix-sockets、posix-apiなどのパーツの多くは、サードパーティのプロジェクトでも積極的に使用されています。
そのJNRは、Java 10を対象とするJEP 191 -Foreign Function Interfaceの開発の基礎です。
JNAとは異なり、JNRにはドキュメントがありません。質問に対するすべての回答はソースコードで見つける必要があり、これが小さなガイドを書く主な理由でした。
Java Native Runtimeのコード作成機能
関数バインディング
単純なlibcバインディングは次のようになります。
import jnr.ffi.*; import jnr.ffi.types.pid_t; /** * Gets the process ID of the current process, and that of its parent. */ public class Getpid { public interface LibC { public @pid_t long getpid(); public @pid_t long getppid(); } public static void main(String[] args) { LibC libc = LibraryLoader.create(LibC.class).load("c"); System.out.println("pid=" + libc.getpid() + " parent pid=" + libc.getppid()); } }
LibraryLoaderを使用して、転送されたインターフェイスに対応するライブラリを名前でロードします。
FUSEの場合、すべてのコールバックを含むFuseOperations構造が渡されるfuse_main_realメソッドとのインターフェースが必要です。
public interface LibFuse { int fuse_main_real(int argc, String argv[], FuseOperations op, int op_size, Pointer user_data); }
構造体の実装
多くの場合、特定のアドレスにある構造(fuse_bufvec構造など)を使用する必要があります。
struct fuse_bufvec { size_t count; size_t idx; size_t off; struct fuse_buf buf[1]; };
JNRで実装するには、jnr.ffi.Structから継承する必要があります。
import jnr.ffi.*; public class FuseBufvec extends Struct { public FuseBufvec(jnr.ffi.Runtime runtime) { super(runtime); } public final size_t count = new size_t(); public final size_t idx = new size_t(); public final size_t off = new size_t(); public final FuseBuf buf = inner(new FuseBuf(getRuntime())); }
各構造内には、メモリに配置されるポインタが格納されます。 構造体を操作するためのAPIのほとんどは、Structの静的メソッドを見るとわかります。
size_tはStructの内部クラスであり、それを作成すると、各フィールドについて、メモリ内のこのフィールドのオフセットが記憶されます。これにより、各フィールドはメモリ内のオフセットによって認識されます。 多くの内部クラス(Signed64、Unsigned32、time_tなど)が既に実装されているため、いつでも独自のクラスを実装できます。
コールバック
struct fuse_operations { int (*getattr) (const char *, struct stat *); }
国鉄でコールバックを操作するための注釈があります
@デリゲート
public interface GetAttrCallback { @Delegate int getattr(String path, Pointer stbuf); } public class FuseOperations extends Struct { public FuseOperations(Runtime runtime) { super(runtime); } public final Func<GetAttrCallback> getattr = func(GetAttrCallback.class); }
次に、たとえばgetattrフィールドに目的のコールバック実装を設定できます。
fuseOperations.getattr.set((path, stbuf) -> 0);
列挙型
いくつかの非自明なことのうち、列挙型のラッパーに注目する価値もあります。これは、jnr.ffi.util.EnumMapper.IntegerEnumから列挙型を継承し、intValueメソッドを実装する必要があるためです。
enum fuse_buf_flags { FUSE_BUF_IS_FD = (1 << 1), FUSE_BUF_FD_SEEK = (1 << 2), FUSE_BUF_FD_RETRY = (1 << 3), };
public enum FuseBufFlags implements EnumMapper.IntegerEnum { FUSE_BUF_IS_FD(1 << 1), FUSE_BUF_FD_SEEK(1 << 2), FUSE_BUF_FD_RETRY(1 << 3); private final int value; FuseBufFlags(int value) { this.value = value; } @Override public int intValue() { return value; } }
メモリを操作する
- メモリを直接操作するために、生のjnr.ffi.Pointerポインターのラッパーがあります
- jnr.ffi.Memoryを使用してメモリを割り当てることができます
- JNR APIの開始点はjnr.ffi.Runtimeです。
この知識は、単純なクロスプラットフォームラッパーをネイティブライブラリに簡単に実装するのに十分です。
jnr-fuse
これはまさに 、 jnr-fuseプロジェクトでFUSEを使用して行ったことです。 最初はfuse-jnaライブラリが使用されていましたが、FSの実装ではボットネットでした。 APIを開発するとき、fuse-jnaとネイティブ実装(<fuse.h>)との互換性を可能な限り維持しようとしました。
ユーザー空間にファイルシステムを実装するには、ru.serce.jnrfuse.FuseStubFSを継承し、必要なメソッドを実装する必要があります。 Fuse_operationsには多くのメソッドが含まれていますが、機能するFSを取得するには、いくつかの基本的なメソッドを実装するだけで十分です。
これは非常に簡単です。FSの動作例をいくつか示します。
Linuxは現在サポートされています(x86およびx64)。
ライブラリはjcenterにあり、近い将来、Maven Centralにミラーを追加します。
グラドル
repositories { jcenter() } dependencies { compile 'com.github.serceman:jnr-fuse:0.1' }
メイヴン
<repositories> <repository> <id>central</id> <name>bintray</name> <url>http://jcenter.bintray.com</url> </repository> </repositories> <dependencies> <dependency> <groupId>com.github.serceman</groupId> <artifactId>jnr-fuse</artifactId> <version>0.1</version> </dependency> </dependencies>
fuse-jnaとjnr-fuseのパフォーマンスを比較する
私の場合、FSは読み取り専用であり、特にスループットに興味がありました。 パフォーマンスはFSの実装に大きく依存するため、突然fuse-jnaを使用する場合、jnr-fuseを簡単に接続し、負荷プロファイルに基づいてテストを作成し、違いを確認できます。 (とにかくこのテストはあなたにとって有用です、私たちは皆、パフォーマンスのために運転するのが大好きですか?)
違いの順序を示すために、最小限の変更でMemoryFSの実装をfuse-jnaからfuse-jnrに移行し、fio読み取りテストを実行しました。 テストには、 fioフレームワークを使用しました。これについては、少し前にハブに関する良い記事がありました。
テスト構成
[readtest]
ブロックサイズ= 4k
ディレクトリ= / tmp / mnt /
rw = randread
ダイレクト= 1
バッファリング= 0
ioengine = libaio
time_based = 60
サイズ= 16M
ランタイム= 60
ブロックサイズ= 4k
ディレクトリ= / tmp / mnt /
rw = randread
ダイレクト= 1
バッファリング= 0
ioengine = libaio
time_based = 60
サイズ= 16M
ランタイム= 60
結果fuse-jna
serce @ SerCe-FastLinux:〜/ git / jnr-fuse / bench $ fio read.ini
readtest:(g = 0):rw = randread、bs = 4K-4K / 4K-4K / 4K-4K、ioengine = libaio、iodepth = 1
fio-2.1.3
1つのプロセスを開始
readtest:IOファイルのレイアウト(s)(1ファイル(s)/ 16MB)
ジョブ:1(f = 1):[r] [100.0%完了] [24492KB / 0KB / 0KB / s] [6123/0/0 iops] [eta 00m:00s]
readtest:(groupid = 0、jobs = 1):err = 0:pid = 10442:Sun Jun 21 14:49:13 2015
読み取り:io = 1580.2MB、bw = 26967KB / s、iops = 6741、runt = 60000msec
スラット(usec):最小= 46、最大= 29997、平均= 146.55、stdev = 327.68
clat(usec):最小= 0、最大= 69、平均= 0.47、標準偏差= 0.66
lat(usec):最小= 47、最大= 30002、平均= 147.26、stdev = 327.88
clatパーセンタイル(usec):
| 1.00th = [0]、5.00th = [0]、10.00th = [0]、20.00th = [0]、
| 30.00th = [0]、40.00th = [0]、50.00th = [0]、60.00th = [1]、
| 70.00th = [1]、80.00th = [1]、90.00th = [1]、95.00th = [1]、
| 99.00番目= [2]、99.50番目= [2]、99.90番目= [3]、99.95番目= [12]、
| 99.99 = [14]
bw(KB / s):最小= 17680、最大= 32606、per = 96.09%、平均= 25913.26、stdev = 3156.20
lat(usec):2 = 97.95%、4 = 1.96%、10 = 0.02%、20 = 0.06%、50 = 0.01%
lat(usec):100 = 0.01%
cpu:usr = 1.98%、sys = 5.94%、ctx = 405302、majf = 0、minf = 28
IO深度:1 = 100.0%、2 = 0.0%、4 = 0.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、> = 64 = 0.0%
送信:0 = 0.0%、4 = 100.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、64 = 0.0%、> = 64 = 0.0%
完了:0 = 0.0%、4 = 100.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、64 = 0.0%、> = 64 = 0.0%
発行済み:合計= r = 404511 / w = 0 / d = 0、short = r = 0 / w = 0 / d = 0
実行ステータスグループ0(すべてのジョブ):
読み取り:io = 1580.2MB、aggrb = 26967KB / s、minb = 26967KB / s、maxb = 26967KB / s、mint = 60000msec、maxt = 60000msec
readtest:(g = 0):rw = randread、bs = 4K-4K / 4K-4K / 4K-4K、ioengine = libaio、iodepth = 1
fio-2.1.3
1つのプロセスを開始
readtest:IOファイルのレイアウト(s)(1ファイル(s)/ 16MB)
ジョブ:1(f = 1):[r] [100.0%完了] [24492KB / 0KB / 0KB / s] [6123/0/0 iops] [eta 00m:00s]
readtest:(groupid = 0、jobs = 1):err = 0:pid = 10442:Sun Jun 21 14:49:13 2015
読み取り:io = 1580.2MB、bw = 26967KB / s、iops = 6741、runt = 60000msec
スラット(usec):最小= 46、最大= 29997、平均= 146.55、stdev = 327.68
clat(usec):最小= 0、最大= 69、平均= 0.47、標準偏差= 0.66
lat(usec):最小= 47、最大= 30002、平均= 147.26、stdev = 327.88
clatパーセンタイル(usec):
| 1.00th = [0]、5.00th = [0]、10.00th = [0]、20.00th = [0]、
| 30.00th = [0]、40.00th = [0]、50.00th = [0]、60.00th = [1]、
| 70.00th = [1]、80.00th = [1]、90.00th = [1]、95.00th = [1]、
| 99.00番目= [2]、99.50番目= [2]、99.90番目= [3]、99.95番目= [12]、
| 99.99 = [14]
bw(KB / s):最小= 17680、最大= 32606、per = 96.09%、平均= 25913.26、stdev = 3156.20
lat(usec):2 = 97.95%、4 = 1.96%、10 = 0.02%、20 = 0.06%、50 = 0.01%
lat(usec):100 = 0.01%
cpu:usr = 1.98%、sys = 5.94%、ctx = 405302、majf = 0、minf = 28
IO深度:1 = 100.0%、2 = 0.0%、4 = 0.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、> = 64 = 0.0%
送信:0 = 0.0%、4 = 100.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、64 = 0.0%、> = 64 = 0.0%
完了:0 = 0.0%、4 = 100.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、64 = 0.0%、> = 64 = 0.0%
発行済み:合計= r = 404511 / w = 0 / d = 0、short = r = 0 / w = 0 / d = 0
実行ステータスグループ0(すべてのジョブ):
読み取り:io = 1580.2MB、aggrb = 26967KB / s、minb = 26967KB / s、maxb = 26967KB / s、mint = 60000msec、maxt = 60000msec
JNRヒューズの結果
serce @ SerCe-FastLinux:〜/ git / jnr-fuse / bench $ fio read.ini
readtest:(g = 0):rw = randread、bs = 4K-4K / 4K-4K / 4K-4K、ioengine = libaio、iodepth = 1
fio-2.1.3
1つのプロセスを開始
readtest:IOファイルのレイアウト(s)(1ファイル(s)/ 16MB)
ジョブ:1(f = 1):[r] [100.0%完了] [208.5MB / 0KB / 0KB / s] [53.4K / 0/0 iops] [eta 00m:00s]
readtest:(groupid = 0、jobs = 1):err = 0:pid = 10153:Sun Jun 21 14:45:17 2015
読み取り:io = 13826MB、bw = 235955KB / s、iops = 58988、runt = 60002msec
スラット(usec):最小= 6、最大= 23671、平均= 15.80、標準偏差= 19.97
clat(usec):最小= 0、最大= 1028、平均= 0.37、標準偏差= 0.78
lat(usec):最小= 7、最大= 23688、平均= 16.29、stdev = 20.03
clatパーセンタイル(usec):
| 1.00th = [0]、5.00th = [0]、10.00th = [0]、20.00th = [0]、
| 30.00th = [0]、40.00th = [0]、50.00th = [0]、60.00th = [0]、
| 70.00th = [1]、80.00th = [1]、90.00th = [1]、95.00th = [1]、
| 99.00th = [1]、99.50th = [1]、99.90th = [2]、99.95th = [2]、
| 99.99 = [10]
lat(usec):2 = 99.88%、4 = 0.10%、10 = 0.01%、20 = 0.01%、50 = 0.01%
lat(usec):100 = 0.01%、250 = 0.01%
lat(ミリ秒):2 = 0.01%
cpu:usr = 9.33%、sys = 34.01%、ctx = 3543137、majf = 0、minf = 28
IO深度:1 = 100.0%、2 = 0.0%、4 = 0.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、> = 64 = 0.0%
送信:0 = 0.0%、4 = 100.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、64 = 0.0%、> = 64 = 0.0%
完了:0 = 0.0%、4 = 100.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、64 = 0.0%、> = 64 = 0.0%
発行済み:合計= r = 3539449 / w = 0 / d = 0、short = r = 0 / w = 0 / d = 0
実行ステータスグループ0(すべてのジョブ):
読み取り:io = 13826MB、aggrb = 235955KB / s、minb = 235955KB / s、maxb = 235955KB / s、mint = 60002msec、maxt = 60002msec
readtest:(g = 0):rw = randread、bs = 4K-4K / 4K-4K / 4K-4K、ioengine = libaio、iodepth = 1
fio-2.1.3
1つのプロセスを開始
readtest:IOファイルのレイアウト(s)(1ファイル(s)/ 16MB)
ジョブ:1(f = 1):[r] [100.0%完了] [208.5MB / 0KB / 0KB / s] [53.4K / 0/0 iops] [eta 00m:00s]
readtest:(groupid = 0、jobs = 1):err = 0:pid = 10153:Sun Jun 21 14:45:17 2015
読み取り:io = 13826MB、bw = 235955KB / s、iops = 58988、runt = 60002msec
スラット(usec):最小= 6、最大= 23671、平均= 15.80、標準偏差= 19.97
clat(usec):最小= 0、最大= 1028、平均= 0.37、標準偏差= 0.78
lat(usec):最小= 7、最大= 23688、平均= 16.29、stdev = 20.03
clatパーセンタイル(usec):
| 1.00th = [0]、5.00th = [0]、10.00th = [0]、20.00th = [0]、
| 30.00th = [0]、40.00th = [0]、50.00th = [0]、60.00th = [0]、
| 70.00th = [1]、80.00th = [1]、90.00th = [1]、95.00th = [1]、
| 99.00th = [1]、99.50th = [1]、99.90th = [2]、99.95th = [2]、
| 99.99 = [10]
lat(usec):2 = 99.88%、4 = 0.10%、10 = 0.01%、20 = 0.01%、50 = 0.01%
lat(usec):100 = 0.01%、250 = 0.01%
lat(ミリ秒):2 = 0.01%
cpu:usr = 9.33%、sys = 34.01%、ctx = 3543137、majf = 0、minf = 28
IO深度:1 = 100.0%、2 = 0.0%、4 = 0.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、> = 64 = 0.0%
送信:0 = 0.0%、4 = 100.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、64 = 0.0%、> = 64 = 0.0%
完了:0 = 0.0%、4 = 100.0%、8 = 0.0%、16 = 0.0%、32 = 0.0%、64 = 0.0%、> = 64 = 0.0%
発行済み:合計= r = 3539449 / w = 0 / d = 0、short = r = 0 / w = 0 / d = 0
実行ステータスグループ0(すべてのジョブ):
読み取り:io = 13826MB、aggrb = 235955KB / s、minb = 235955KB / s、maxb = 235955KB / s、mint = 60002msec、maxt = 60002msec
このテストでは、fuse-jnaとfuse-jnrのファイルの読み取り速度の違いのみを示していますが、それに基づいて、JNAとJNRの速度の違いを把握できます。 希望する人は、すべての機能を考慮して、 JMHを使用してネイティブコールのより詳細なテストをいつでも作成できます。私自身は、これらのテストに興味があります。
Charles Nutterによるプレゼンテーションのように、JNRとJNAのスループットとレイテンシの両方の差は、約10倍になると予想されます。
参照資料
- SourceForgeのヒューズ
- githubのJNR
- Charles Nutterによる国鉄に関するプレゼンテーション
- Jep 191
- Javaの hello-fuse / Cのhello-fuse
jnr-fuseプロジェクトはGitHubでホストされています 。 プロジェクトを改善するための星、プールのリクエスト、提案に満足しています。
また、JNRとjnr-fuseに関するすべての質問にお答えします。