FUSE(Filesystem in Userspace)0

前言

最近解決問題涉及到了FUSE,遂在此簡單總結一下。
代碼導讀部分未經(jīng)過實際運行驗證,所以分析的流程可能是錯的。

FUSE

什么是FUSE

Filesystem in Userspace顧名思義,即在用戶空間的文件系統(tǒng)。
為什么要強調(diào)用戶空間呢?接觸過Linux內(nèi)核的同學大概會知道,文件系統(tǒng)一般是實現(xiàn)在內(nèi)核里面的,比如,Ext4、Fat32、NTFS(Kernel原生版)等常見的文件系統(tǒng),其代碼都在內(nèi)核中,而FUSE特殊之處就是,其文件系統(tǒng)的核心邏輯是在用戶空間實現(xiàn)的。

為什么FUSE會存在

事物的存在的原因之一是其優(yōu)勢大于劣勢,下面是它的優(yōu)劣描述。

優(yōu)勢

  • 文件系統(tǒng)的改動不用更新內(nèi)核
    FUSE的核心邏輯在用戶空間,所以修改文件系統(tǒng)的行為絕大部分修改會在用戶空間。這在很多場合是一件很方便的事情。
  • 很容易實現(xiàn)自己的文件系統(tǒng)
    理論上它可以實現(xiàn)任何天馬行空的文件系統(tǒng),只要一個開發(fā)者實現(xiàn)了基本的文件操作。而這個所謂的文件操作也是自己定義的,甚至可以這個操作可能只是一句打印而已,或者是一件超級復雜的事情,只要這個操作符合開發(fā)者的要求,他就完成了一個符合開發(fā)者需求的文件系統(tǒng)(也許本質(zhì)上并不是文件系統(tǒng)了,這種情況是有現(xiàn)實例子的)。

劣勢

  • 效率較低
    這是顯而易見的,就針對塊設備的文件系統(tǒng)而言,用戶層肯定不如內(nèi)核實現(xiàn)的效率高,畢竟用戶態(tài)/內(nèi)核態(tài)切換的開銷是少不了的。這也是符合一般軟件規(guī)律的,越高層次的軟件易用性越高,效率越低。

FUSE實現(xiàn)原理

下面這張圖體現(xiàn)了FUSE工作的基本套路,是根據(jù)WIki里的畫的,這張圖感覺更符合我看到的代碼的狀況。

fuse基本工作流程

圖中體現(xiàn)了FUSE的2個關鍵部分(綠色方框),分別是Kernel中的那個FUSE(這里簡稱kernel FUSE)和user space中的那個fuse_user程序。其中kernel FUSE是負責把從用戶層過來的文件系統(tǒng)操作請求傳遞給fuse_user程序的,而這個fuse_user程序?qū)崿F(xiàn)了前面所說的文件系統(tǒng)的核心邏輯。
下面分步描述一下在用戶對一個FUSE分區(qū)上的文件執(zhí)行l(wèi)s命令時發(fā)生了什么,當然,這里隱含了個前提,即這個系統(tǒng)的/tmp目錄已經(jīng)屬于某個FUSE分區(qū)了,為了達到這種狀況,前面還需要有一個mount的過程。

圖中1號折線過程
  • 用戶敲ls -l /tmp/file_on_fuse_fs+回車
    這時ls會調(diào)用一些系統(tǒng)調(diào)用(例如stat(2))。
  • kernel FUSE接收用戶請求
    文件相關的系統(tǒng)調(diào)用會進入VFS處理,然后VFS會根據(jù)這個分區(qū)的文件系統(tǒng),找到對應文件系統(tǒng)的實現(xiàn)接口,這里當然是找到kernel FUSE。
  • kernel FUSE會把收到的操作請求按照FUSE定義的通信協(xié)議發(fā)送給fuse_user程序
    那么問題來了,kernel FUSE憑什么把消息給fuse_user,卻沒給別人呢?
    如果看得懂,請體會如下兩段代碼
