参考文献
FUSE – George’s Blog (georgesims21.github.io)
目次
アーキ
The fuse kernel holds 5 different queues, with each queue providing a specific purpose:
- Interrupts (highest priority) – For all interrupt requests
- Forgets – For all forget requests
- Pending – Latency sensitive requests (related to metadata)
- Background – All other requests (read/write etc)
- Processing – The requests that are currently being processed by the daemon
FUSEのコマンド処理
コマンドの流れは以下のドキュメントに係れている。
FUSE — The Linux Kernel documentation
| "rm /mnt/fuse/file" | FUSE filesystem daemon
| |
| | >sys_read()
| | >fuse_dev_read()
| | >request_wait()
| | [sleep on fc->waitq]
| |
| >sys_unlink() |
| >fuse_unlink() |
| [get request from |
| fc->unused_list] |
| >request_send() |
| [queue req on fc->pending] |
| [wake up fc->waitq] | [woken up]
| >request_wait_answer() |
| [sleep on req->waitq] |
| | <request_wait()
| | [remove req from fc->pending]
| | [copy req to read buffer]
| | [add req to fc->processing]
| | <fuse_dev_read()
| | <sys_read()
| |
| | [perform unlink]
| |
| | >sys_write()
| | >fuse_dev_write()
| | [look up req in fc->processing]
| | [remove from fc->processing]
| | [copy write buffer to req]
| [woken up] | [wake up req->waitq]
| | <fuse_dev_write()
| | <sys_write()
| <request_wait_answer() |
| <request_send() |
| [add request to |
| fc->unused_list] |
| <fuse_unlink() |
| <sys_unlink() |
以下ソース解析結果を示す。なお、ソース解析では生成AIをつかっているため、間違いが含まれる可能性がある。
カーネル→FUSEデーモン
カーネルはfuse_dev_write 関数より/dev/fuseへの書き込み操作を通じてFUSEデーモンに要求を出す。
1. 要求の構築: カーネルは、ファイルシステム操作に関連するデータを含む fuse_req 構造体を構築する。以下は、fuse_req 構造体を初期化し、要求を設定する一般的なコードの例。
struct fuse_req *req;
req = fuse_request_alloc();
if (!req)
return -ENOMEM;
req->in.h.opcode = FUSE_READ; // 要求の種類を設定
req->in.h.nodeid = inode->i_ino; // inode番号を設定
req->in.numargs = 1; // 引数の数を設定
/* リクエストにデータを設定 */
req->in.args[0].size = size;
req->in.args[0].value = buffer;
/* FUSEデーモンに要求を送信 */
fuse_request_send(fc, req);
2. データの準備: fuse_req には、操作の種類、対象のinode、必要なデータなどが含まれる。
3. データの書き込み: __fuse_request_send 関数を通じて、これらの要求データをFUSEデバイスファイルに書き込む。この書き込み操作により、データがFUSEデーモンに渡される。
FUSEデーモン→カーネル
ユーザスペースのFUSEデーモンは、以下の方法でカーネルにリクエストを出す。
1. デバイスファイルの操作: FUSEデーモンは、/dev/fuseという特殊なデバイスファイルを介してカーネルと通信する。
2. リクエストの書き込み: FUSEデーモンは、ファイルシステム操作に対応するリクエストを構築し、/dev/fuseに書き込む。リクエストは、操作の種類(例えば、ファイルの読み取り、書き込み、属性の取得など)を示すオペコードと、必要なパラメータを含むデータ構造で構成される。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fuse_lowlevel.h>
void send_read_request(fuse_req_t req, fuse_ino_t inode, size_t size, off_t offset, struct fuse_file_info *fi) {
struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
// バッファを確保
buf.buf[0].mem = malloc(size);
if (buf.buf[0].mem == NULL) {
fuse_reply_err(req, ENOMEM);
return;
}
// リードリクエストのパラメータを設定
buf.buf[0].size = size;
buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
buf.buf[0].fd = fi->fh;
buf.buf[0].pos = offset;
// カーネルにリードリクエストを送信
fuse_reply_data(req, &buf, FUSE_BUF_COPY_FLAGS_SPLICE_MOVE);
// バッファのメモリを解放
free(buf.buf[0].mem);
}
// この関数はFUSEのreadコールバックとして登録される
void fuse_read_callback(fuse_req_t req, fuse_ino_t ino, size_t size, off_t offset, struct fuse_file_info *fi) {
printf("Read request received: inode=%lu, size=%zu, offset=%ld\n", (unsigned long)ino, size, offset);
send_read_request(req, ino, size, offset, fi);
}
3. カーネルの処理: カーネル側のFUSEモジュールは、/dev/fuseデバイスからリクエストを読み取り、それを解析して適切なファイルシステム操作を行う。この処理は、FUSEカーネルモジュール内で行われ、必要に応じてユーザスペースのデーモンに追加情報を要求することがある。
FUSEのキューの実装は、入力キュー(struct fuse_iqueue) と 処理キュー(struct fuse_pqueue) を通じて行われる。キューへの要求の追加や取り出しは、queue_request_and_unlock や fuse_dev_wake_and_unlock などの関数を通じて行われる。
static void queue_request_and_unlock(struct fuse_iqueue *fiq, struct fuse_req *req)
__releases(fiq->lock)
{
req->in.h.len = sizeof(struct fuse_in_header) +
fuse_len_args(req->args->in_numargs, (struct fuse_arg *) req->args->in_args);
list_add_tail(&req->list, &fiq->pending);
fiq->ops->wake_pending_and_unlock(fiq);
}
queue_request_and_unlockはstruct fuse_req のリストに要求を追加し(list_add_tail)、その後 wake_pending_and_unlock を呼び出して、キューに新しい要求が追加されたことを待機中のスレッドに通知する。
static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq)
__releases(fiq->lock)
{
wake_up(&fiq->waitq);
kill_fasync(&fiq->fasync, SIGIO, POLL_IN);
spin_unlock(&fiq->lock);
}
fuse_dev_do_read 関数は、FUSE (Filesystem in Userspace) システムにおいて、カーネルがユーザースペースの FUSE デーモンから送信された要求を読み取る。
fuse_dev_do_read 関数は、FUSEデバイスからの読み取り要求を処理するための関数であり、以下のステップで構成されている。
1. バッファサイズの検証: 読み取りバッファのサイズが最小要件を満たしているか確認し、不足している場合は -EINVAL を返す。
2. リクエストの待機と取得: リクエストが利用可能になるまで待機し、利用可能なリクエストをキューから取得する。非ブロッキングモードが有効な場合は、リクエストがなければ -EAGAIN を返す。
3. リクエストの種類に応じた処理:割り込みリクエストがある場合、fuse_read_interrupt を呼び出す。忘れられたリクエスト(forget)がある場合、fuse_read_forget を呼び出す。通常のリクエストの場合、リクエストデータをユーザースペースにコピーする。
4. データのコピー: fuse_copy_one 関数を使用して、リクエストヘッダーと引数のデータをユーザースペースにコピーする。
5. エラーハンドリング: コピー中にエラーが発生した場合は、適切なクリーンアップを行い、エラーコードを返す。
6. リクエストの完了: リクエストが正常に処理された場合、そのサイズを返して読み取り操作を完了する。
static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
struct fuse_copy_state *cs, size_t nbytes) {
if (nbytes < MIN_REQUIRED_SIZE) {
return -EINVAL;
}
wait_for_request();
struct fuse_req *req = get_next_request();
if (!req) {
return -EAGAIN;
}
int err = copy_request_to_user(req, cs);
if (err) {
handle_error(err);
return err;
}
complete_request(req);
return req->size;
}
4. レスポンスの受信: 操作が完了すると、カーネルは結果を/dev/fuseデバイスを通じてFUSEデーモンに返す。このレスポンスには、操作の成否、エラーコード、および必要なデータ(ファイルの内容、属性情報など)が含まれることがあります。
5. デーモンの処理: FUSEデーモンは、カーネルからのレスポンスを受け取り、それに基づいてユーザスペースのアプリケーションに対して適切な応答を行います。例えば、ファイルの読み取りリクエストに対しては、読み取ったデータをアプリケーションに返すなどです。このプロセスにより、ユーザスペースのデーモンはカーネルのファイルシステムインターフェースを利用して、独自のファイルシステムロジックを実装することができます。
トレース
Read要求を受けとりbackgroundキューにエンキュー@カーネル
root@ubuntu3:~# bpftrace -e 'kprobe:fuse_simple_background { printf("%s", kstack()) }'
(..)
fuse_simple_background+1
read_pages+149
page_cache_ra_unbounded+353
do_page_cache_ra+61
ondemand_readahead+311
page_cache_async_ra+166
filemap_get_pages+544
filemap_read+190
generic_file_read_iter+229
fuse_file_read_iter+158
new_sync_read+272
vfs_read+258
ksys_read+103
__x64_sys_read+26
do_syscall_64+92
entry_SYSCALL_64_after_hwframe+97
カーネルWrite
root@ubuntu3:/sys/kernel/debug/tracing# bpftrace -e 'kprobe:fuse_perform_write { printf("%s", kstack()) }'
Attaching 1 probe...
fuse_perform_write+1
fuse_file_write_iter+99
new_sync_write+279
vfs_write+393
ksys_write+103
__x64_sys_write+26
do_syscall_64+92
entry_SYSCALL_64_after_hwframe+97