F2FS文件系统中文件的存储结构

文件数据的组织方式一般时被设计为inode-data模式,即 每一个文件都具有一个inode,这个inode记录data的组织关系,这个关系称为文件结构。例如用户需要访问A文件的第1000个字节,系统就会先根据A文件的路径找到的A的inode,然后从inode找到第1000个字节所在的物理地址,然后从磁盘读取出来。那么F2FS的文件结构是怎么样的呢?

如上图,F2FS中的一个inode,包含两个主要部分: metadata部分,和数据块寻址部分。我们重点观察数据块寻址部分,分析inode是如何将数据块索引出来。在图中,数据块寻址部分包含direct pointers,single-indirect,double-indirect,以及triple-indirect。它们的含义分别是:

direct pointer: inode内直接指向数据块(图右上角Data)的地址数组,即inode->data模式

single-indirect pointer: inode记录了两个single-indirect pointer(图右上角Direct node),每一个single-indirect pointer存储了多个数据块的地址,即inode->direct_node->data模式

double-indirect: inode记录了两个double-indirect pointer(图右上角indirect node),每一个double-indirect pointer记录了许多single-indirect pointer,每一个single-indirect pointer指向了数据块,即inode->indirect_node->direct_node->data模式

triple-indirect: inode记录了一个triple-indirect pointer(图右上角indirect node),每一个triple-indirect pointer记录了许多double-indirect pointer,每一个double-indirect pointer记录了许多single-indirect pointer,最后每一个single-indirect pointer指向了数据块。即inode->indirect_node->indirect_node->direct_node->data模式

因此,我们可以发现,F2FS的inode结构采取indirect_node,首先在inode内部寻找物理地址,如果找不到再去direct_node找,层层深入。

f2fs_node的结构以及作用

根据上面的分析,我们可以发现一个对于一个较大的文件,它可能包含inode以外的node,去保存一些间接寻址的信息。single-indirect pointer记录的是数据块的地址,而double-indirect pointer记录的是single-indirect pointer的地址,triple-indirect pointer记录的double-indirect pointer地址。在F2FS中,

inode对应的是f2fs_inode结构,包含了多个direct pointer指向数据块物理地址;

single-indirect pointer对应的是direct_node结构,包含了多个direct pointer指向物理地址;

double-indirect pointer对应的是indirect_node结构,包含了多个指向direct_node的地址;

triple-indirect pointer对应的也是indirect_node结构,包含了多个指向indirect_node的地址

接下来我们逐个分析F2FS每一个node的具体数据结构。

基本node结构

为了方便F2FS的对node的区分和管理,f2fs_inodedirect_node以及indirect_node都使用了同一个数据结构f2fs_node进行描述,并通过union的方式,将f2fs_node初始化成不同的node形式,它的结构如下:

arduino 复制代码
// f2fs_node size:4096
struct f2fs_node {
        union {
                struct f2fs_inode i; //size:4072
                struct direct_node dn; //size:4072
                struct indirect_node in; //size:4072
        };
        struct node_footer footer; // footer用于记录node的类型
} __packed;

struct node_footer {
        __le32 nid;                /* node id */
        __le32 ino;                /* inode nunmber */
        __le32 flag;                /* include cold/fsync/dentry marks and offset */
        __le64 cp_ver;                /* checkpoint version */
        __le32 next_blkaddr;        /* next node page block address */
} __packed;

其中起到区分是哪一种node的关键数据结构是node_footer

每一个node都有一个独特的nid,它被记录在footer中,如果是direct_node或者indirect_node,它们都有一个对应的f2fs_inode,因此为了记录从属关系,还需要footer记录它所属于的f2fs_inodenid,即ino。因此,如果footer->nid == footer->ino,那么这个node就是inode,反正这个nodedirect_node或者indirect_node

footer->flag的作用是标记当前的node的属性。目前F2FS给node定义了三种属性:

arduino 复制代码
enum {
        COLD_BIT_SHIFT = 0,
        FSYNC_BIT_SHIFT,
        DENT_BIT_SHIFT,
        OFFSET_BIT_SHIFT
};

#define OFFSET_BIT_MASK                (0x07)        /* (0x01 << OFFSET_BIT_SHIFT) - 1 */

其中footer->flag

第0位表示这个node是否是cold node。

第1位表示这个node是否执行了完整的fsync。F2FS为了fsync的效率做了一些改进,F2FS不会在fsync刷写所有脏的node page进去磁盘,只会刷写一些根据data直接相关的node page进入磁盘,例如f2fs_inodedirect_node。因此这个标志位是用来记录这个node是否执行了完整的fsync,以便系统在crash中恢复。

第3位表示这个node是是用来保存文件数据,还是目录数据的,也是用于数据恢复

footer->cp_ver分别用来记录当前的checkpoint的version,恢复的时候比较version版本确定如何进行数据恢复。

footer->next_blkaddr则是用来记录这个node对应下一个node page的地址,也是用来恢复数据

f2fs_inode结构

我们先看f2fs_inode的结构,省略其他元数据的信息,重点关注文件如何索引的,结构如下:

ini 复制代码
struct f2fs_inode {
        ...
        union {
                struct {
                        __le16 i_extra_isize;   /* extra inode attribute size */
                        __le16 i_inline_xattr_size;     /* inline xattr size, unit: 4 bytes */
                        __le32 i_projid;        /* project id */
                        __le32 i_inode_checksum;/* inode meta checksum */
                        __le64 i_crtime;        /* creation time */
                        __le32 i_crtime_nsec;   /* creation time in nano scale */
                        __le32 i_extra_end[0];  /* for attribute size calculation */
                } __packed;
                __le32 i_addr[DEF_ADDRS_PER_INODE];     /* DEF_ADDRS_PER_INODE=923, Pointers to data blocks */
        };
        __le32 i_nid[DEF_NIDS_PER_INODE];       /*DEF_NIDS_PER_INODE=5, direct(2), indirect(2),
                                                double_indirect(1) node id */
        ...
} __packed;

i_addr数组就是前面提及的direct pointer,数组的下标是文件的逻辑位置,数组的值就是flash设备的物理地址。例如文件的第一个页就对应i_addr[0],第二个页就对应i_addr[1],而i_addr[0]i_addr[1]所记录的物理地址,就是文件第一个页(page)和第二个页的数据的物理地址,系统可以将两个物理地址提交到flash设备,将数据读取出来。

我们可以发现i_addr的数组长度只有923,即一个f2fs_inode只能直接索引到923个页/块的地址(约3.6MB),对于大于3.6MB的文件,就需要使用间接寻址f2fs_inodei_nid数组就是为了间接寻址而设计,i_nid数组是一个长度为5的数组,可以记录5个node的地址。其中

i_nid[0]i_nid[1]记录的是direct_node的地址,即对应前述的single-indirect pointer。

i_nid[2]i_nid[3]记录的是indirect_node的地址,这两个indirect_node记录的是direct_node的地址,即对应前述的double-indirect pointer。

i_nid[4]记录的是indirect_node的地址,但是这个indirect_node记录的是indirect_node的地址,即前述的triple-indirect pointer。

direct_node和indirect_node结构

direct_inode以及indirect_inode的结构如下所示,direct_node记录的是数据块的地址,indirect_inode记录的是node的id,系统可以通过nid找到对应的node的地址。

ini 复制代码
struct direct_node {
        __le32 addr[ADDRS_PER_BLOCK]; // ADDRS_PER_BLOCK=1018
} __packed;

struct indirect_node {
        __le32 nid[NIDS_PER_BLOCK]; // NIDS_PER_BLOCK=1018
} __packed;

Wandering Tree问题

