存储技术实践笔记:从用户态ioctl到内核态bio,深入NVMe磁盘的两种编程接口
1. 从零开始为什么需要两种方式操作NVMe磁盘如果你刚开始接触存储开发或者想自己写点代码直接跟NVMe硬盘“对话”你可能会有点懵。市面上资料要么太理论一上来就是NVMe协议栈、PCIe通道要么就是直接甩给你一堆内核源码看得人头大。我自己刚开始搞这块的时候也踩了不少坑今天我就用最“人话”的方式跟你聊聊两种最核心的实操方法用户态的ioctl和内核态的bio。简单来说这就像你要跟一个仓库NVMe磁盘打交道。你有两种身份可以选择仓库访客用户态你通过仓库管理员操作系统内核提供的固定窗口ioctl系统调用递交取货单struct nvme_user_io。管理员帮你完成所有复杂的内部操作你只需要填好单子就行。这种方式简单、安全但灵活性差所有操作都要经过管理员转手。仓库管理员内核态你自己就是管理员拥有仓库的内部通行证。你可以直接进入仓库按照内部流程struct bio自己安排货物的存取。这种方式权力大、效率高可以直接优化搬运路线但门槛也高你得非常熟悉仓库的内部规则写错了可能把整个仓库搞乱。为什么我们要关心这两种方式因为选择决定了你能做什么、做多快、以及有多麻烦。比如你想写一个高性能的数据库或者定制自己的文件系统可能就需要绕过“访客通道”直接使用内核模块来获得极致的控制权和性能。而如果你只是想写个工具安全地读写磁盘的某个特定区域那用户态的ioctl就足够好用还不用担心把系统搞崩。这篇文章我就带你亲手走通这两条路。我们会从最基础的准备环境开始一步步写代码看看怎么用ioctl读写磁盘再看看怎么编译一个内核模块用bio直接提交请求。我会把过程中我踩过的坑、需要注意的细节都告诉你目标是让你看完就能动手试起来真正理解这两种接口的“手感”差异。2. 动手前的准备认识你的NVMe磁盘在写任何代码之前我们得先把“实验场地”准备好。这里假设你已经在虚拟机里添加了一块NVMe磁盘比如在VMware里新增一个NVMe控制器。我们首先得找到它、认识它并把它清理干净就像在一块白板上画画。2.1 找到并初始化磁盘首先我们得确认磁盘已经被系统识别。打开终端用lsblk命令看看块设备列表。$ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS ... nvme0n1 259:0 0 20G 0 disk看到nvme0n1了吗这就是我们新加的NVMe磁盘。nvme0表示第一个NVMe控制器n1表示该控制器下的第一个命名空间Namespace。接下来为了后续实验清晰我们最好把它格式化成一种常见的文件系统比如ext4。这步操作会写入超级块等元数据方便我们后续用cat等命令查看磁盘原始内容虽然看到的是乱码但能证明磁盘可读。# 注意格式化会清空磁盘所有数据请确认操作对象正确。 $ sudo mkfs.ext4 /dev/nvme0n1格式化之后磁盘里其实已经有文件系统的元数据了为了得到一个绝对“干净”的盘用于我们后续的原始读写测试我习惯用dd命令把它填零。这相当于把整个磁盘的每一个字节都写成0。# 使用 /dev/zero 这个特殊的“零生成器”作为输入(if)输出到我们的磁盘(of) # bs5M 表示每次读写5兆字节count4096表示进行4096次总共写入20G数据 $ sudo dd if/dev/zero of/dev/nvme0n1 bs5M count4096 40960 records in 40960 records out 21474836480 bytes (21 GB, 20 GiB) copied, 44.0386 s, 488 MB/s执行完你的NVMe磁盘就变成了一块“白板”里面全是0。这样我们后面自己写入的数据就会非常清晰不会被残留数据干扰。2.2 了解NVMe磁盘的关键参数磁盘准备好了我们还得了解它的“规格”。一个关键参数是逻辑块大小。你可以把它理解为磁盘管理数据的最小单元就像仓库里每个货架格子的大小。我们之后所有的读写操作地址SLBA和长度NBLOCKS都是基于这个逻辑块来计算的。怎么查看呢安装nvme-cli这个强大的工具包。$ sudo apt install nvme-cli然后用nvme list命令查看磁盘信息。$ sudo nvme list Node SN Model Namespace Usage Format FW Rev ---------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- -------- /dev/nvme0n1 VMware NVME_0000 VMware Virtual NVMe Disk 1 21.47 GB / 21.47 GB 512 B 0 B 1.3看Format这一列512 B 0 B。这告诉我们这块磁盘的逻辑块大小是512字节并且没有额外的元数据区。这个512字节非常重要它意味着你读写数据的起始地址SLBA必须是512字节的整数倍。你读写数据的长度NBLOCKS * 512字节也最好是512字节的整数倍。 很多初学者在这里栽跟头比如申请了300字节的缓冲区就想写结果不是报错就是数据不对。记住在块设备的世界里一切操作都以“块”为单位。3. 用户态利器用ioctl和nvme_user_io读写磁盘现在进入正题。第一种方法我们以普通用户程序的身份通过操作系统提供的“后门”——ioctl系统调用来操作磁盘。这是最常用、最安全的方式。3.1 理解核心结构体struct nvme_user_io你可以把struct nvme_user_io看作是一张发给内核的“标准化工作指令单”。它定义在linux/nvme_ioctl.h头文件里。我们写程序时就是填充这张单子的各个字段然后通过ioctl递交给内核内核再翻译成真正的NVMe命令发给硬盘。这张“指令单”里有哪些关键字段呢我挑几个最常用的给你解释opcode操作码。告诉磁盘你要干嘛。比如0x01代表写NVME_CMD_WRITE0x02代表读NVME_CMD_READ。slba起始逻辑块地址。你想从磁盘的哪个“货架格子”开始操作注意单位是“块”不是字节。比如slba 1表示从第1块开始即字节偏移量 1 * 512 512 字节处。nblocks要操作的逻辑块数量。你想连续操作几个格子nblocks 1表示操作1个块512字节。addr数据缓冲区的用户空间地址。对于写操作这是你要写入的数据的地址对于读操作这是内核存放读出数据的目标地址。metadata元数据指针高级功能我们基础读写可以先不管。理解这几个字段你就能完成最基本的读写了。下面我们写个完整的程序来感受一下。3.2 实战编写一个用户态读写程序我们来写一个C程序实现向磁盘的第一个逻辑块slba0写入一串字符然后再把它读回来。#include stdio.h #include stdlib.h #include string.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include linux/nvme_ioctl.h // 关键头文件包含结构体和IOCTL命令定义 #include sys/ioctl.h #define NVME_DEVICE /dev/nvme0n1 int main() { int fd; char write_buffer[512] Hello, NVMe World! This is block 0.; // 准备写入的数据 char read_buffer[512] {0}; // 准备读取数据的缓冲区 // 1. 打开NVMe设备文件就像打开一个普通文件一样 fd open(NVME_DEVICE, O_RDWR); if (fd 0) { perror(Failed to open NVMe device); return -1; } // 2. 准备我们的“指令单” struct nvme_user_io io; memset(io, 0, sizeof(io)); // 清空结构体避免残留数据干扰 // 填写写操作指令单 io.opcode 0x01; // NVMe写命令 io.flags 0; io.control 0; io.nblocks 0; // 注意nblocks表示块数-10代表1个块。这是协议规定容易踩坑 io.addr (__u64)write_buffer; // 数据从哪来 io.slba 0; // 写到哪去逻辑块0 io.dsmgmt 0; io.reftag 0; io.apptag 0; io.appmask 0; // 3. 提交写请求 if (ioctl(fd, NVME_IOCTL_SUBMIT_IO, io) -1) { perror(Failed to submit WRITE IO); close(fd); return 1; } printf(Write operation submitted successfully.\n); // 4. 准备读操作指令单复用结构体修改关键字段 memset(read_buffer, 0, sizeof(read_buffer)); // 清空读缓冲区 memset(io, 0, sizeof(io)); // 重新初始化指令单 io.opcode 0x02; // NVMe读命令 io.nblocks 0; // 读1个块 io.addr (__u64)read_buffer; // 数据放到哪 io.slba 0; // 从哪读逻辑块0 // 5. 提交读请求 if (ioctl(fd, NVME_IOCTL_SUBMIT_IO, io) -1) { perror(Failed to submit READ IO); close(fd); return 1; } // 6. 打印读回的数据 printf(Read from block 0: %s\n, read_buffer); close(fd); return 0; }编译和运行$ gcc -o nvme_ioctl_test nvme_ioctl_test.c $ sudo ./nvme_ioctl_test Write operation submitted successfully. Read from block 0: Hello, NVMe World! This is block 0.看到成功打印的信息了吗这说明我们通过用户态程序成功地指挥内核让NVMe磁盘完成了一次写入和读取。这个过程完全在操作系统的安全管控之下。3.3 深入探索slba与块大小的关系上面我们固定写在了slba0的位置。现在我们来做个实验修改代码分别写入slba0和slba1然后用cat命令直接查看磁盘的原始内容看看会发生什么。首先我们修改写程序连续执行两次第一次写slba0第二次写slba1写入不同的字符串。为了简化你可以写两个程序或者在一个程序里用循环改slba。假设我们写完后用cat /dev/nvme0n1 | head -c 1024查看前1024字节。你会发现输出类似这样Hello from block 0.Hello from block 1.两个字符串是紧挨着的但是我们明明写在了不同的逻辑块地址0和1。这是因为逻辑块地址是索引每个索引对应512字节的空间。slba0占据了磁盘最开始的512字节即使我们的字符串很短它也占用了那个“格子”。slba1则从第513字节开始。当我们用cat线性地读取磁盘原始数据时它们就按顺序显示出来了。这引出了一个重要概念块设备的所有操作都是按块对齐的。即使你只写1个字节内核和磁盘驱动也会读写整个块512字节。用户态的ioctl接口帮你处理了缓冲区的对齐等问题但如果你传入的缓冲区地址不对齐或者长度不是块大小的整数倍性能会下降甚至在某些配置下会出错。3.4 使用nvme-cli工具验证我们写的程序对不对除了自己读回来验证还可以用nvme-cli这个官方工具来交叉验证。比如我们可以用nvme read命令直接读取磁盘的某个逻辑块。# 从逻辑块0开始读取1个块-c 0 同样表示1个块块大小为512字节-z 512 $ sudo nvme read /dev/nvme0n1 -s 0 -c 0 -z 512 NVMe Read: Success 输出的数据可能是十六进制或ASCII格式...这个工具非常强大不仅能读写还能查看磁盘的SMART信息nvme smart-log、识别控制器nvme id-ctrl等。当你调试自己的程序时用这些工具做对比能快速定位问题是出在你的代码逻辑还是对协议的理解有偏差。4. 进入内核用block_device和bio直接对话磁盘用户态的ioctl很方便但它有一层不可逾越的“墙”——系统调用和内核上下文切换的开销。对于延迟极其敏感的应用比如超高速缓存、定制文件系统或者需要实现特殊I/O调度策略时我们就需要进入内核态直接使用块设备层提供的接口。这里的主角是struct block_device和struct bio。4.1 内核模块编程基础在内核里编程和我们写普通C程序感觉完全不同。你需要编译一个内核模块.ko文件然后用insmod命令把它动态加载到运行中的内核里。这给了你巨大的权力也带来了巨大的风险一个空指针错误就可能导致内核崩溃Kernel Panic。首先你需要一个基本的内核模块框架。下面是一个最简单的Makefile和模块源码示例。Makefile:obj-m nvme_kernel.o # 你的模块源文件是 nvme_kernel.c KVERSION $(shell uname -r) all: make -C /lib/modules/$(KVERSION)/build M$(PWD) modules clean: make -C /lib/modules/$(KVERSION)/build M$(PWD) cleannvme_kernel.c (骨架):#include linux/module.h #include linux/kernel.h static int __init nvme_kernel_init(void) { printk(KERN_INFO NVMe Kernel Module: Hello World!\n); // 你的初始化代码将在这里 return 0; } static void __exit nvme_kernel_exit(void) { printk(KERN_INFO NVMe Kernel Module: Goodbye!\n); // 你的清理代码将在这里 } module_init(nvme_kernel_init); module_exit(nvme_kernel_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(A simple NVMe kernel module);编译并加载试试$ make $ sudo insmod nvme_kernel.ko $ dmesg | tail -2 # 查看内核日志应该能看到我们的打印信息 [ 1234.567890] NVMe Kernel Module: Hello World! $ sudo rmmod nvme_kernel $ dmesg | tail -2 [ 1235.678901] NVMe Kernel Module: Goodbye!如果能看到这些打印恭喜你你的内核开发环境基本没问题了4.2 认识核心数据结构block_device 与 bio在内核中一个块设备比如我们的/dev/nvme0n1用struct block_device来表示。你可以把它理解为内核中那个磁盘对象的“句柄”。我们要操作磁盘首先得获取到这个句柄。struct bioBlock I/O是内核中I/O请求的承载者。它描述了一次块I/O操作从哪里读/写扇区号、数据在哪一组内存页、操作类型读/写/丢弃等。你可以把bio看作一个更底层、更灵活的“工作指令单”它直接由内核的块设备层处理不再经过ioctl那样的通用接口转换。工作流程简化版获取block_device通过设备路径如/dev/nvme0n1拿到块设备对象。创建并组装bio申请一个bio结构体设置操作类型、起始扇区、关联的块设备。关联数据缓冲区为数据分配内核内存页struct page将用户数据复制进去写操作然后把内存页添加到bio中。提交请求将组装好的bio提交给块设备层submit_bio_wait并等待其完成。处理结果对于读操作数据被从磁盘读到我们关联的内存页中我们需要再把它复制出来。4.3 实战编写内核模块进行读写现在我们把骨架填充起来实现内核模块对NVMe磁盘的读写。为了清晰我们把读写操作封装成函数。#include linux/module.h #include linux/kernel.h #include linux/bio.h // struct bio #include linux/blkdev.h // struct block_device, blkdev_get_by_path #include linux/fs.h // FMODE_READ, FMODE_WRITE #define DISK_NAME /dev/nvme0n1 static struct block_device *nvme_bdev NULL; // 静态全局变量存储块设备对象 // 内核模块写函数 static int nvme_kernel_write(const char *data, int len, sector_t sector) { struct bio *bio; struct page *page; int ret 0; // 1. 分配一个bionr_vecs1表示我们只用单个内存页来承载数据 bio bio_alloc(GFP_KERNEL, 1); if (!bio) { printk(KERN_ERR Failed to allocate bio for write\n); return -ENOMEM; } // 2. 设置bio写操作起始扇区关联的块设备 bio-bi_opf REQ_OP_WRITE; // 操作标志写 bio-bi_iter.bi_sector sector; // 起始扇区号LBA bio_set_dev(bio, nvme_bdev); // 关联块设备 // 3. 分配一个物理内存页来存放要写的数据 page alloc_page(GFP_KERNEL); if (!page) { printk(KERN_ERR Failed to allocate page for write\n); ret -ENOMEM; goto out_bio_put; } // 4. 将用户数据复制到内核页中 // 注意这里简化了实际应考虑数据长度超过一页、数据对齐等问题 memcpy(page_address(page), data, len); // 5. 将这个内存页添加到bio中。PAGE_SIZE是内存页大小通常4096字节 // 这里假设len PAGE_SIZE且我们写一个扇区512字节。实际需要计算。 if (bio_add_page(bio, page, len, 0) 0) { printk(KERN_ERR Failed to add page to bio\n); ret -EIO; goto out_page_free; } // 6. 提交bio并等待完成。这是同步操作会阻塞直到磁盘写完。 submit_bio_wait(bio); printk(KERN_INFO Kernel write to sector %llu succeeded.\n, (unsigned long long)sector); out_page_free: __free_page(page); // 释放内存页 out_bio_put: bio_put(bio); // 释放bio return ret; } // 内核模块读函数 static int nvme_kernel_read(char *buffer, int len, sector_t sector) { struct bio *bio; struct page *page; int ret 0; bio bio_alloc(GFP_KERNEL, 1); if (!bio) { printk(KERN_ERR Failed to allocate bio for read\n); return -ENOMEM; } // 读操作不需要设置bi_opf为WRITE默认为READ bio-bi_iter.bi_sector sector; bio_set_dev(bio, nvme_bdev); page alloc_page(GFP_KERNEL); if (!page) { printk(KERN_ERR Failed to allocate page for read\n); ret -ENOMEM; goto out_bio_put; } // 将空页添加到bio磁盘数据会读到这里 if (bio_add_page(bio, page, len, 0) 0) { printk(KERN_ERR Failed to add page to bio\n); ret -EIO; goto out_page_free; } submit_bio_wait(bio); // 提交并等待读完成 // 将内核页中的数据复制到输出缓冲区 memcpy(buffer, page_address(page), len); printk(KERN_INFO Kernel read from sector %llu succeeded.\n, (unsigned long long)sector); out_page_free: __free_page(page); out_bio_put: bio_put(bio); return ret; } // 模块初始化函数 static int __init nvme_kernel_init(void) { char write_data[] Data written from kernel module at sector 8!; char read_buffer[512] {0}; printk(KERN_INFO NVMe Kernel Module: Initializing...\n); // 核心步骤通过设备路径获取块设备对象 // FMODE_READ | FMODE_WRITE 表示以读写模式打开 // 第三个参数NULL表示不需要模块所有者对于动态获取通常填NULL nvme_bdev blkdev_get_by_path(DISK_NAME, FMODE_READ | FMODE_WRITE, NULL); if (IS_ERR(nvme_bdev)) { printk(KERN_ERR Failed to get block device for %s\n, DISK_NAME); return PTR_ERR(nvme_bdev); } // 测试向扇区8LBA 8即 8*512 4096 字节偏移写入数据 if (nvme_kernel_write(write_data, strlen(write_data), 8) ! 0) { printk(KERN_ERR Kernel write test failed\n); blkdev_put(nvme_bdev, FMODE_READ | FMODE_WRITE); return -EIO; } // 测试从扇区8读取数据 if (nvme_kernel_read(read_buffer, sizeof(read_buffer), 8) ! 0) { printk(KERN_ERR Kernel read test failed\n); blkdev_put(nvme_bdev, FMODE_READ | FMODE_WRITE); return -EIO; } printk(KERN_INFO Data read back: %s\n, read_buffer); return 0; // 初始化成功 } // 模块退出函数 static void __exit nvme_kernel_exit(void) { if (nvme_bdev) { // 必须释放块设备引用 blkdev_put(nvme_bdev, FMODE_READ | FMODE_WRITE); nvme_bdev NULL; } printk(KERN_INFO NVMe Kernel Module: Exited.\n); } module_init(nvme_kernel_init); module_exit(nvme_kernel_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(A kernel module to read/write NVMe disk using bio);编译、加载与验证编译将上述代码保存为nvme_kernel.c与之前的Makefile放在同一目录执行make。加载模块sudo insmod nvme_kernel.ko。查看结果dmesg | tail -10。你应该能看到类似以下的成功信息[ 3456.789012] NVMe Kernel Module: Initializing... [ 3456.789123] Kernel write to sector 8 succeeded. [ 3456.789456] Kernel read from sector 8 succeeded. [ 3456.789789] Data read back: Data written from kernel module at sector 8! [ 3456.790000] NVMe Kernel Module: Exited.用用户态工具验证我们可以用dd命令跳过前8个扇区8*5124096字节读取512字节看看内容是否一致。$ sudo dd if/dev/nvme0n1 bs512 skip8 count1 statusnone | cat -v Data written from kernel module at sector 8!看到我们写入的字符串了吗这说明内核模块成功绕过了文件系统直接对磁盘扇区进行了读写4.4 内核编程的“坑”与注意事项内核编程比用户态编程凶险得多这里有几个我踩过的坑你一定要留心内存管理内核用的是kmalloc,vmalloc,alloc_page不是malloc。分配失败是常事必须检查返回值。用完的内存必须释放否则就是内存泄漏。并发与锁我们的示例代码没有考虑并发。如果多个进程同时调用你的模块函数可能会对同一个bio或缓冲区进行操作导致数据错乱或崩溃。实际项目中需要考虑使用锁如mutex来保护共享资源。错误处理内核函数失败返回的是错误码负数不像用户态用-1和errno。IS_ERR()和PTR_ERR()是处理返回指针类型函数错误的常用宏。扇区与字节内核块设备层普遍使用512字节扇区作为寻址单位即使你的NVMe磁盘物理扇区是4K高级格式。bio-bi_iter.bi_sector的单位就是512字节扇区。这是一个历史遗留的兼容层务必注意。模块引用计数blkdev_get_by_path会增加块设备的引用计数退出时必须用blkdev_put配对减少否则设备无法正常卸载。5. 两种接口的深度对比与选型指南走通了两种技术路径是时候坐下来好好对比一下看看它们各自适合什么场景。这就像选工具用对了事半功倍用错了处处碰壁。特性维度用户态 ioctl (struct nvme_user_io)内核态 bio (struct block_device/bio)编程复杂度低。标准C程序使用系统调用。调试方便gdb崩溃只影响自身进程。高。需要编写、编译、加载内核模块。调试困难printk, kdb错误可能导致系统崩溃。性能中。需要经过系统调用陷入内核存在上下文切换开销。对于小IO、高频率操作开销占比大。高。直接在内核块设备层提交请求路径最短无上下文切换。可实现零拷贝、轮询等高级优化。灵活性低。只能使用内核NVMe驱动暴露的固定接口如NVME_IOCTL_SUBMIT_IO无法定制I/O调度、合并、完成处理等逻辑。极高。可以深度定制I/O栈的几乎所有环节自定义I/O调度算法、实现复杂的请求合并拆分、介入完成中断处理、实现自己的块设备驱动等。安全性高。进程运行在用户空间受操作系统完整保护。错误的操作通常只会导致进程被杀死或返回错误。低。模块运行在内核空间拥有最高权限。一个错误的指针解引用就可能引发内核Oops或Panic导致整个系统宕机。功能范围受限。主要支持标准NVMe命令的读写、管理如identify, get-log。对于厂商特定命令、非标准操作支持有限。全面。可以访问所有内核块设备层和NVMe驱动层的内部数据结构与函数能实现任何底层操作。适用场景1. 磁盘管理工具如分区、格式化、健康检查。2. 需要安全、稳定读写的应用程序。3. 原型验证和快速测试。4. 对极致性能要求不高的常规存储访问。1. 高性能数据库如定制存储引擎。2. 新型文件系统的研发。3. 存储虚拟化、去重、压缩等中间件。4. 需要对硬件进行极致优化的专用存储设备。5. 研究和修改Linux块设备层或NVMe驱动本身。怎么选我的经验是如果你是应用开发者只是想安全可靠地读写磁盘或者开发系统工具无脑选用户态ioctl。它的稳定性和安全性优势太大了性能对于绝大多数应用也完全足够。如果你是存储系统研究员或底层开发工程师需要研究I/O路径、实现新的调度算法、或者为特定硬件做深度优化那么必须进入内核态使用bio接口。这是你唯一能接触到真实I/O请求链路的途径。如果你在做一个对延迟要求极高的产品比如金融交易系统、实时数据分析平台并且你有足够深厚的内核开发经验和测试资源可以谨慎考虑使用内核模块方案来榨干最后一点性能。否则优化用户态程序、使用异步IO如libaio、甚至选用更快的硬件可能是性价比更高的选择。6. 从读写到管理下一步可以做什么现在你已经掌握了直接操作NVMe磁盘的两种基本方法。但这仅仅是开始就像学会了用锤子和锯子离造出一把椅子还有很远。基于这两种能力你可以向更深处探索如果你选择用户态路线深入NVMe协议struct nvme_user_io只是冰山一角。尝试使用struct nvme_passthru_cmd来发送更复杂的NVMe命令比如获取详细的识别信息、读取SMART日志、管理命名空间等。构建磁盘工具你可以写一个自己的简易nvme-cli实现格式化、安全擦除、固件更新等功能。实现简单文件系统这听起来很吓人但其实可以从最简单的“裸盘文件管理器”开始。比如在磁盘开头设计一个超级块来记录文件索引然后实现文件的创建、读写、删除。这是一个绝佳的学习项目能让你深刻理解文件系统是如何在块设备之上组织数据的。如果你选择内核态路线探索完整的bio生命周期我们的例子只用了submit_bio_wait。试试submit_bio异步并学习如何设置一个bi_end_io回调函数在I/O完成后得到通知。理解请求队列request_queuebio最终会被合并、排序放入request_queue。你可以尝试编写一个简单的I/O调度器或者拦截特定的bio进行加密、压缩等处理。创建虚拟块设备利用内核的device mapper框架或自己实现一个block_device_operations创建一个虚拟的块设备。这个虚拟设备可以镜像、条带化底层NVMe磁盘或者实现一个简单的RAM Disk。这是理解Linux块设备驱动模型的绝佳实践。无论选择哪条路动手实践都是最好的老师。我建议你先从用户态的ioctl开始把NVMe的基本读写、参数获取玩熟练。然后在一个安全的测试环境比如虚拟机里大胆地编译、加载、测试你的内核模块。每遇到一个错误就去查源码、看文档、分析日志这个过程积累的经验远比读十篇文章来得扎实。存储技术的乐趣就在于这种从软件到硬件、从应用到系统的完整掌控感。