// kernel/fs/fuse/dev.c
const struct file_operations fuse_dev_operations = {
    .owner      = THIS_MODULE,
    .open       = fuse_dev_open,
    .llseek     = no_llseek,
    .read_iter  = fuse_dev_read,
    .splice_read    = fuse_dev_splice_read,
    .write_iter = fuse_dev_write,
    .splice_write   = fuse_dev_splice_write,
    .poll       = fuse_dev_poll,
    .release    = fuse_dev_release,
    .fasync     = fuse_dev_fasync,
    .unlocked_ioctl = fuse_dev_ioctl,
    .compat_ioctl   = fuse_dev_ioctl,
};
EXPORT_SYMBOL_GPL(fuse_dev_operations);

static struct miscdevice fuse_miscdevice = {
    .minor = FUSE_MINOR,
    .name  = "fuse",
    .fops = &fuse_dev_operations,
};
// fuse_user
int fd = open("/dev/fuse", ...);
read(fd, ...);
write(fd, ...);

1.第一段代碼說明,F(xiàn)USE會創(chuàng)建一個名為fuse的混雜設備文件(簡稱fuse設備文件);
2.第二段代碼說明,fuse_user可以用過讀寫fuse設備文件來與kernel FUSE通信,也就是說,fuse_user通過read函數(shù)主動讀取了kernel FUSE的請求。
至此1號折線走完

圖中2號曲線過程
  • fuse_user收到kernel FUSE發(fā)來的請求
    這個收發(fā)請求的機制在文末的參考資料中有提及,感興趣的同學可以研究一下。
  • fuse_user處理這個請求
    這個“處理”完全是開發(fā)者自己定義的,只要符合開發(fā)者的要求就是合適的處理方式,不過本文討論的是針對塊設備的貨真價實的文件系統(tǒng),所以這個“處理”必須能夠讀寫塊設備上面的內(nèi)容。
    那么一個很簡單的問題來了,如果不使用fwrite(3)write(2)這種方式,怎么寫入文件呢?

答案要回到事物的本源,文件是個抽象概念,它本質(zhì)上只是塊設備(例如磁盤、優(yōu)盤或SD卡)上字節(jié)的有序排列而已,所以只要能寫入塊設備,寫入文件當然就可以實現(xiàn)。

那么如何讀寫塊設備呢?請想象插入一個優(yōu)盤,然后體會下面代碼。

int fd = open("/dev/block/sda");//或者sda1
pwrite(fd, buf, count, offset);

解釋一下上面代碼。當我們插入一個優(yōu)盤到linux系統(tǒng)時,常見的情況是系統(tǒng)會自動生成/dev/block/sda/dev/block/sda1兩個塊設備文件,所以第一句open就是在獲取塊設備的fd(file descriptor),然后再用pwrite訪問這個fd,將buf的內(nèi)容向offset位置寫count個字節(jié)。其中offset是寫入位置,即從塊設備的哪個字節(jié)開始寫。雖然這里也是用了write一類的函數(shù),但是write的對象不同哦。
綜上所述,再加上pread函數(shù),我們對塊設備就可以為所欲為了。
至此,2號曲線走完。

圖中3號折線過程
  1. fuse_user將處理結果返回給kernel FUSE
  2. 繼續(xù)順著1號折線的來路,原路返回處理結果
    至此,3號折線走完。

說完了代碼,下面我們用2個實際使用的case(Android和NTFS-3G)進行說明。

FUSE的實現(xiàn)代碼

如前面所講,F(xiàn)USE分為2部分,分別在user、kernel spcae中,在kernel space中的部分由kernel官方維護,user space中的部分(僅是個框架,不包括開發(fā)者的實現(xiàn))有一個開源庫叫libfuse,NTFS-3G就是基于這個FUSE框架的實現(xiàn),另一個我接觸到實現(xiàn)是Android 8.0的中SD卡的文件系統(tǒng)的實現(xiàn),它沒有用libfuse,完全是谷歌自己寫的一個實現(xiàn)。

NTFS-3G與FUSE

