virtio1.2 vfs 设备初始化、操作等

文件系统设备

VirtIO 文件系统设备提供文件系统访问。该设备要么直接管理一个文件系统,要么充当远程文件系统的网关。设备实现如何访问文件的细节由设备接口隐藏,从而允许多种使用场景。

与 VirtIO 块级存储设备(如 VirtIO 块和 SCSI)不同,VirtIO 文件系统设备提供对数据的文件级访问。设备接口基于 Linux 用户空间文件系统(FUSE)协议。这包括对文件系统遍历和访问其中的文件和目录的请求。协议细节由 FUSE 定义。

设备作为 FUSE 文件系统守护进程,驱动程序则作为挂载文件系统的 FUSE 客户端。VirtIO 文件系统设备提供了传输 FUSE 请求的机制,类似于传统 FUSE 应用程序中的 /dev/fuse

本节依赖于 FUSE 的定义。

5.11.1 设备 ID

5.11.2 Virtqueues

  • 0 hiprio
  • 1 通知队列
  • 2...n 请求队列

通知队列仅在协商了 VIRTIO_FS_F_NOTIFICATION 特性位时存在。

5.11.3 特性位

  • VIRTIO_FS_F_NOTIFICATION(0): 设备支持 FUSE 通知消息。通知队列为 virtqueue 1。

5.11.4 设备配置布局

struct virtio_fs_config {
    char tag[36];
    le32 num_request_queues;
    le32 notify_buf_size;
};
  • tag 与此文件系统关联的名称。tag 使用 UTF-8 编码,如果长度不足,则用 NUL 字节填充。如果编码字节占满整个字段,则此字段不以 NUL 终止。
  • num_request_queues 始终可用。表示设备暴露的请求 virtqueues 总数。每个 virtqueue 提供相同的功能,不同队列之间的请求处理顺序没有保证。使用多个队列旨在提高性能。
  • notify_buf_size 仅在协商了 VIRTIO_FS_F_NOTIFICATION 特性位时可用。表示通知队列中每个缓冲区所需的最小字节数。
5.11.4.1 驱动程序要求:设备配置布局
  • 驱动程序 不得 写入设备配置字段。
  • 驱动程序 可以 使用从 1 到 num_request_queues 任意数量的请求 virtqueues。
5.11.4.2 设备要求:设备配置布局
  • 设备 必须 num_request_queues 设置为 1 或更大。
  • 设备 必须 notify_buf_size 设置为足够容纳设备发出的任何 FUSE 通知消息的字节数。

5.11.5 设备初始化

  1. 驱动程序首先发现设备的 virtqueues。
  2. 如果协商了 VIRTIO_FS_F_NOTIFICATION 特性位,驱动程序使用缓冲区填充通知队列,以接收 FUSE 通知消息。
  3. 通过在一个请求 virtqueue 上发送 FUSE_INIT 请求启动 FUSE 会话,该请求由 FUSE 协议定义。所有 virtqueues 提供对同一 FUSE 会话的访问,因此无论可用队列数量如何,只需要一个 FUSE_INIT 请求。

5.11.6 设备操作

设备操作包括操作 virtqueues 以促进文件系统访问。

FUSE 请求类型如下:

  • 普通请求: 由驱动程序在请求队列上提供,并由设备使用。
  • 高优先级请求(FUSE_INTERRUPT、FUSE_FORGET 和 FUSE_BATCH_FORGET): 由驱动程序在高优先级队列(hiprio)上提供,以便即使请求队列已满,设备也能处理这些请求。
  • FUSE 通知消息: 如果协商了 VIRTIO_FS_F_NOTIFICATION 特性位,通知消息将在通知队列上接收。
5.11.6.1 设备操作:请求队列
  • 驱动程序在任意请求队列上排队普通请求。
  • 高优先级请求 放在请求队列上。
  • 设备以任意顺序处理请求。
  • 驱动程序负责确保排序约束,通过仅在前置请求已用后才提供依赖请求。

请求格式如下:

struct virtio_fs_req {
    // 设备可读部分
    struct fuse_in_header in;
    u8 datain[];
    // 设备可写部分
    struct fuse_out_header out;
    u8 dataout[];
};
  • in 所有类型 FUSE 请求的公共头。
  • datain 包含请求特定的数据(如果有)。与 FUSE 守护进程从 /dev/fuse 读取的数据相同。
  • out 所有类型 FUSE 请求的完成头。
  • dataout 包含请求特定的数据(如果有)。与 FUSE 守护进程写入 /dev/fuse 的数据相同。

