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;
}
相关推荐
哎呦喂-ll38 分钟前
Linux进阶:环境变量
linux
Rverdoser39 分钟前
Linux环境开启MongoDB的安全认证
linux·安全·mongodb
PigeonGuan1 小时前
【jupyter】linux服务器怎么使用jupyter
linux·ide·jupyter
东华果汁哥1 小时前
【linux 免密登录】快速设置kafka01、kafka02、kafka03 三台机器免密登录
linux·运维·服务器
咖喱鱼蛋2 小时前
Ubuntu安装Electron环境
linux·ubuntu·electron
ac.char2 小时前
在 Ubuntu 系统上安装 npm 环境以及 nvm(Node Version Manager)
linux·ubuntu·npm
肖永威2 小时前
CentOS环境上离线安装python3及相关包
linux·运维·机器学习·centos
tian2kong2 小时前
Centos 7 修改YUM镜像源地址为阿里云镜像地址
linux·阿里云·centos
布鲁格若门2 小时前
CentOS 7 桌面版安装 cuda 12.4
linux·运维·centos·cuda