關于NTFS-3G

NTFS-3G是一個叫Szabolcs Szakacsits的開發(fā)者2006年創(chuàng)建的項目,后來他創(chuàng)建了一個公司叫Tuxera,從事很多NTFS文件系統(tǒng)相關的業(yè)務,NTFS-3G這個開源項目,也由這個公司維護至今,它就是一份典型的FUSE文件系統(tǒng)實現(xiàn)源碼。

代碼導讀

根據(jù)上面所說的原理,這個文件系統(tǒng)中必然存在著塊設備fuse設備open/close/read/write。下面著重描述3個重要動作,分別是打開塊設備、打開fuse設備處理kernel FUSE請求,啥也不說了,都在代碼里了,擼!。

ntfs-3g.c
main——打開塊設備
{
    ...
    //打開塊設備,opts.device就是塊設備的名字,例如"/dev/block/sda1"
    //這里就是前面代碼中的open來獲得fd的動作
    err = ntfs_open(opts.device);
    ==> ctx->vol = ntfs_mount(device, flags);
        {
        dev = ntfs_device_alloc(name, 0, &ntfs_device_default_io_ops, NULL);
            {
            //埋下伏筆(1)?。?!
            //注冊了設備文件操作函數(shù)
            //dev->d_ops->open = ntfs_device_unix_io_open
            //dev->d_ops->write = ntfs_device_unix_io_write
            dev->d_ops = dops;
            dev->d_private = priv_data;
            }
        ...
        vol = ntfs_device_mount(dev, flags);
        ==> vol = ntfs_volume_startup(dev, flags);
            {
            if ((dev->d_ops->open)(dev, ...)) 
                //為什么會call 到這呢,請看前面的伏筆(1)?。?!
                ==> ntfs_device_unix_io_open(struct ntfs_device *dev, int flags)
                    //注意了!注意了!open塊設備了啊!
                    //例如,dev->d_name = "/dev/block/sda1"
                    ==> *(int*)dev->d_private = open(dev->d_name, flags);
            //埋下伏筆(7)!??!
            //注冊了設備文件操作函數(shù)
            vol->dev = dev;
            }
        }
    ...
}

從上述代碼中可以看到,塊設備確實被打開了。

ntfs-3g.c
main——打開fuse設備
{
    //前面已經(jīng)open了塊設備
    ...
    fh = mount_fuse(parsed_options);
    {
    ctx->fc = try_fuse_mount(parsed_options);
        ==> fc = fuse_mount(opts.mnt_point, &margs);
            {
            fd = fuse_kern_mount(mountpoint, args);
            ==> res = fusermount(0, 0, 0, mnt_opts ? mnt_opts : "", mountpoint);
                ==> res = mount_fuse(mnt, opts);
                    ==> fd = open_fuse_device(&dev);
                        ==> fd = try_open(FUSE_DEV_NEW, devp);
                            //注意了!注意了!open fuse設備了啊!
                            //例如,dev = "/dev/fuse"
                            ==> fd = open(dev, O_RDWR);
            ...
            ==> ch = fuse_kern_chan_new(fd);
                {
                struct fuse_chan_ops op = {
                    //埋下伏筆(2.0)?。。?                    //注冊了設備文件操作函數(shù)
                    //op.receive = fuse_kern_chan_receive
                    //op.send = fuse_kern_chan_send
                    .receive = fuse_kern_chan_receive,
                    .send = fuse_kern_chan_send,
                    ...
                    };
                ...
                return fuse_chan_new(&op, fd, bufsize, NULL);
                ==> return fuse_chan_new_common(op, fd, bufsize, data);
                    {
                    //埋下伏筆(2.1)?。?!
                    //伏筆(2.0)的op給了ch
                    //ch->op->receive = fuse_kern_chan_receive
                    //ch->op->send = fuse_kern_chan_send
                    ch->op = *op;
                    //埋下伏筆(3)?。?!
                    //打開fuse設備的fd給了ch
                    ch->fd = fd;
                    }
                }
            }
    //埋下伏筆(4.0)?。?!
    //ntfs_3g_ops.write = ntfs_fuse_write
    fh = fuse_new(ctx->fc, &args , &ntfs_3g_ops, sizeof(ntfs_3g_ops), NULL);
        //埋下伏筆(5.0)?。?!
        //llop = fuse_path_ops
        //fuse_path_ops.write = fuse_lib_write
    ==> f->se = fuse_lowlevel_new(args, &llop, sizeof(llop), f);
        {
        struct fuse_session_ops sop = {
            //埋下伏筆(6.0)?。?!
            //sop.process = fuse_ll_process
            .process = fuse_ll_process,
            ...
             };
        ...
            //埋下伏筆(4.1)!??!
            //f->fs->op = ntfs_3g_ops
            //f->fs->op.write = ntfs_fuse_write
        fs = fuse_fs_new(op, op_size, user_data);
            ==> memcpy(&fs->op, op, op_size);
        f->fs = fs;
        ...
        //埋下伏筆(5.1)!?。?        //op = llop = fuse_path_ops
        //f->op->write = fuse_lib_write
        memcpy(&f->op, op, op_size);
        ...
        se = fuse_session_new(&sop, f);
            {
            //埋下伏筆(6.1)?。?!
            //sop給了se
            se->op = *op;
            se->data = data;
            }
        }
    }
    ...
}