示例: FUSE_READ 请求的完整布局如下:

struct virtio_fs_read_req {
    // 设备可读部分
    struct fuse_in_header in;
    union {
        struct fuse_read_in readin;
        u8 datain[sizeof(struct fuse_read_in)];
    };
    // 设备可写部分
    struct fuse_out_header out;
    u8 dataout[out.len - sizeof(struct fuse_out_header)];
};

FUSE 协议在设备操作中定义了请求类型及其内容。

字节序检测:
FUSE_PROTOCOL 会话的字节序通过检查驱动程序发送的 FUSE_INIT 请求中的 uint32_t in.opcode 字段来检测。这允许设备确定会话是小端字节序还是大端字节序。下一个 FUSE_INIT 消息终止当前会话,并开始一个可能更改字节序的新会话。

5.11.6.2 设备操作:高优先级队列

高优先级队列(hiprio)遵循与请求队列相同的请求格式。此队列仅包含 FUSE_INTERRUPTFUSE_FORGETFUSE_BATCH_FORGET 请求。

  • 中断和忘记请求具有比普通请求更高的优先级。 使用单独的高优先级队列确保即使请求队列已满,这些请求仍能被传递。
5.11.6.2.1 设备要求:设备操作:高优先级队列
  • 设备 不得 因请求队列上的活动而暂停处理高优先级队列。
  • 设备 可以 与请求队列并发处理高优先级队列。
5.11.6.2.2 驱动程序要求:设备操作:高优先级队列
  • 驱动程序 必须 只在高优先级队列上提交 FUSE_INTERRUPTFUSE_FORGET FUSE_BATCH_FORGET 请求。
  • 驱动程序 不得 在高优先级队列上提交普通请求。
  • 驱动程序 必须 预计请求队列与高优先级队列是并发处理的。
5.11.6.3 设备操作:通知队列

通知队列由驱动程序填充缓冲区,设备使用这些缓冲区发送 FUSE 通知消息。通知队列的缓冲区布局如下:

struct virtio_fs_notify {
    // 设备可写部分
    struct fuse_out_header out_hdr;
    char outarg[notify_buf_size - sizeof(struct fuse_out_header)];
};
  • outarg 包含取决于正在发送的通知类型的 FUSE 通知消息负载。

缓冲区耗尽情况:
如果驱动程序以比设备发出 FUSE 通知消息更慢的速度提供通知队列缓冲区,则 virtqueue 最终会变为空。对此的响应取决于 FUSE 通知消息类型。支持的 FUSE 通知消息类型包括:

  • FUSE_NOTIFY_LOCK 当缓冲区再次可用时传递。设备有一定数量的资源用于锁请求。如果设备资源耗尽,新锁请求将以 ENOLCK 失败。
5.11.6.3.1 驱动程序要求:设备操作:通知队列
  • 驱动程序 必须 提供至少 notify_buf_size 字节的缓冲区。
  • 驱动程序 应当 足够快速地补充通知队列缓冲区,以确保始终至少有一个可用缓冲区。

5.11.6.4 设备操作:DAX 窗口

FUSE_READFUSE_WRITE 请求在驱动程序提供的缓冲区和设备之间传输文件内容。在不希望进行数据传输的情况下,设备可以将文件内容映射到共享内存区域中的 DAX 窗口。然后,驱动程序直接访问设备拥有的内存中的文件内容,而无需数据传输。

DAX 窗口 是访问文件内容的替代机制。FUSE_READ/FUSE_WRITE 请求和 DAX 窗口访问可以同时进行。提供 DAX 窗口对设备是可选的。驱动程序使用 DAX 窗口也是可选的。

共享内存区域 ID 0 被称为 DAX 窗口。驱动程序像常规 RAM 一样映射此共享内存区域,并使用写回缓存。除非该范围有映射,否则 DAX 窗口的内容未定义。

映射文件范围:
驱动程序使用 FUSE_SETUPMAPPING 请求将文件范围映射到 DAX 窗口。FUSE_SETUPMAPPINGFUSE_REMOVEMAPPING 请求的对齐约束在 FUSE_INIT 协商期间传达。

  • 完美重叠: 如果 FUSE_SETUPMAPPING 请求与先前的映射完全重叠,则替换先前的映射。
  • 部分重叠: 如果部分重叠,则将先前的映射拆分为一个或两个较小的映射。
  • 部分解绑: 当映射部分被解绑时,也会拆分为一个或两个较小的映射。

