00 FUSE neden kullanılır?
FUSE (Filesystem in Userspace), kernel alanında çalışan geleneksel dosya sistemi sürücüsü yazmadan, standart POSIX dosya arayüzü sunan özel dosya sistemleri oluşturmayı mümkün kılar.
Geleneksel kernel FS ile karşılaştırma
Klasik bir dosya sistemi sürücüsü yazmak, kernel modülü geliştirme becerisi, VFS katmanını anlama ve çekirdek güvenliği konusunda derin bilgi gerektirir. Herhangi bir hata doğrudan kernel panikine yol açabilir. FUSE bu sorunu tersine çevirir: dosya sistemi mantığı tamamen kullanıcı alanında çalışır; kernel yalnızca istekleri iletir.
Geleneksel yol:
Uygulama --syscall--> VFS --> ext4/btrfs.ko --> blok sürücüsü
FUSE yolu:
Uygulama --syscall--> VFS --> fuse.ko --> /dev/fuse --> FUSE daemon (kullanıcı alanı)
FUSE'un tercih edildiği senaryolar
FUSE'un sınırlamaları
FUSE, her I/O isteği için kullanıcı alanına geçiş yapması nedeniyle geleneksel kernel FS'ye kıyasla daha yüksek gecikme sergiler. Yüksek IOPS gerektiren uygulamalar için bu fark belirleyici olabilir. Ayrıca mmap(MAP_SHARED) desteği sınırlıdır ve bazı POSIX anlam garantileri kırılgan olabilir.
| Özellik | Kernel FS | FUSE |
|---|---|---|
| Geliştirme kolaylığı | Düşük (kernel API) | Yüksek (C/C++/Python/Go) |
| Hata izolasyonu | Yok (kernel panic) | Var (daemon çöker, mount kalır) |
| I/O gecikme ek yükü | ~0 ek yük | ~1–10 µs ek yük (context switch) |
| Root yetkisi | Zorunlu | Opsiyonel (allow_other hariç) |
01 FUSE mimarisi
FUSE, üç bileşenden oluşur: kernel içindeki fuse.ko modülü, kullanıcı-kernel köprüsü olan /dev/fuse karakter aygıtı ve gerçek FS mantığını uygulayan kullanıcı alanı daemon'ı.
İstek-Yanıt Döngüsü
1. Uygulama: open("/mnt/myfuse/dosya.txt", O_RDONLY)
2. VFS: FUSE mount noktasını tanır, fuse.ko'ya iletir
3. fuse.ko: isteği sıraya koyar
4. /dev/fuse: daemon isteği poll()/read() ile okur
5. Daemon: isteği işler (örn. HTTP'den veri çeker)
6. /dev/fuse: daemon yanıtı write() ile kernel'e gönderir
7. fuse.ko: yanıtı VFS'e iletir
8. Uygulama: read() çağrısı veriyi alır
Kernel modülü
fuse.ko modülü çoğu dağıtımda yerleşik (built-in) gelir veya otomatik yüklenir. Modülün yüklü olup olmadığını şöyle kontrol edilir:
# fuse.ko durumu
lsmod | grep fuse
# fuse 135168 3
# Yoksa yükle
modprobe fuse
# /dev/fuse karakter aygıtını doğrula
ls -la /dev/fuse
# crw-rw-rw- 1 root fuse 10, 229 ... /dev/fuse
libfuse sürümleri
Kurulum
# Ubuntu/Debian
sudo apt-get install libfuse-dev fuse
# Alpine (gömülü)
apk add fuse fuse-dev
# Yocto recipe içinde
DEPENDS += "fuse"
IMAGE_INSTALL += "fuse"
02 libfuse API temelleri
fuse_operations yapısı, dosya sistemi işlemlerini (getattr, read, write, readdir vb.) uygulayan callback fonksiyon işaretçilerini tutar. Yalnızca desteklemek istediğiniz operasyonları doldurmanız yeterlidir.
fuse_operations yapısı — temel callback'ler
#define FUSE_USE_VERSION 31
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
static int myfs_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi) {
/* Dosya/dizin meta verisi döndür */
memset(stbuf, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
return 0;
}
return -ENOENT;
}
static int myfs_readdir(const char *path, void *buf,
fuse_fill_dir_t filler, off_t offset,
struct fuse_file_info *fi,
enum fuse_readdir_flags flags) {
if (strcmp(path, "/") != 0) return -ENOENT;
filler(buf, ".", NULL, 0, 0);
filler(buf, "..", NULL, 0, 0);
return 0;
}
static int myfs_read(const char *path, char *buf, size_t size,
off_t offset, struct fuse_file_info *fi) {
return -ENOENT;
}
static const struct fuse_operations myfs_ops = {
.getattr = myfs_getattr,
.readdir = myfs_readdir,
.read = myfs_read,
};
Temel callback sözlüğü
| Callback | Karşılık gelen syscall | Zorunlu mu? |
|---|---|---|
| getattr | stat(), lstat() | Evet — her dizin/dosya için |
| readdir | opendir() / readdir() | Dizinler varsa |
| open | open() | Hayır — yoksa izin kontrolü varsayılan |
| read | read(), pread() | Dosya okuma için |
| write | write(), pwrite() | Yazma desteği için |
| create | creat(), open() + O_CREAT | Yeni dosya oluşturma için |
| unlink | unlink() | Dosya silme için |
| mkdir / rmdir | mkdir(), rmdir() | Dizin yönetimi için |
| truncate | truncate(), ftruncate() | Dosya boyutu değiştirme için |
| flush / fsync | close(), fsync() | Veri kalıcılığı için |
main() — fuse_main ile başlatma
int main(int argc, char *argv[]) {
return fuse_main(argc, argv, &myfs_ops, NULL);
}
/* Derleme */
/* gcc -Wall myfs.c -o myfs $(pkg-config fuse3 --cflags --libs) */
/* Kullanım */
/* mkdir /mnt/test */
/* ./myfs /mnt/test */
/* ls /mnt/test */
/* fusermount3 -u /mnt/test */
03 Basit bellek-içi dosya sistemi
hello-fuse örneği, statik içerikli tek bir dosyayı gösterir. Gerçek bir uygulama için başlangıç noktası olarak kullanılabilir.
hello-fuse: tam kaynak
#define FUSE_USE_VERSION 31
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
static const char *hello_path = "/merhaba.txt";
static const char *hello_str = "Merhaba FUSE Dunyasi!\n";
static int hello_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi) {
int res = 0;
memset(stbuf, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
} else if (strcmp(path, hello_path) == 0) {
stbuf->st_mode = S_IFREG | 0444;
stbuf->st_nlink = 1;
stbuf->st_size = (off_t)strlen(hello_str);
} else {
res = -ENOENT;
}
return res;
}
static int hello_readdir(const char *path, void *buf,
fuse_fill_dir_t filler, off_t offset,
struct fuse_file_info *fi,
enum fuse_readdir_flags flags) {
if (strcmp(path, "/") != 0) return -ENOENT;
filler(buf, ".", NULL, 0, 0);
filler(buf, "..", NULL, 0, 0);
filler(buf, "merhaba.txt", NULL, 0, 0);
return 0;
}
static int hello_open(const char *path, struct fuse_file_info *fi) {
if (strcmp(path, hello_path) != 0) return -ENOENT;
if ((fi->flags & O_ACCMODE) != O_RDONLY) return -EACCES;
return 0;
}
static int hello_read(const char *path, char *buf, size_t size,
off_t offset, struct fuse_file_info *fi) {
size_t len;
if (strcmp(path, hello_path) != 0) return -ENOENT;
len = strlen(hello_str);
if (offset >= (off_t)len) return 0;
if (offset + size > len) size = len - (size_t)offset;
memcpy(buf, hello_str + offset, size);
return (int)size;
}
static const struct fuse_operations hello_ops = {
.getattr = hello_getattr,
.readdir = hello_readdir,
.open = hello_open,
.read = hello_read,
};
int main(int argc, char *argv[]) {
return fuse_main(argc, argv, &hello_ops, NULL);
}
Derleme ve test
# Derle
gcc -Wall hello_fuse.c -o hello_fuse $(pkg-config fuse3 --cflags --libs)
# Mount noktası oluştur
mkdir -p /tmp/hello_mount
# Arka planda mount et
./hello_fuse /tmp/hello_mount -f &
# Test et
ls /tmp/hello_mount
cat /tmp/hello_mount/merhaba.txt
# "Merhaba FUSE Dunyasi!"
# Unmount
fusermount3 -u /tmp/hello_mount
Bellek-içi dinamik FS — veri yapısı tasarımı
Birden fazla dosya desteklemek için bir düğüm ağacı (inode tree) tutulur. Basit bir yaklaşım, yol adını anahtar olarak kullanan bir hash tablosudur:
#include <stdlib.h>
#include <stdint.h>
#define MAX_FILES 128
#define MAX_PATH 256
#define MAX_DATA (64 * 1024) /* 64 KB maks dosya boyutu */
typedef struct {
char path[MAX_PATH];
char data[MAX_DATA];
size_t size;
int used;
mode_t mode;
} memfs_node_t;
static memfs_node_t nodes[MAX_FILES];
static memfs_node_t *find_node(const char *path) {
int i;
for (i = 0; i < MAX_FILES; i++) {
if (nodes[i].used && strcmp(nodes[i].path, path) == 0)
return &nodes[i];
}
return NULL;
}
static memfs_node_t *alloc_node(const char *path) {
int i;
for (i = 0; i < MAX_FILES; i++) {
if (!nodes[i].used) {
nodes[i].used = 1;
strncpy(nodes[i].path, path, MAX_PATH - 1);
nodes[i].size = 0;
return &nodes[i];
}
}
return NULL; /* kapasite dolu */
}
04 Gerçekçi FS: HTTP/S3 üzerinden dosya okuma
HTTP veya S3 protokolü üzerinden erişilen uzak blob'ları dosya sistemi olarak sunmak, FUSE'un en yaygın gömülü kullanım senaryolarından biridir. libcurl ile getirilen veriler bellekte önbelleğe alınır.
Mimari
ls /mnt/remoteblobs/
|
v
FUSE daemon (readdir callback)
|-- Uzak index dosyasını getir (HTTP GET /index.json)
|-- JSON'u ayrıştır, dosya adlarını listele
v
cat /mnt/remoteblobs/veri.bin
|
v
FUSE daemon (read callback)
|-- Önbellekte var mı? -- EVET --> bellekten oku
| -- HAYIR--> HTTP GET /blobs/veri.bin
|-- Yanıtı önbelleğe al
v
uygulama veriye ulaşır
libcurl ile HTTP blob getirme
#include <curl/curl.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *data;
size_t size;
} http_buf_t;
static size_t write_cb(void *ptr, size_t sz, size_t nmemb, void *ud) {
http_buf_t *buf = (http_buf_t *)ud;
size_t new_sz = buf->size + sz * nmemb;
buf->data = realloc(buf->data, new_sz + 1);
memcpy(buf->data + buf->size, ptr, sz * nmemb);
buf->size = new_sz;
buf->data[new_sz] = '\0';
return sz * nmemb;
}
int http_get(const char *url, http_buf_t *out) {
CURL *curl = curl_easy_init();
if (!curl) return -1;
out->data = malloc(1);
out->size = 0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, out);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
CURLcode rc = curl_easy_perform(curl);
curl_easy_cleanup(curl);
return (rc == CURLE_OK) ? 0 : -1;
}
read callback — HTTP ile entegrasyon
#define BASE_URL "https://ornek-sunucu.yerel/blobs"
#define CACHE_SIZE 16
typedef struct {
char name[256];
char *data;
size_t size;
} cache_entry_t;
static cache_entry_t cache[CACHE_SIZE];
static cache_entry_t *cache_get(const char *name) {
int i;
for (i = 0; i < CACHE_SIZE; i++) {
if (cache[i].data && strcmp(cache[i].name, name) == 0)
return &cache[i];
}
return NULL;
}
static int remfs_read(const char *path, char *buf, size_t size,
off_t offset, struct fuse_file_info *fi) {
const char *fname = path + 1; /* baştaki / karakterini atla */
cache_entry_t *entry = cache_get(fname);
if (!entry) {
/* Önbellekte yok — HTTP'den getir */
char url[512];
snprintf(url, sizeof(url), "%s/%s", BASE_URL, fname);
http_buf_t hbuf = {0};
if (http_get(url, &hbuf) != 0) return -EIO;
/* Önbelleğe ekle (round-robin slot seçimi) */
static int slot = 0;
free(cache[slot].data);
strncpy(cache[slot].name, fname, 255);
cache[slot].data = hbuf.data;
cache[slot].size = hbuf.size;
entry = &cache[slot];
slot = (slot + 1) % CACHE_SIZE;
}
if (offset >= (off_t)entry->size) return 0;
size_t avail = entry->size - (size_t)offset;
if (size > avail) size = avail;
memcpy(buf, entry->data + offset, size);
return (int)size;
}
05 Write işlemleri ve tutarlılık
Yazma desteği eklemek, okumaya göre daha karmaşık tutarlılık garantileri gerektirir. WAL (Write-Ahead Logging) yaklaşımı, güç kesintilerinde veri kaybını en aza indirir.
write ve create callback'leri
static int memfs_create(const char *path, mode_t mode,
struct fuse_file_info *fi) {
memfs_node_t *node = find_node(path);
if (!node) {
node = alloc_node(path);
if (!node) return -ENOSPC;
}
node->mode = mode;
node->size = 0;
return 0;
}
static int memfs_write(const char *path, const char *buf,
size_t size, off_t offset,
struct fuse_file_info *fi) {
memfs_node_t *node = find_node(path);
if (!node) return -ENOENT;
size_t end = (size_t)offset + size;
if (end > MAX_DATA) return -ENOSPC;
memcpy(node->data + offset, buf, size);
if (end > node->size) node->size = end;
return (int)size;
}
static int memfs_truncate(const char *path, off_t size,
struct fuse_file_info *fi) {
memfs_node_t *node = find_node(path);
if (!node) return -ENOENT;
if ((size_t)size > MAX_DATA) return -EFBIG;
if ((size_t)size > node->size)
memset(node->data + node->size, 0, (size_t)size - node->size);
node->size = (size_t)size;
return 0;
}
fsync ve flush
/* flush: dosya kapatılırken çağrılır — tamponları diske yaz */
static int myfs_flush(const char *path, struct fuse_file_info *fi) {
/* Burada veriyi kalıcı depolamaya yaz */
/* Örnek: SQLite veritabanına commit */
return persist_to_storage(path);
}
/* fsync: açık dosyayı güvenli yap (uygulamadan explicit çağrı) */
static int myfs_fsync(const char *path, int isdatasync,
struct fuse_file_info *fi) {
/* isdatasync != 0: yalnızca veri (metadata hariç) yeterli */
return myfs_flush(path, fi);
}
WAL tabanlı yazma tutarlılığı
Güç kesintisine dayanıklı bir FUSE FS için Write-Ahead Log yaklaşımı uygulanır: her yazma işlemi önce bir log dosyasına sıralı olarak kaydedilir, ardından asıl veri yapısına uygulanır. Başlangıçta log yeniden oynatılarak tutarlılık sağlanır.
/* WAL giriş yapısı */
typedef struct {
uint32_t magic; /* 0xWAL1 */
uint32_t op; /* 1=write, 2=delete, 3=create */
char path[256];
off_t offset;
size_t size;
/* data[size] takip eder */
} wal_entry_t;
int wal_append(int wal_fd, wal_entry_t *entry, const char *data) {
/* Atomic: entry + data aynı write() çağrısıyla yaz */
struct iovec iov[2];
iov[0].iov_base = entry;
iov[0].iov_len = sizeof(*entry);
iov[1].iov_base = (void *)data;
iov[1].iov_len = entry->size;
return (writev(wal_fd, iov, 2) > 0) ? 0 : -1;
}
06 Performans optimizasyonu
FUSE'un context-switch maliyeti, doğru seçenekler ve zero-copy teknikler kullanılarak önemli ölçüde azaltılabilir.
Kritik mount seçenekleri
splice ile zero-copy
splice() sistem çağrısı, kullanıcı alanı tamponu olmadan çekirdek arasında veri transferi sağlar. FUSE low-level API ile birleştirildiğinde yüksek bant genişlikli okumalar için kullanışlıdır:
/* Low-level FUSE read callback — splice ile */
static void ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
off_t off, struct fuse_file_info *fi) {
int fd = (int)(uintptr_t)fi->fh;
struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
buf.buf[0].fd = fd;
buf.buf[0].pos = off;
/* fuse_reply_data: kernel splice'ı kullanır, kullanıcı kopyası yok */
fuse_reply_data(req, &buf, FUSE_BUF_SPLICE_MOVE);
}
Çok iş parçacıklı mod
/* Çok iş parçacıklı FUSE oturumu başlatma */
struct fuse_loop_config config;
memset(&config, 0, sizeof(config));
config.clone_fd = 1;
config.max_idle_threads = 10;
ret = fuse_loop_mt(fuse, &config);
/* Ya da komut satırından: */
/* ./myfs /mnt/test -o clone_fd */
Performans karşılaştırma tablosu
| Seçenek | Sıralı okuma | Rastgele okuma | Açıklama |
|---|---|---|---|
| Varsayılan | Orta | Düşük | Her istek için context switch |
| direct_io | Orta | Orta | Önbellek bypass, taze veri |
| kernel_cache | Yüksek | Yüksek | Tekrar okumalarda önbellekten |
| writeback_cache | — | — | Yazma gecikmeyi azaltır |
| splice + ll API | Çok yüksek | Yüksek | Zero-copy, en yüksek verim |
07 Güvenlik
FUSE varsayılan olarak yalnızca mount işlemini yapan kullanıcının erişimine izin verir. Gömülü sistemlerde çok kullanıcılı erişim, CAP_SYS_ADMIN gereklilikleri ve daemon izolasyonu dikkatli tasarlanmalıdır.
allow_other ve allow_root
/* Mount seçenekleri */
/* -o allow_other: mount sahibi dışındaki kullanıcılara erişim */
/* -o allow_root: yalnızca root ve mount sahibi erişebilir */
/* /etc/fuse.conf içinde etkinleştirme (sistem geneli) */
/* user_allow_other */
./myfs /mnt/test -o allow_other
/* Güvenlik notu: allow_other olmadan diğer kullanıcılar EACCES alır */
CAP_SYS_ADMIN gereklilikleri
Gömülü güvenlik önerileri
# FUSE daemon'ı ayrıcalık düşürme ile çalıştırma
# Gerçek root ile başla, mount sonrası ayrıcalıkları bırak
int main(int argc, char *argv[]) {
/* Mount işlemi öncesi hazırlık (root gerekiyorsa) */
setup_privileged_resources();
/* ayrıcalıkları düşür */
if (setuid(DAEMON_UID) != 0 || setgid(DAEMON_GID) != 0) {
perror("setuid/setgid");
return 1;
}
/* Root olmayan kullanıcı olarak FUSE çalıştır */
return fuse_main(argc, argv, &myfs_ops, NULL);
}
# Systemd service ile güvenli çalıştırma:
# [Service]
# User=fuseuser
# AmbientCapabilities=
# NoNewPrivileges=true
# PrivateTmp=true
Güvenli dosya izinleri
/* getattr içinde gömülü cihaza özel izin kısıtlaması */
static int myfs_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi) {
memset(stbuf, 0, sizeof(*stbuf));
if (strcmp(path, "/config") == 0) {
/* Konfigürasyon dizini: yalnızca sahip okuyabilir/yazabilir */
stbuf->st_mode = S_IFDIR | 0700;
stbuf->st_nlink = 2;
stbuf->st_uid = getuid();
return 0;
}
if (strcmp(path, "/firmware.bin") == 0) {
/* Firmware dosyası: salt okunur, herkes okuyabilir */
stbuf->st_mode = S_IFREG | 0444;
stbuf->st_nlink = 1;
stbuf->st_size = firmware_size();
return 0;
}
return -ENOENT;
}
08 Hata ayıklama
FUSE sorunlarını tespit etmenin en hızlı yolu -d veya -f seçenekleri ve strace ile mount sürecini izlemektir. /sys/fs/fuse/connections/ dizini bağlantı istatistiklerini sunar.
Debug modunda çalıştırma
# -f: ön planda çalış (daemon olmadan)
./myfs /mnt/test -f
# -d: debug modu — her FUSE isteğini yazdırır (en ayrıntılı)
./myfs /mnt/test -d
# Örnek -d çıktısı:
# LOOKUP /dosya.txt
# GETATTR /dosya.txt
# OPEN /dosya.txt fi->flags=0x8000
# READ /dosya.txt off=0 size=4096
strace ile mount izleme
# Uygulamanın FUSE mount'una yaptığı syscall'ları izle
strace -e trace=openat,read,write,getdents64 \
cat /mnt/test/dosya.txt
# FUSE daemon sürecini izle
strace -p $(pidof myfs) -e trace=read,write
/sys/fs/fuse/connections/
# Aktif FUSE bağlantılarını listele
ls /sys/fs/fuse/connections/
# 42/ (bağlantı ID)
# Bağlantı istatistiklerini oku
cat /sys/fs/fuse/connections/42/waiting
# 0 (bekleyen istek sayısı)
cat /sys/fs/fuse/connections/42/max_background
# 12 (eş zamanlı arka plan istek sınırı)
# Bağlantıyı zorla kapat (donmuş FUSE kurtarma)
echo 1 | sudo tee /sys/fs/fuse/connections/42/abort
Yaygın sorunlar ve çözümleri
| Sorun | Neden | Çözüm |
|---|---|---|
| Transport endpoint not connected | Daemon çöktü, mount noktası asılı kaldı | fusermount3 -u -z /mnt/test ile lazy unmount |
| ls: cannot access: Permission denied | allow_other yok veya sahip farklı | /etc/fuse.conf'a user_allow_other ekle |
| FUSE mount sonrası dosyalar görünmüyor | readdir NULL döndürüyor | filler çağrılarını ve dönüş değerini kontrol et |
| Yüksek CPU kullanımı | Busy-wait veya spinlock daemon içinde | libfuse poll ile olay tabanlı döngüye geç |
| Veri tutarsızlığı | flush/fsync implement edilmemiş | flush callback'inde kalıcı yazmayı zorla |
valgrind ile bellek sızıntısı tespiti
# FUSE daemon'ı valgrind altında çalıştır
valgrind --leak-check=full --track-origins=yes \
./myfs /mnt/test -f -s
# -s: tek iş parçacıklı mod — valgrind ile daha temiz izleme
# Bir sonraki terminalde test et, ardından fusermount3 -u ile kapat