從上述代碼中可以看到,fuse混雜設備確實被打開了。

ntfs-3g.c
main——處理kernel FUSE請求
{
    //前面已經(jīng)open了塊設備和fuse設備
    ...
    fuse_loop(fh);
    ==> return fuse_session_loop(f->se);
        {
        //這個循環(huán)中不停地響應著kernel FUSE的請求
        while (!fuse_session_exited(se)) {
            struct fuse_chan *tmpch = ch;
            res = fuse_chan_recv(&tmpch, buf, bufsize);
            ==> return ch->op.receive(chp, buf, size);
                //為啥call到這?請看伏筆(2.x)
                ==> fuse_kern_chan_receive
                    {
                    //fuse_chan_fd(ch)是什么?請看伏筆(3)
                    res = read(fuse_chan_fd(ch), buf, size);
                    }
                ...
            fuse_session_process(se, buf, res, tmpch);
            ==> se->op.process(se->data, buf, len, ch);
                //為啥call到這?請看伏筆(6.x)
                ==> fuse_ll_process
                    /**********
                        static struct {
                            void (*func)(fuse_req_t, fuse_ino_t, const void *);
                            const char *name;
                            } fuse_ll_ops[] = {
                                ...
                                [FUSE_WRITE]       = { do_write,       "WRITE"       },
                                ...
                        };
                    ***********/
                    //假設我們在進行寫(FUSE_WRITE)操作
                    ==> fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
                        ==> do_write
                            ==> req->f->op.write(req, nodeid, PARAM(arg), arg->size, arg->offset, &fi);
                                //為啥call到這?請看伏筆(5.x)
                                ==> fuse_lib_write
                                    {
                                    ...
                                    res = fuse_fs_write(f->fs, path, buf, size, off, fi);
                                    ==> return fs->op.write(path, buf, size, off, fi);
                                        //為啥call到這?請看伏筆(4.x)
                                        ==> ntfs_fuse_write
                                            ==> s64 ret = ntfs_attr_pwrite(na, offset, size, buf + total);
                                                //vol->dev是什么?請看伏筆(7)
                                                ==> written = ntfs_pwrite(vol->dev, ...);
                                                    ==> written = dops->pwrite(dev, ...);
                                                        //為啥call到這?請看伏筆(1)
                                                        ==> ntfs_device_unix_io_pwrite
                                                            //注意了!注意了!對塊設備文件pwrite了啊!
                                                            //DEV_FD(dev)是什么?請看前面打開塊設備的地方
                                                            ==> return pwrite(DEV_FD(dev), buf, count, offset);
                                    ...
                                    fuse_reply_write(req, res);
                                        ==> return send_reply_ok(req, &arg, sizeof(arg));
                                            ==> return send_reply(req, 0, arg, argsize);
                                                ==> return send_reply_iov(req, error, iov, count);
                                                    ==> res = fuse_chan_send(req->ch, iov, count);
                                                        ==> return ch->op.send(ch, iov, count);
                                                            ==> fuse_kern_chan_send
                                                                //注意了!注意了!對fuse設備文件writev了??!
                                                                //雖然和write不同,但也是向fuse設備文件的fd寫東西
                                                                //fuse_chan_fd(ch)是什么?請看伏筆(3)
                                                                ==> ssize_t res = writev(fuse_chan_fd(ch), iov, count);
                                    ...
                                    }
            }
        }
    //收尾工作
    ...
}