资源消耗和失败处理:
建立新映射或拆分现有映射会消耗资源。如果设备资源耗尽,则 FUSE_SETUPMAPPING 请求失败,直到资源通过 FUSE_REMOVEMAPPING 再次可用。

访问和持久性保证:
成功完成 FUSE_SETUPMAPPING 后,文件范围在由驱动程序在请求中提供的偏移量处可从 DAX 窗口访问。使用 FUSE_REMOVEMAPPING 请求移除映射。

数据持久性:
设备仅在驱动程序通过 FUSE_FSYNC 请求使写入可用后,才保证数据的持久性。这适用于通过 FUSE_SETUPMAPPING 或 DAX 窗口进行的写入。

5.11.6.4.1 设备要求:设备操作:DAX 窗口
  • 设备 可以 提供 DAX 窗口以实现对文件内容的内存映射访问。
  • 如果存在,DAX 窗口 必须 是共享内存区域 ID 0。
  • 设备 必须 支持 FUSE_READ FUSE_WRITE 请求,无论是否使用 DAX 窗口。
  • 设备 必须 允许 DAX 窗口内的映射完全或部分重叠。
  • 设备 必须 拒绝超出 DAX 窗口末尾的映射。
  • 数据持久性保证:
    • 设备 必须 在报告写入完成(案例1和2)或刷新完成(案例3)之前,确保稳定写入已提交到持久性设备后端存储。
  • 寿命指标:
    • 如果设备支持提供寿命指标(如 eMMC 或 UFS 持久存储),设备应当提供 VIRTIO_BLK_F_LIFETIME 特性位。
    • 如果设备未协商 VIRTIO_BLK_F_LIFETIME 特性位,或者设备不支持相关寿命指标,则 不得 提供该特性位。
    • 设备 必须 不发送规范中定义的保留值。
5.11.6.4.2 驱动程序要求:设备操作:DAX 窗口
  • 驱动程序 应当 准备好发现共享内存区域 ID 0 缺失,并回退到 FUSE_READ FUSE_WRITE 请求。
  • 驱动程序 可以 同时使用 FUSE_READ/FUSE_WRITE 请求和 DAX 窗口访问文件内容。
  • 驱动程序 不得 访问未映射的 DAX 窗口区域。

5.11.6.5 安全考虑

设备提供访问包含一个或多个 POSIX 用户 ID 和组 ID 所拥有的文件的文件系统。设备 没有 区分通过驱动程序发起请求的用户的安全机制。因此,设备 接受 驱动程序提供的 POSIX 用户 ID 和组 ID,安全性由驱动程序而非设备执行。然而,设备 可以 实现 POSIX 用户 ID 和组 ID 映射或白名单,以控制驱动程序可用的所有权和访问权限。

特殊文件的安全风险:

  • 特殊文件(包括设备节点和 setuid 可执行文件):
    这些属性由文件类型和模式定义,驱动程序在创建新文件或稍后更改时设置。如果文件系统与另一台机器共享,���些特殊文件会带来安全风险。攻击者可以通过共享文件系统,在其他机器上的未授权用户提升权限。

解决方案:

  • 挂载选项: 使用操作系统的挂载选项忽略特殊文件。
  • 设备限制: 设备 可以 实现对特殊文件的限制,拒绝其创建。

多用户系统的安全性:

  • 竞争条件: 在多个机器共享文件系统时,可能发生符号链接竞态条件、文件系统容量耗尽以及覆盖或删除其他用户使用的文件。这些问题通常在文件系统管理层面通过仅向互相信任的用户提供共享访问来管理。

时序侧信道攻击(Timing Side-Channel Attacks):

  • 攻击原理: 多台机器共享访问文件系统时,通过测量访问文件内容或文件系统元数据的延迟,可以推断其他机器是否也访问了相同的信息。
  • DAX 窗口的风险: DAX 窗口直接访问文件内容,因此是此类攻击的潜在目标。
  • 防护措施: 最安全的方法是避免在不受信任的机器之间共享文件系统。

5.11.6.6 实时迁移注意事项

当驱动程序迁移到新设备时,需要考虑 FUSE 会话及其状态。必须保持 FUSE inode 编号(也称为 nodeids)和 fh 值的连续性,以确保驱动程序能够继续运行而不受干扰。