相关新闻

VideoAgentTrek-ScreenFilter在单片机系统中的应用前瞻:边缘AI的潜力与挑战

VideoAgentTrek-ScreenFilter在单片机系统中的应用前瞻:边缘AI的潜力与挑战

VideoAgentTrek-ScreenFilter在单片机系统中的应用前瞻:边缘AI的潜力与挑战 1. 引言 想象一下,一个只有指甲盖大小的芯片,不仅能控制家里的智能灯泡,还能实时分析摄像头捕捉的视频画面,自动识别异常事件、过滤掉不必…

2026/5/17 4:53:46 阅读更多 →
Ostrakon-VL-8B助力餐饮教学:AI辅助作业批改与技能评估

Ostrakon-VL-8B助力餐饮教学:AI辅助作业批改与技能评估

Ostrakon-VL-8B助力餐饮教学:AI辅助作业批改与技能评估 你有没有想过,在烹饪学校里,老师批改一份“红烧肉”的作业,和学生批改一份数学卷子,哪个更费神? 乍一听,这问题有点奇怪。但如果你了解…

2026/5/17 12:43:07 阅读更多 →
Nunchaku-flux-1-dev创意效果:使用AIGC技术生成概念艺术与插画

Nunchaku-flux-1-dev创意效果:使用AIGC技术生成概念艺术与插画