F2FS的设计是为了解决wandering tree的问题,那么现在的设计是如何解决这个问题的呢。假设一个文件发生更改,修改了direct_node里面的某一个block的数据,根据LFS的异地更新特性,我们需要给更改后的数据一个新的block。传统的LFS需要将这个新的block的地址一层层往上传递,直到inode结构。而F2FS的设计是只需要将direct_node对应位置的addr的值更新为新block的地址,从而没必要往上传递,因此解决了wandering tree的问题。

普通文件数据的保存

从上节描述可以知道,一个文件由一个f2fs_inode和多个direct_inode或者indirect_inode所组成。当系统创建一个文件的时候,它会首先创建一个f2fs_inode写入到flash设备,然后用户往该文件写入第一个page的时候,会将数据写入到main area的一个block中,然后将该block的物理地址赋值到f2fs_inode->i_addr[0]中,这样就完成了Node-Data的管理关系。随着对同一文件写入的数据的增多,会逐渐使用到其他类型的node去保存文件的数据。

经过上面的分析,我们可以计算F2FS单个文件的最大尺寸:

  1. f2fs_inode 直接保存了923个block的数据的物理地址
  2. f2fs_inode->i_nid[0~1] 保存了两个 direct_node 的地址,这里可以保存 2 x 1018个block的数据
  3. f2fs_inode->i_nid[2~3] 保存了两个indirect_node 的地址,这两个其中2个indirect_node保存的是 direct_node 的nid,因此可以保存 2 x 1018 x 1018个block的数据;
  4. f2fs_inode->i_nid[4] 保存了一个indirect_node 的地址,这个indirect_node保存的是 indirect_node 的nid,因此可以保存 1018 x 1018 x 1018个页的数据

可以得到如下计算公式: 4KB x (923 + 2 x 1018 + 2 x 1018 x 1018 + 1 x 1018 x 1018 x 1018) = 3.93 TB 因此F2FS单个文件最多了保存3.93TB数据。

内联文件数据的保存

从上节可以知道,文件的实际数据是保存在f2fs_inode->i_addr对应的物理块当中,因此即使一个很小的文件,如1个字节的小文件,也需要一个node和data block才能实现正常的保存和读写,也就是需要8KB的磁盘空间去保存一个尺寸为1字节的小文件。而且f2fs_inode->i_addr[923]里面除了f2fs_inode->i_addr[0]保存了一个物理地址,其余的922个i_addr都被闲置,造成了空间的浪费。

因此F2FS为了减少空间的使用量,使用内联(inline)文件减少这些空间的浪费。它的核心思想是当文件足够小的时候,使用f2fs_inode->i_addr数组直接保存数据本身,而不单独写入一个block中,再进行寻址。因此,如上面的例子,只有1个字节大小的文件,只需要一个f2fs_inode结构,即4KB,就可以同时将node信息和data信息同时保存,减少了一半的空间使用量。

根据上述定义,可以计算得到多大的文件可以使用内联的方式进行保存,f2fs_inode有尺寸为923的用于保存数据物理地址的数组i_addr,它的数据类型是__le32,即4个字节。保留一个数组成员另做它用,因此内联文件最大尺寸为: 922 * 4 = 3688字节。

inline_data的挂载标志设置到了sbi中的struct f2fs_mount_info,因此可以通过文件大小就可以确定出,inode中保存的是文件数据还是Direct pointer。

文件读写与 物理地址 的映射的例子

Linux的文件是通过page进行组织起来的,默认page的size是4KB,使用index作为编号。

一个小文件访问例子

例如一个size=10KB的文件,需要3个page去保存数据,这3个page的编号是0,1,2。当用户访问这个文件的第2~6kb的数据的时候,系统就会计算出数据保存在page index = 0和1的page中,然后根据文件的路径找到对应的f2fs_inode结构,page index = 0和1即对应f2fs_inodei_addr[0]i_addr[1]。系统进而从这两个i_addr读取物理地址,提交到flash设备将数据读取出来。