可以通过传递状态或将请求从新设备重定向到状态所在的旧设备来在实时迁移过程中维护 FUSE 会话。实现这一点的具体细节取决于具体的实现,并且在设备接口层面上不可见。

保持由 FUSE_INIT 协商的版本和特性信息是必要的,以确保在实时迁移过程中,驱动程序不会察觉到 FUSE 协议特性的任何变化。FUSE_INIT 信息构成了在实时迁移过程中需要传输的 FUSE 会话状态的一部分。


示例代码结构

以下是 virtio_blk_req 和相关结构体的示例代码:

struct virtio_blk_req {
    le32 type;
    le32 reserved;
    le64 sector;
    u8 data[];
    u8 status;
};
struct virtio_blk_discard_write_zeroes {
    le64 sector;
    le32 num_sectors;
    struct {
        le32 unmap:1;
        le32 reserved:31;
    } flags;
};

SCSI 命令请求示例:

/* 所有字段均采用来宾的本机字节序。 */
struct virtio_scsi_pc_req {
    u32 type;
    u32 ioprio;
    u64 sector;
    u8 cmd[];
    u8 data[][512];
    #define SCSI_SENSE_BUFFERSIZE 96
    u8 sense[SCSI_SENSE_BUFFERSIZE];
    u32 errors;
    u32 data_len; // 已弃用,应由驱动程序忽略
    u32 sense_len;
    u32 residual;
    u8 status;
};

请求类型也可以是 SCSI 包命令(VIRTIO_BLK_T_SCSI_CMDVIRTIO_BLK_T_SCSI_CMD_OUT)。这两种类型是等效的,设备 不区分 它们:

#define VIRTIO_BLK_T_SCSI_CMD 2
#define VIRTIO_BLK_T_SCSI_CMD_OUT 3
  • cmd 字段: 仅存在于 SCSI 包命令请求中,用于指示要执行的命令。此字段 必须 位于单个独立的设备可读缓冲区中;命令长度可从此缓冲区的总长度中推导出来。
  • sense 字段: 仅存在于 SCSI 包命令请求中,用于指示 SCSI 感测数据的缓冲区。
  • data_len 字段: 仅存在于 SCSI 包命令请求中,此字段已弃用,驱动程序 应当忽略。历史上,设备将数据长度写入此字段。
  • sense_len 字段: 仅存在于 SCSI 包命令请求中,用于指示实际写入 sense 缓冲区的字节数。
  • residual 字段: 仅存在于 SCSI 包命令请求中,用于指示剩余大小,计算方式为数据长度减去实际传输的字节数。

总结

上述章节详细描述了 VirtIO 文件系统设备的设备配置布局、初始化过程以及设备操作方式,包括请求队列的管理、高优先级请求的处理、通知队列的使用、DAX 窗口的配置与访问、安全考虑以及传统接口下的特殊要求。

关键点总结:

  • 文件级访问: VirtIO 文件系统设备提供文件级访问,与块级设备不同,采用 FUSE 协议。
  • Virtqueues 管理:
    • 请求队列: 用于处理普通请求。
    • 高优先级队列(hiprio): 用于处理紧急请求,如中断和忘记请求,确保这些请求即使在请求队列已满时也能被处理。
    • 通知队列: 用于接收设备发出的 FUSE 通知消息。
  • DAX 窗口: 提供对文件内容的内存映射访问,允许驱动程序直接访问设备内存中的文件内容,减少数据传输开销。
  • 安全性:
    • 用户和组 ID 管理: 设备接受驱动程序提供的用户和组 ID,安全性主要由驱动程序负责。
    • 特殊文件风险: 需要通过挂载选项或设备限制来管理特殊文件(如设备节点和 setuid 可执行文件)的创建。
    • 时序侧信道攻击: 共享文件系统可能受到基于访问延迟的侧信道攻击,DAX 窗口尤其容易成为攻击目标。避免在不受信任的机器之间共享文件系统是最安全的做法。
  • 多队列和 RSS:
    • 多队列模式: 通过自动接收引导和接收端扩展(RSS)提高网络性能,支持多个发送和接收 virtqueues。
    • RSS(接收端扩展): 允许设备根据哈希计算将传入数据包分配到不同的接收队列,提高并发性能。
  • 传统接口要求:
    • 字节序格式化: 在传统接口下,驱动程序和设备必须根据来宾的本机字节序格式化结构体字段。
    • 帧格式要求: 需要遵循特定的帧格式,确保与旧驱动程序和设备的兼容性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值