Nunchaku-flux-1-dev创意效果:使用AIGC技术生成概念艺术与插画 最近试用了Nunchaku-flux-1-dev这个工具,它在生成创意图像方面的表现确实让人眼前一亮。特别是对于概念艺术和插画创作,它能快速地把脑海里的想法变成一幅幅充满细节和氛围感的…

2026/7/8 7:38:54 阅读更多 →

最新新闻

大数据毕设选题推荐:基于 Hadoop 的零食电商销售数据挖掘系统的设计与实现 基于 Django 的零食市场销售趋势可视化系统【附源码、mysql、文档、调试+代码讲解+全bao等】

大数据毕设选题推荐:基于 Hadoop 的零食电商销售数据挖掘系统的设计与实现 基于 Django 的零食市场销售趋势可视化系统【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/9 7:05:45 阅读更多 →
RPG Maker解密工具:轻松解锁加密游戏资源,开启二次创作之旅

RPG Maker解密工具:轻松解锁加密游戏资源,开启二次创作之旅

RPG Maker解密工具:轻松解锁加密游戏资源,开启二次创作之旅 【免费下载链接】RPGMakerDecrypter Tool for decrypting and extracting RPG Maker XP, VX and VX Ace encrypted archives and MV and MZ encrypted files. 项目地址: https://gitcode.com…