一个大文件访问例子

假设用户需要读取文件第4000个页(page index = 3999)的数据, 第一步: 那么首先系统会根据文件路径找到对应的f2fs_inode结构 第二步: 由于4000 >(923 + 1018 + 1018),f2fs_inode->i_addrf2fs_inode->nid[0]和nid[1]都无法满足需求,因此系统根据f2fs_inode->nid[2]找到对应的 indirect_node的地址 第三步: indirect_node保存的是direct_node的nid数组,由于 4000 - 923 - 1018 - 1018 = 1041,而一个direct_node只能保存1018个block,因此可以知道数据位于indirect_node->nid[1]对应的direct_node中 第四步: 计算剩下的的偏移(4000-923-1018-1018-1018=23)找到数据的物理地址位于该direct_nodedirect_node->addr[23]中。

F2FS 物理地址 寻址的实现

VFS的读写都依赖于物理地址的寻址。经典的读流程,VFS会传入inode以及page index信息给文件系统,然后文件系统需要根据以上信息,找到物理地址,然后访问磁盘将其读取出来。F2FS的物理地址寻址,是通过f2fs_get_dnode_of_data函数实现。

在执行这个f2fs_get_dnode_of_data函数之前,需要通过set_new_dnode函数进行对数据结构struct dnode_of_data进行初始化:

arduino 复制代码
/*
 * this structure is used as one of function parameters.
 * all the information are dedicated to a given direct node block determined
 * by the data offset in a file.
 */
struct dnode_of_data {
        struct inode *inode;                /* VFS inode结构 */
        struct page *inode_page;        /* f2fs_inode对应的node page */
        struct page *node_page;                /* 用户需要访问的物理地址所在的node page,有可能跟inode_page一样*/
        nid_t nid;                        /* 用户需要访问的物理地址所在的node的nid,与上面的node_page对应*/
        unsigned int ofs_in_node;        /* 用户需要访问的物理地址位于上面的node_page对应的addr数组第几个位置 */
        bool inode_page_locked;                /* inode page is locked or not */
        bool node_changed;                /* is node block changed */
        char cur_level;                        /* 当前node_page的层次,按直接访问或者简介访问的深度区分 */
        char max_level;                        /* level of current page located */
        block_t        data_blkaddr;                /* 用户需要访问的物理地址 */
};

static inline void set_new_dnode(struct dnode_of_data *dn, struct inode *inode,
                struct page *ipage, struct page *npage, nid_t nid)
{
        memset(dn, 0, sizeof(*dn));
        dn->inode = inode;
        dn->inode_page = ipage;
        dn->node_page = npage;
        dn->nid = nid;
}

以f2fs_do_write_data_page为例

ini 复制代码
int f2fs_do_write_data_page(struct f2fs_io_info *fio)
{
        struct page *page = fio->page;
        struct inode *inode = page->mapping->host;
        struct dnode_of_data dn;
        struct extent_info ei = {0,0,0};
        struct node_info ni;
        bool ipu_force = false;
        int err = 0;

        set_new_dnode(&dn, inode, NULL, NULL, 0); // 0表示不清楚nid
        ...
        //然后根据需要访问的page index(文件的page offset),执行f2fs_get_dnode_of_data函数寻找
        err = f2fs_get_dnode_of_data(&dn, page->index, LOOKUP_NODE);
        if (err)
                goto out;

        fio->old_blkaddr = dn.data_blkaddr;  //对应要访问的物理地址

}

f2fs_get_dnode_of_data