光練不說傻把式,還得說一下。
從上述代碼中可以看到,“寫”的用戶請求是用ntfs_fuse_write函數(shù)處理的,struct fuse_operations ntfs_3g_ops就是開發(fā)者要實現(xiàn)的文件系統(tǒng)核心邏輯,這些文件操作在打開fuse設備過程(mount_fuse函數(shù))中被綁定到那些核心數(shù)據(jù)結構中。在最后處理文件系統(tǒng)請求時調(diào)用,最終以直接訪問塊設備的方式實現(xiàn)了“處理”。然后,以寫入fuse設備文件的方式將“處理”結果發(fā)給kernel FUSE。

Android與FUSE

Android代碼到哪看

談到Android,由于眾所周知的原因,首先要說怎么在中國大陸訪問它的代碼,這事靠百度可以解決,如果只是看看,用這些網(wǎng)站在線看就好了。
Android代碼瀏覽網(wǎng)站1
Android代碼瀏覽網(wǎng)站2

Android 8.0的FUSE

Android里面用的并不是NTFS-3G所使用的libfuse,因為我接觸到的是AN8(Android 8.0)的代碼,所以就基于它來再次領略一下FUSE文件系統(tǒng)的實現(xiàn)。代碼在AN8/system/core/sdcard,下面有3個代碼文件

AN8/system/core/sdcard
├── Android.mk
├── fuse.cpp
├── fuse.h
└── sdcard.cpp // main函數(shù)在這里?。?!

sdcard.cpp是對SD卡文件系統(tǒng)處理的代碼。谷歌搞了個sdcardfs文件系統(tǒng),當系統(tǒng)支持sdcardfs,并且用戶要求使用時,就會優(yōu)先用這個文件系統(tǒng)掛載SD卡,否則就用FUSE掛載。也就是說,對于AN8來說,F(xiàn)USE是sdcardfs的備胎,下面代碼反映了這綠油油的事實。

int main(int argc, char **argv) {
    //各種準備工作
    ...
    if (should_use_sdcardfs()) {
        //如果應該用sdcardfs,就運行sdcardfs
        run_sdcardfs(...);
    } else {
        //否則,就運行FUSE
        run(...);
    }
    return 1;
}

下面創(chuàng)建了3個start_handler的線程,看得出來它們之間有些區(qū)別。為什么是這3個?我也不知道,那就是AN8的實現(xiàn)問題了,不是本文重點。

static void run(...) {
    //準備工作
    ...
    //埋下伏筆(1)
    //這些dest_path后面會用到
    snprintf(fuse_default.dest_path, PATH_MAX, "/mnt/runtime/default/%s", label);
    snprintf(fuse_read.dest_path, PATH_MAX, "/mnt/runtime/read/%s", label);
    snprintf(fuse_write.dest_path, PATH_MAX, "/mnt/runtime/write/%s", label);

    handler_default.fuse = &fuse_default;
    handler_read.fuse = &fuse_read;
    handler_write.fuse = &fuse_write;

    ...
    if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006)
                || fuse_setup(&fuse_read, AID_EVERYBODY, ...)
                || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0027))
        ==> fuse_setup
            {
            //注意了!注意了!打開fuse設備文件了啊!
            //埋下伏筆(2)
            //注意這個fd,后面會用到
            fuse->fd = TEMP_FAILURE_RETRY(open("/dev/fuse", O_RDWR | O_CLOEXEC));
            //欲知fuse->dest_path是什么,請看伏筆(1)
            mount("/dev/fuse", fuse->dest_path,...)
            }
    ...
    if (pthread_create(&thread_default, NULL, start_handler, &handler_default)
            || pthread_create(&thread_read, NULL, start_handler, &handler_read)
            || pthread_create(&thread_write, NULL, start_handler, &handler_write)) {
        LOG(FATAL) << "failed to pthread_create";
    }
    // 一些不會退出的loop
    ...
}