2026/7/9 7:05:45 阅读更多 →
2026 Web 渗透测试全流程实战:从 0 到 1 拿下高分|合规 + 闭环 + 可复现

2026 Web 渗透测试全流程实战:从 0 到 1 拿下高分|合规 + 闭环 + 可复现

Web 渗透测试不是 “工具跑一遍”,而是在合法授权下,以攻击者视角完整复现攻击链,输出可落地修复方案的安全评估工程。一篇高分博文 / 报告必须满足: ✅ 流程闭环:准备→信息收集→探测→利用→提权→清理→报告 ✅ 可…

2026/7/9 7:05:45 阅读更多 →
企业级融媒体平台EasyDSS:培训视频不存档,直播点播全上场,预算花出复利感

企业级融媒体平台EasyDSS:培训视频不存档,直播点播全上场,预算花出复利感

每年投入几十万做企业培训,结果课程录完没人看、新人入职重复讲、员工学习效果看不见。EasyDSS用直播让培训一步触达全员,用点播把知识沉淀为永久资产,用数据让培训效果一目了然。 一、培训部门的"三大痛" 做过企业培训的人都知道…

2026/7/9 7:03:45 阅读更多 →
携号转网查询能力综合评估:2026主流方案数据源实时性、识别准确率与响应速度梯队解读