ini 复制代码
int f2fs_get_dnode_of_data(struct dnode_of_data *dn, pgoff_t index, int mode)
{
        struct f2fs_sb_info *sbi = F2FS_I_SB(dn->inode);
        struct page *npage[4];
        struct page *parent = NULL;
        int offset[4];
        unsigned int noffset[4];
        nid_t nids[4];
        int level, i = 0;
        int err = 0;

        //根据文件offset,找到对应的物理地址所保存的位置,noffset和offset,以及Level
        level = get_node_path(dn->inode, index, offset, noffset);
        if (level < 0)
                return level;

        //下面是根据inode的信息,依次索引读出noffset和offset对应的数据,获取offset对应的block物理地址。
        nids[0] = dn->inode->i_ino;
        npage[0] = dn->inode_page;

        if (!npage[0]) {
                npage[0] = f2fs_get_node_page(sbi, nids[0]); // 获取inode对应的f2fs_inode的node page
                if (IS_ERR(npage[0]))
                        return PTR_ERR(npage[0]);
        }

        /* if inline_data is set, should not report any block indices */
        if (f2fs_has_inline_data(dn->inode) && index) {
                err = -ENOENT;
                f2fs_put_page(npage[0], 1);
                goto release_out;
        }

        parent = npage[0];
        if (level != 0)
                nids[1] = get_nid(parent, offset[0], true);// 获取f2fs_inode->i_nid
        dn->inode_page = npage[0];
        dn->inode_page_locked = true;
        
        /* get indirect or direct nodes */
        for (i = 1; i <= level; i++) {
                bool done = false;

                if (!nids[i] && mode == ALLOC_NODE) {
                        // 创建模式,常用,写入文件时,需要node page再写入数据,因此对于较大文件,在这里创建node page
                        if (!f2fs_alloc_nid(sbi, &(nids[i]))) {
                                err = -ENOSPC;
                                goto release_pages;
                        }

                        dn->nid = nids[i];
                        // 分配node page
                        npage[i] = f2fs_new_node_page(dn, noffset[i]);
                        if (IS_ERR(npage[i])) {
                                f2fs_alloc_nid_failed(sbi, nids[i]);
                                err = PTR_ERR(npage[i]);
                                goto release_pages;
                        }
                        //  如果i == 1,表示f2fs_inode->nid[0~1],即direct node,直接赋值到f2fs_inode->i_nid中
                        //  如果i != 1,表示parent是indirect node类型的,要赋值到indirect_node->nid中
                        set_nid(parent, offset[i - 1], nids[i], i == 1);
                        f2fs_alloc_nid_done(sbi, nids[i]);
                        done = true;
                } else if (mode == LOOKUP_NODE_RA && i == level && level > 1) {
                        npage[i] = f2fs_get_node_page_ra(parent, offset[i - 1]);
                        if (IS_ERR(npage[i])) {
                                err = PTR_ERR(npage[i]);
                                goto release_pages;
                        }
                        done = true;
                }

                if (i == 1) {
                        dn->inode_page_locked = false;
                        unlock_page(parent);
                } else {
                        f2fs_put_page(parent, 1);
                }

                if (!done) {
                        npage[i] = f2fs_get_node_page(sbi, nids[i]);
                        if (IS_ERR(npage[i])) {
                                err = PTR_ERR(npage[i]);
                                f2fs_put_page(npage[0], 0);
                                goto release_out;
                        }
                }
                if (i < level) {
                        parent = npage[i]; // 注意这里parent被递归地赋值,目的是处理direct node和indrect node的赋值问题
                        nids[i + 1] = get_nid(parent, offset[i], false);
                }
        }
        // 全部完成后,将结果赋值到dn,然后退出函数
        dn->nid = nids[level];
        dn->ofs_in_node = offset[level];
        dn->node_page = npage[level];
        dn->data_blkaddr = datablock_addr(dn->inode,
                                dn->node_page, dn->ofs_in_node);
        return 0;

release_pages:
        f2fs_put_page(parent, 1);
        if (i > 1)
                f2fs_put_page(npage[0], 0);
release_out:
        dn->inode_page = NULL;
        dn->node_page = NULL;
        if (err == -ENOENT) {
                dn->cur_level = i;
                dn->max_level = level;
                dn->ofs_in_node = offset[level];
        }
        return err;
}