在上面代碼中,打開了fuse設備文件,同時創(chuàng)建了3個FUSE用戶線程來處理kernel FUSE的請求。

start_handler ==> handle_fuse_requests
{
    for (;;) {
        //欲知fuse->fd是什么,請看伏筆(2)
        read(fuse->fd,...);
        ...
        //埋下伏筆(3)
        //data是kernel FUSE發(fā)來的請求
        int res = handle_fuse_request(fuse, handler, hdr, data, data_len);
                //以一個順利的寫請求為例
            ==> return handle_write(fuse, handler, hdr, req, buffer);
                {
                struct handle *h = static_cast<struct handle*>(id_to_ptr(req->fh));
                ...
                //注意了!注意了!寫塊設備文件了??!
                //欲知h->fd是什么,它源自伏筆(3)提到的data,所以它來自kernel FUSE
                //它是怎么來的呢?此處設下一個懸念(1)
                res = TEMP_FAILURE_RETRY(pwrite64(h->fd, buffer, req->size, req->offset));
                ...
                fuse_reply(fuse, hdr->unique, &out, sizeof(out));
                    //注意了!注意了!寫fuse設備文件了??!
                    //欲知fuse->fd是什么,請看伏筆(2)
                ==> ssize_t ret = TEMP_FAILURE_RETRY(writev(fuse->fd, vec, 2));
                ...
                }
        ...
    }
}

在上面代碼中,讀取了kernel FUSE的請求,然后處理,即寫入了塊設備文件,最后發(fā)回結果給kernel FUSE。
這段代碼中有一個懸念,后文會揭露。

Kernel FUSE

代碼在哪

代碼導讀

我對這里沒有多少研究,怕誤人子弟,所以不展開了,僅僅圍繞前面代碼中的懸念(1)進行說明。前面的懸念(1)在于那個來自kernel FUSE的fd是在哪里賦值的。下面先從讀fuse設備文件說起,因為這里就是獲取kernel FUSE請求的現(xiàn)場。

讀寫fuse設備文件

前面的FUSE文件系統(tǒng)實現(xiàn)中,它們與kernel FUSE溝通都是通過讀寫fuse設備文件實現(xiàn)的,而這個設備文件的讀寫操作就定義在struct file_operations fuse_dev_operations中。

//dev.c
const struct file_operations fuse_dev_operations = {
    ...
    .read_iter  = fuse_dev_read,
    .splice_read    = fuse_dev_splice_read,
    .write_iter = fuse_dev_write,
    .splice_write   = fuse_dev_splice_write,
    ...
};

我看了半天才想到,上面這部分代碼對我們揭開懸念沒有幫助,因為這里只是把請求從某處讀出來給用戶層而已,所以它并不生產(chǎn)請求,只是請求的搬運工。回顧一下原理,直接給kernel FUSE創(chuàng)建請求的是VFS,所以應該從對接VFS的那部分kernel FUSE的代碼尋找線索。另外一個重要線索就是在AN8的代碼中,處理寫請求時用到了struct fuse_write_in這個結構體,懸念處的fd值就是源于fuse_write_in.fh,關鍵是它定義在kernel的頭文件里喲,你懂的。

神秘的fh