携号转网查询能力综合评估:2026主流方案数据源实时性、识别准确率与响应速度梯队解读

携号转网的市场渗透与企业盲区 截至2026年第二季度,全国携号转网用户总量已突破1.2亿,占移动通信用户总数的比例逼近8%。这个数字放在五年前几乎难以想象——彼时携号转网刚刚结束试点、推向全国,不少业内人士对用户转网意愿持观望态度。如今…

2026/7/9 7:03:45 阅读更多 →
Browser-use:基于AI的浏览器自动化智能体实战指南

Browser-use:基于AI的浏览器自动化智能体实战指南

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 如果你最近在 GitHub 上看到过“Open Interpreter”或者“01 Light”这些项目,可能会好奇它们到底能做什么。简单来说&…

2026/7/9 7:03:45 阅读更多 →

日新闻

3大音乐平台逐字歌词完整解决方案:ESLyric-LyricsSource完全指南

3大音乐平台逐字歌词完整解决方案:ESLyric-LyricsSource完全指南

3大音乐平台逐字歌词完整解决方案:ESLyric-LyricsSource完全指南 【免费下载链接】ESLyric-LyricsSource Advanced lyrics source for ESLyric in foobar2000 项目地址: https://gitcode.com/gh_mirrors/es/ESLyric-LyricsSource 还在为Foobar2000找不到高质…

2026/7/9 0:01:04 阅读更多 →
ElegantBook封面定制揭秘:3个步骤打造专业级学术书籍

ElegantBook封面定制揭秘:3个步骤打造专业级学术书籍

ElegantBook封面定制揭秘:3个步骤打造专业级学术书籍 【免费下载链接】ElegantBook Elegant LaTeX Template for Books 项目地址: https://gitcode.com/gh_mirrors/el/ElegantBook 你是否曾经为学术书籍的封面设计而烦恼?想要一个既专业又美观的封…

2026/7/9 0:03:06 阅读更多 →
如何高效使用pyodbc:企业级数据库连接终极指南

如何高效使用pyodbc:企业级数据库连接终极指南

如何高效使用pyodbc:企业级数据库连接终极指南 【免费下载链接】pyodbc Python ODBC bridge 项目地址: https://gitcode.com/gh_mirrors/py/pyodbc 在当今数据驱动的商业环境中,企业级数据库连接已成为现代应用开发的核心需求。pyodbc作为一款强大…

2026/7/9 0:07:11 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/8 16:14:06 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/7 12:34:47 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/8 16:59:55 阅读更多 →

月新闻