get_node_path

根据在文件中的offset,确定出要读取的Page,是属于几级索引的哪个偏移地址。这个函数输出的是一个Level和两个数组。

这里offset和noffset分别表示block offset和node offset,返回的level表示寻址的深度,一共有4个深度,使用0~3表示: level=0: 表示可以直接在f2fs_inode找到物理地址 level=1: 表示可以在f2fs_inode->i_nid[0~1]对应的direct_node能够找到物理地址 level=2: 表示可以在f2fs_inode->i_nid[2~3]对应的indirect_node下的nid对应的direct_node能够找到物理地址 level=3: 表示只能在f2fs_inode->i_nid[4]对应indirect_node的nid对应的indirect_node的nid对应的direct_node才能找到地址

由于offset和noffset,表示的是物理地址寻址信息,分别表示block偏移和direct node偏移来表示,它们是长度为4的数组,代表不同level 0~3 的寻址信息。之后的函数可以通过offset和noffset将数据块计算出来。

例子1: 物理地址 位于f2fs_inode 例如我们要寻找page->index = 665的数据块所在的位置,显然655是位于f2fs_inode内,因此level=0,因此我们只需要看offset[0]以及noffset[0]的信息,如下图。offset[0] = 665表示这个数据块在当前direct node(注意: f2fs_inode也是direct node的一种)的位置;noffset[0]表示当前direct node是属于这个文件的第几个node,由于f2fs_inode是第一个node,所以noffset[0] = 0。

ini 复制代码
level = 0 // 可以直接在f2fs_inode找到物理地址
offset[0] = 665 // 由于level=0,因此我们只需要看offset[level]=offset[0]的信息,这里offset[0] = 665表示地址位于f2fs_inode->i_addr[665]
noffset[0] = 0 // 对于level=0的情况,即看noffset[0],因为level=0表示数据在唯一一个的f2fs_inode中,因此这里表示inode。

例子2: 物理地址 位于direct_node 例如我们要寻找page->index = 2113的数据块所在的位置,它位于第二个direct_node,所以level=1。我们只需要看offset[1]以及noffset[1]的信息,如下图。offset[1] = 172表示这个数据块在当前direct node的位置,即direct_node->addr[172];noffset[1]表示当前direct node是属于这个文件的第几个node,由于它位于第二个direct_node,前面还有一个f2fs_inode以及一个direct node,所以这是第三个node,因此noffset[1] = 2。

ini 复制代码
level = 1 // 表示可以在f2fs_inode->i_nid[0~1]对应的direct_node能够找到物理地址
offset[1] = 172 // 表示物理地址位于对应的node page的i_addr的第172个位置中,即direct_node->addr[172]
noffset[1] = 2 // 数据保存在总共第三个node中 (1个f2fs_inode,2个direct_node)

例子3: 物理地址 位于indirect_node 例如我们要寻找page->index = 4000的数据块所在的位置,它位于第1个indirect_node的第2个direct_node中,所以level=2。我们只需要看offset[2]以及noffset[2]的信息,如下图。offset[2] = 23表示这个数据块在当前direct node的位置;noffset[2]表示当前direct node是属于这个文件的第几个direct node,即这是第6个node。(1 * f2fs_inode + 2 * direct_node + 1 * indirect_node + 2 * direct node)。

ini 复制代码
level = 2 
offset[2] = 23
noffset[2] = 5

例子4: 物理地址 位于indirect_node再indiret_node中 (double indirect node) 例如我们要寻找page->index = 2075624的数据块所在的位置,它位于第一个double indirect_node的第一个indirect_node的第一个direct_node中,所以level=3。同理我们只需要看offset[3]以及noffset[3]的信息,如下,可以自己计算一下:

ini 复制代码
level = 3 
offset[3] = 17
noffset[3] = 2043
ini 复制代码
/*
 * The maximum depth is four.
 * Offset[0] will have raw inode offset.
 */
static int get_node_path(struct inode *inode, long block,
                                int offset[4], unsigned int noffset[4])
{
        const long direct_index = ADDRS_PER_INODE(inode);
        const long direct_blks = ADDRS_PER_BLOCK(inode);
        const long dptrs_per_blk = NIDS_PER_BLOCK;
        const long indirect_blks = ADDRS_PER_BLOCK(inode) * NIDS_PER_BLOCK;
        const long dindirect_blks = indirect_blks * NIDS_PER_BLOCK;
        int n = 0;
        int level = 0;

        noffset[0] = 0;

        if (block < direct_index) {
                offset[n] = block;
                goto got;
        }
        block -= direct_index;
        if (block < direct_blks) {
                offset[n++] = NODE_DIR1_BLOCK;
                noffset[n] = 1;
                offset[n] = block;
                level = 1;
                goto got;
        }
        block -= direct_blks;
        if (block < direct_blks) {
                offset[n++] = NODE_DIR2_BLOCK;
                noffset[n] = 2;
                offset[n] = block;
                level = 1;
                goto got;
        }
        block -= direct_blks;
        if (block < indirect_blks) {
                offset[n++] = NODE_IND1_BLOCK;
                noffset[n] = 3;
                offset[n++] = block / direct_blks;
                noffset[n] = 4 + offset[n - 1];
                offset[n] = block % direct_blks;
                level = 2;
                goto got;
        }
        block -= indirect_blks;
        if (block < indirect_blks) {
                offset[n++] = NODE_IND2_BLOCK;
                noffset[n] = 4 + dptrs_per_blk;
                offset[n++] = block / direct_blks;
                noffset[n] = 5 + dptrs_per_blk + offset[n - 1];
                offset[n] = block % direct_blks;
                level = 2;
                goto got;
        }
        block -= indirect_blks;
        if (block < dindirect_blks) {
                offset[n++] = NODE_DIND_BLOCK;
                noffset[n] = 5 + (dptrs_per_blk * 2);
                offset[n++] = block / indirect_blks;
                noffset[n] = 6 + (dptrs_per_blk * 2) +
                              offset[n - 1] * (dptrs_per_blk + 1);
                offset[n++] = (block / direct_blks) % dptrs_per_blk;
                noffset[n] = 7 + (dptrs_per_blk * 2) +
                              offset[n - 2] * (dptrs_per_blk + 1) +
                              offset[n - 1];
                offset[n] = block % direct_blks;
                level = 3;
                goto got;
        } else {
                return -E2BIG;
        }
got:
        return level;
}
相关推荐
许白掰1 小时前
Linux入门篇学习——Linux 工具之 make 工具和 makefile 文件
linux·运维·服务器·前端·学习·编辑器
longze_75 小时前
Ubuntu连接不上网络问题(Network is unreachable)
linux·服务器·ubuntu
Dirschs5 小时前
【Ubuntu22.04安装ROS Noetic】
linux·ubuntu·ros
qianshanxue115 小时前
ubuntu 操作记录
linux
AmosTian8 小时前
【系统与工具】Linux——Linux简介、安装、简单使用
linux·运维·服务器
这我可不懂11 小时前
Python 项目快速部署到 Linux 服务器基础教程
linux·服务器·python
车车不吃香菇11 小时前
java idea 本地debug linux服务
java·linux·intellij-idea
tan77º11 小时前
【Linux网络编程】Socket - TCP
linux·网络·c++·tcp/ip
kfepiza12 小时前
Linux的`if test`和`if [ ]中括号`的取反语法比较 笔记250709
linux·服务器·笔记·bash
CodeWithMe12 小时前
【Note】《深入理解Linux内核》 第十九章:深入理解 Linux 进程通信机制
linux·运维·php