在kernel里搜struct fuse_write_in就很容易發(fā)現(xiàn),fuse_write_in.fh的賦值在fuse_write_fill里面。下面的代碼描述的是每個FUSE文件系統(tǒng)中的文件的“寫”過程,從這個過程中可以觀察到,這個fh就在file->private_data中,file->private_data的實際類型是struct fuse_file*。

static const struct file_operations fuse_file_operations = {
    ...
    .write_iter = fuse_file_write_iter,
            ==> written_buffered = fuse_perform_write(file, mapping, from, pos);
                ==> num_written = fuse_send_write_pages(...);
                    ==> res = fuse_send_write(req, &io, pos, count, NULL);
                        {
                            struct fuse_file *ff = file->private_data;
                            ...
                            fuse_write_fill(req, ff, pos, count);
                            {
                            struct fuse_write_in *inarg = &req->misc.write.in;
                            ...
                            //inarg->fh = file->private_data->fh
                            inarg->fh = ff->fh;
                            }
                        }
    ...
};
void fuse_init_file_inode(struct inode *inode)
{
    inode->i_fop = &fuse_file_operations;
    inode->i_data.a_ops = &fuse_file_aops;
}

神秘的private_data——一切還在用戶層

暮然回首,那fd還在用戶層,請看代碼(AN8中)。

static int handle_open(struct fuse* fuse, struct fuse_handler* handler,
        const struct fuse_in_header* hdr, const struct fuse_open_in* req)
{
    ...
    node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path));
    ...
    //注意了!注意了!打開塊設備文件了??!
    //懸念(1)被揭露了
    h->fd = TEMP_FAILURE_RETRY(open(path, req->flags));
    out.fh = ptr_to_id(h);
    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
    ...
}

上面這段代碼是AN8的FUSE實現(xiàn)打開文件的函數(shù),這就是AN8 FUSE打開塊設備文件的現(xiàn)場了。為什么是open呢?此處與kernel中的file->private_data有什么關系呢?請看下面代碼。

int fuse_do_open(struct fuse_conn *fc, u64 nodeid, struct file *file,
         bool isdir)
{
    ...
    //這里就通向了用戶層的open,用戶層那個fh就通過outarg傳了回來
    err = fuse_send_open(fc, nodeid, file, opcode, &outarg);
    ff->fh = outarg.fh;
    ff->open_flags = outarg.open_flags;
    ...
    //file->private_data->fh = outarg.fh
    //outarg就是懸念(1)被揭露現(xiàn)場的那個out
    file->private_data = fuse_file_get(ff);
        ==> return ff;
    ...
}

與NTFS-3G相比,AN8利用每個文件的struct file.private_data來傳遞塊設備文件的fd,并在open文件的時候打開塊設備。

參考資料

FUSE WIKI
FUSE簡介(譯)
FUSE簡介

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • linux資料總章2.1 1.0寫的不好抱歉 但是2.0已經(jīng)改了很多 但是錯誤還是無法避免 以后資料會慢慢更新 大...
    數(shù)據(jù)革命閱讀 13,170評論 2 33
  • Ubuntu的發(fā)音 Ubuntu,源于非洲祖魯人和科薩人的語言,發(fā)作 oo-boon-too 的音。了解發(fā)音是有意...
    螢火蟲de夢閱讀 100,584評論 9 468
  • 01 這是我提筆寫字的第三天,在和朋友一起在聊天的時候,朋友吐槽說,你真的需要改變了,瞧瞧你的穿衣風格,真的是lo...
    泛善可陳閱讀 371評論 1 11
  • 黃昏,我是陽臺上看客 雨浸潤著游蕩的風 穿過榛子樹和洋槐木的拱廊 掀起白晝輕攏的衫 滿溢的綠和八月的雨一起流淌 露...
    十一月湖水閱讀 310評論 0 2
  • 你說,你依然記得第一次見到我時的樣子,仿佛一輩子都忘不了。 可是,我無論如何都無法喚起腦海中的記憶,只能從你口中了...
    家旭老師閱讀 342評論 5 1

友情鏈接更多精彩內(nèi)容