ceph的块存储如何骗过服务器,让服务器把它当做真实的硬盘
ceph的块存储就是一块远程网络硬盘。操作系统为啥会读写这块假硬盘呢一台服务器要使用CEPH提供的块存储也是需要ceph的驱动软件来和ceph通讯吧是的你的理解完全正确。一台服务器想要使用 Ceph 提供的块存储必须通过相应的“驱动软件”或客户端组件来和 Ceph 集群进行通讯。在 Ceph 的块存储RBD, RADOS Block Device体系中这种“驱动软件”主要分为两种实现路径1. 内核态驱动KRBD这是最常见、最接近传统硬盘使用习惯的方式。原理它依赖 Linux 内核自带的rbd模块rbd.ko。使用方式你需要在服务器上安装 Ceph 的客户端软件如ceph-common包然后使用rbd map命令将 Ceph 集群里的一个块设备镜像Image映射到本地。效果映射成功后你的服务器本地会出现一个标准的块设备文件例如/dev/rbd0。你可以像对待一块普通的物理硬盘一样对它进行分区、格式化如 mkfs.ext4并挂载到某个目录下使用。2. 用户态库librbd这种方式不通过内核而是直接在应用程序层面调用 Ceph 提供的动态链接库。原理应用程序直接链接并调用 Ceph 提供的librbd和librados库文件来读写数据。使用方式这种方式不会在操作系统里生成/dev/rbd0这样的设备文件而是由应用程序直接对接。典型场景主要被 KVM/QEMU 虚拟机或 OpenStack 等云平台使用。比如当你启动一台使用 Ceph 作为后端的虚拟机时宿主机上的 QEMU 进程就是通过librbd直接与 Ceph 集群通讯为虚拟机提供磁盘空间的。总结来说如果你是想把 Ceph 块存储当成一块普通的远程硬盘挂载到 Linux 服务器上你需要安装ceph-common客户端并依赖内核的KRBD 驱动如果你是开发应用程序或者配置 KVM 虚拟机来直接使用 Ceph 存储则是通过librbd 库来实现通讯的。自己实现一个最简的远程磁盘及协议要怎么做自己实现一个最简的远程磁盘系统核心原理其实就是**“网络IO”与“本地磁盘IO”的桥接**。简单来说就是把客户端Client对磁盘的读写请求通过网络发给服务端Server服务端在本地执行真实的读写操作后再把结果返回给客户端。我们可以把这个系统拆解为以下几个核心步骤来实现1. 通信层建立网络通道远程磁盘通常追求较高的读写性能因此底层通信协议一般首选TCP而不是 UDPTCP 能保证数据可靠传输避免磁盘数据丢包损坏。服务端启动一个 TCP 监听端口比如 8888等待客户端连接。客户端通过 IP 和端口连接到服务端建立一个稳定的长连接。2. 协议层设计“读写指令”为了让服务端听懂客户端要干什么我们需要自定义一个简单的通信协议报文格式。一个最简的远程磁盘协议只需要包含以下几种核心指令可以用一个字节来表示操作类型opcodeREAD读比如 opcode 0x01客户端告诉服务端“我要读数据”。WRITE写比如 opcode 0x02客户端告诉服务端“我要写数据”。ACK/RESPONSE应答服务端处理完后返回成功状态或读取到的数据。请求报文的结构可以设计得非常简单例如[操作码(1字节)] [偏移量 offset(8字节)] [数据长度 length(4字节)] [实际数据(写操作时附带)](注在网络传输多字节整数时记得使用大端序htons/htonl来保证不同架构机器间的兼容性。)3. 服务端实现本地磁盘的“代笔者”服务端的逻辑非常直观它本质上就是一个拿着“后端真实磁盘文件”的打工人在启动时服务端在本地创建一个巨大的空白文件比如disk.img用dd命令生成这个文件就代表一块“虚拟硬盘”。接收到客户端的READ请求后解析出offset偏移量和length长度使用系统调用如seek和read去读取disk.img对应位置的数据然后通过网络发回给客户端。接收到WRITE请求后解析出offset、length和附带的“实际数据”使用seek和write把数据精准地写入disk.img的对应位置。4. 客户端实现虚拟块设备驱动这是最难、也是最核心的一步。客户端不能只是一个普通的发请求的程序它必须让操作系统认为**“我插上了一块新硬盘”**。Linux 实现思路你需要编写一个 Linux内核模块利用 Linux 内核提供的ioctl接口。当客户端程序通过网络收到服务端发来的数据时通过ioctl把这些数据“喂”给内核的块设备层。这样Linux 系统里就会多出一个/dev/remote_disk0的设备文件。使用效果一旦这个设备文件出现你就可以像操作普通硬盘一样对它执行mkfs.ext4格式化然后mount挂载到某个目录随意读写文件。5. 进阶完善工程化细节当你把上面最简的“读写通路”跑通后一个真正可用的远程磁盘还需要考虑以下问题分包与粘包处理网络传输有 MTU最大传输单元限制大数据块需要拆分成小包发送并在协议头加上数据包的总长度防止接收方读取时发生粘包。断点续传与重试机制网络可能会抖动如果写入一半断网了需要有 ACK 确认机制和超时重传逻辑保证数据的一致性。权限与加密真实的远程磁盘不能谁都能连需要加上简单的身份验证甚至给 TCP 通道套上 TLS/SSL 加密防止数据在局域网内被窃听。总结一下实现一个最简远程磁盘本质上就是**“客户端内核驱动拦截IO请求 - 封装成自定义协议 - 走TCP网络发给服务端 - 服务端落地到本地大文件”**的完整闭环。这不仅是网络编程的硬核练手项目也能让你深刻理解 Ceph RBD、iSCSI 等成熟块存储产品的底层运作原理。linux驱动开发的原理和思路是什么请介绍的通俗易懂一些Linux 驱动开发的本质其实就是给操作系统写一个硬件的“专属翻译官”。因为硬件比如显卡、网卡、传感器只听得懂底层的电信号和寄存器指令而 Linux 内核和应用软件只懂标准的系统指令。驱动就是夹在中间的那层软件负责把上层的指令“翻译”成硬件能懂的操作再把硬件的反馈“翻译”回上层。为了让你更直观地理解我们可以把 Linux 驱动开发的原理和思路拆解为以下几个核心维度 核心思路一切皆文件这是 Linux 驱动开发最底层的哲学。在 Linux 看来不管是普通的文本文件还是键盘、鼠标、硬盘等硬件设备统统都被抽象成了一个“文件”。对用户来说操作硬件就像操作文件一样简单。比如你想点亮一个 LED 灯不需要去研究电路只需要向一个特定的设备文件比如/dev/led执行“写入”操作想读取温湿度传感器的数据只需要从这个设备文件里“读取”数据。对驱动来说你的工作就是写好这个“文件”背后的逻辑。你需要告诉系统当用户对这个文件执行open打开、read读、write写时驱动底层具体要去操作硬件的哪些寄存器、拉高还是拉低哪个引脚的电平。️ 核心模型总线-设备-驱动Bus-Device-Driver在早期的驱动开发中硬件信息和驱动代码经常混杂在一起导致代码极其难维护。现代 Linux 驱动开发采用了**“总线-设备-驱动”模型**核心思想就是**“分离与解耦”**。我们可以用**“开餐厅”**来打个比方设备Device就是**“食材”**比如鱼、肉、蔬菜它只负责描述硬件的客观属性比如硬件的内存地址、中断号等不包含任何处理逻辑。驱动Driver就是**“厨师”**比如王师傅只做鱼李师傅只做肉它只包含处理逻辑怎么切、怎么炒但不关心食材具体放在哪个冰箱里。总线Bus就是餐厅的**“调度员”**。它负责把“食材”和“厨师”匹配起来。开发思路变成了你只需要在代码里告诉内核“我是哪个总线的驱动我能处理哪种设备”内核的总线调度员就会自动帮你完成匹配。一旦匹配成功就会自动调用驱动里的初始化函数在代码中通常叫probe函数开始干活。️ 开发流程“三步曲”无论开发哪种驱动核心流程都可以简化为以下三个步骤“报名”——注册驱动在驱动模块加载时通过特定的注册函数告诉内核“嗨我来了我能管理某某硬件”“干活”——实现核心功能这是驱动的灵魂。你需要填充一个名为file_operations的结构体可以理解为一张任务清单把你的open、read、write等函数填进去。当用户程序调用系统指令时内核就会拿着这张清单精准地执行你写好的硬件操作代码。“卸任”——卸载驱动在驱动模块退出时负责释放占用的内存、注销设备把资源干干净净地交还给系统。 驱动家族的“三大门派”Linux 根据硬件传输数据的方式不同把驱动分成了三大类驱动类型核心特点典型代表字符设备驱动像水流一样按“字节”顺序传输不能随机乱跳键盘、鼠标、串口、各类传感器块设备驱动像集装箱运输按“块”如4KB批量传输支持随机读写硬盘、U盘、SSD也就是你刚才问的磁盘网络设备驱动专门处理网络数据包不走文件系统的 read/write 逻辑以太网卡、Wi-Fi 模块总结一下Linux 驱动开发就是利用**“一切皆文件”的哲学通过“总线-设备-驱动”**的解耦模型去编写一套标准化的代码填好file_operations这张表让操作系统能够统一、高效地指挥各种各样的硬件为你工作。驱动注册后设备从哪里来的驱动注册后“设备”的来源其实分为两个层面一个是物理上真实的硬件另一个是软件上供你调用的设备文件如/dev/xxx。我们可以结合你刚才了解的“总线-设备-驱动”模型来看看设备到底是从哪里“冒”出来的1. 物理设备从哪来硬件的“自我介绍”物理设备并不是驱动“变”出来的而是原本就存在的。驱动注册后内核会帮你在系统里“找到”这些设备。主要有两种情况热插拔发现即插即用当你把 U 盘、鼠标等 USB 设备插入电脑时硬件控制器会立刻感知到电压变化并向内核发送一个中断信号。内核收到信号后会马上读取这个新设备的描述符比如厂商 ID、产品 ID然后在系统内部登记“嘿总线上来了个新家伙”。系统启动枚举开机自检对于插在主板上的 PCIe、SATA 等固定设备系统在上电启动时内核的总线代码会主动去扫描物理总线把系统里所有的硬件设备都“点名”一遍登记在自己的花名册上。驱动注册后的动作当你调用xxx_driver_register()注册驱动时其实只是向内核提交了一份“简历”包含你能支持的设备 ID 等信息。内核会拿着你的简历去和它花名册上已经登记的设备进行匹配。一旦匹配成功内核就会自动调用你驱动里的probe探测函数相当于把硬件正式交到你手里去接管。2. 设备文件从哪来软件的“自动建档”物理设备被驱动接管后为了让用户空间也就是你的应用程序能方便地操作它系统还需要在/dev/目录下生成一个对应的设备文件比如/dev/sda或/dev/my_led。这个过程在现代 Linux 中是全自动的驱动“上报户口”在你的驱动代码通常在probe函数里接管硬件后会调用class_create()和device_create()这两个内核 API。这一步相当于驱动向系统大声汇报“我已经成功接管了这个硬件它的设备号是 XXX请给它建个档案”。内核“发广播”内核收到汇报后会在内部的sysfs文件系统/sys/class/目录里记录下这个设备的详细信息同时向用户空间发送一个名为uevent用户事件的广播通知。管家“自动建档”在用户空间有一个 7x24 小时值班的“管家”守护进程通常是udev在嵌入式系统中是它的精简版mdev。它时刻监听内核发出的uevent广播。一旦收到“有新设备加入”的通知它就会自动在/dev/目录下创建出对应的设备节点文件。简单总结一下驱动注册后物理设备是内核通过“扫描”或“监听中断”早就发现并登记好的而设备文件则是驱动在成功接管硬件后通过device_create()上报最终由系统里的udev/mdev管家自动在/dev/目录下创建出来的。请写一个最简单的驱动程序我学习一下结合你之前对“虚拟设备”和“驱动原理”的理解写一个最简单的 Linux 驱动最好的方式就是写一个**“虚拟内存字符设备”**。它的功能非常直观你在内核里开辟一块内存当作“虚拟设备”用户往里面写数据它就存起来用户读数据它就把存的内容返回给你。下面我们用大概 100 行代码带你一步步实现这个驱动。第一步编写驱动源码创建一个名为simple_char.c的文件把下面的代码复制进去#includelinux/init.h#includelinux/module.h#includelinux/fs.h#includelinux/cdev.h#includelinux/uaccess.h#includelinux/device.h#defineDEVICE_NAMEmy_virtual_dev// 设备名字#defineCLASS_NAMEmy_class// 设备类名字#defineBUFFER_SIZE1024// 虚拟内存缓冲区大小staticintmajor_num;// 主设备号staticstructclass*my_classNULL;// 设备类staticcharkernel_buffer[BUFFER_SIZE];// 模拟设备的内核内存// 1. 实现设备的 open 操作staticintmy_open(structinode*inode,structfile*file){pr_info(虚拟设备被打开啦\n);return0;}// 2. 实现设备的 read 操作把内核数据拷贝给用户staticssize_tmy_read(structfile*file,char__user*user_buf,size_tcount,loff_t*offset){intbytes_to_read;// 防止读取越界bytes_to_readmin(count,(size_t)(BUFFER_SIZE-*offset));if(bytes_to_read0)return0;// 安全地把数据从内核空间拷贝到用户空间if(copy_to_user(user_buf,kernel_buffer*offset,bytes_to_read)!0){return-EFAULT;}*offsetbytes_to_read;pr_info(给用户返回了 %d 个字节的数据\n,bytes_to_read);returnbytes_to_read;}// 3. 实现设备的 write 操作把用户数据拷贝给内核staticssize_tmy_write(structfile*file,constchar__user*user_buf,size_tcount,loff_t*offset){intbytes_to_write;bytes_to_writemin(count,(size_t)(BUFFER_SIZE-*offset));if(bytes_to_write0)return-ENOSPC;// 安全地把数据从用户空间拷贝到内核空间if(copy_from_user(kernel_buffer*offset,user_buf,bytes_to_write)!0){return-EFAULT;}*offsetbytes_to_write;pr_info(从用户那里接收了 %d 个字节的数据\n,bytes_to_write);returnbytes_to_write;}// 4. 填充文件操作结构体告诉内核我们实现了哪些功能staticstructfile_operationsfops{.ownerTHIS_MODULE,.openmy_open,.readmy_read,.writemy_write,};// 5. 驱动模块初始化函数驱动被加载时执行staticint__initsimple_char_init(void){// 动态申请一个主设备号major_numregister_chrdev(0,DEVICE_NAME,fops);if(major_num0){pr_err(申请主设备号失败\n);returnmajor_num;}// 创建设备类并在 /dev 下自动创建设备文件my_classclass_create(THIS_MODULE,CLASS_NAME);if(IS_ERR(my_class)){unregister_chrdev(major_num,DEVICE_NAME);pr_err(创建设备类失败\n);returnPTR_ERR(my_class);}device_create(my_class,NULL,MKDEV(major_num,0),NULL,DEVICE_NAME);pr_info(驱动加载成功主设备号是: %d\n,major_num);return0;}// 6. 驱动模块退出函数驱动被卸载时执行staticvoid__exitsimple_char_exit(void){device_destroy(my_class,MKDEV(major_num,0));// 销毁设备文件class_destroy(my_class);// 销毁设备类unregister_chrdev(major_num,DEVICE_NAME);// 释放主设备号pr_info(驱动已安全卸载\n);}module_init(simple_char_init);module_exit(simple_char_exit);MODULE_LICENSE(GPL);MODULE_AUTHOR(YourName);MODULE_DESCRIPTION(A Simple Virtual Character Driver);第二步编写 Makefile在同一个目录下创建一个名为Makefile的文件注意首字母大写没有后缀obj-m simple_char.o all: make -C /lib/modules/$(shell uname -r)/build M$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M$(PWD) clean第三步编译并运行测试你需要一台 Linux 电脑虚拟机或物理机均可打开终端进入这两个文件所在的目录依次执行以下命令编译驱动make编译成功后当前目录下会生成一个simple_char.ko文件这就是你的驱动模块。加载驱动需要管理员权限sudoinsmod simple_char.ko此时驱动已经运行在内核里了。你可以用ls /dev/my_virtual_dev看看系统已经自动帮你生成了这个设备文件。测试写入数据echoHello Linux Driver!|sudotee/dev/my_virtual_dev这行命令会把字符串写入你的虚拟设备驱动里的my_write函数会被触发。测试读取数据sudocat/dev/my_virtual_dev你会看到终端打印出Hello Linux Driver!这说明驱动里的my_read函数把刚才存在内核内存里的数据又还给了你。查看内核打印的日志dmesg|tail你会看到驱动里用pr_info打印的“虚拟设备被打开啦”、“接收了xx字节”等调试信息。卸载驱动sudormmod simple_char卸载后/dev/my_virtual_dev文件也会自动消失。通过这个最简的驱动你就完整走通了**“编写驱动代码 - 编译成模块 - 加载到内核 - 自动创建设备文件 - 用户空间读写测试 - 卸载驱动”**的整个 Linux 驱动开发闭环。你可以试着修改一下代码比如在write里加一些特殊的逻辑来加深对它的理解试了一下成功实现了一个最简驱动$ insmod simple_char.ko(gaofeng1120)$cat/dev/my_virtual_dev(gaofeng1120)$echoHello Linux Driver2!/dev/my_virtual_dev(gaofeng1120)$cat/dev/my_virtual_dev Hello Linux Driver2!simple_char.mod.c 是哪里来的为什会有这个文件simple_char.mod.c这个文件并不是你手写的而是Linux 内核构建系统Kbuild在编译过程中自动生成的临时文件。你可以把它理解为驱动模块的**“专属身份证”或“元数据说明文件”**。️ 它是怎么来的当你执行make命令编译驱动时内核的构建系统会按照以下流水线工作首先把你手写的simple_char.c编译成目标文件simple_char.o。紧接着构建系统会自动生成simple_char.mod.c并把它编译成simple_char.mod.o。最后链接器会把simple_char.o和simple_char.mod.o打包链接在一起生成最终的内核模块文件simple_char.ko。 为什么需要这个文件Linux 内核模块.ko和普通的应用程序不同它需要携带一些额外的“身份信息”才能被内核正确加载和识别。simple_char.mod.c的作用就是存放这些内核专属的元数据主要包括模块的许可证与描述信息把你代码里写的MODULE_LICENSE(GPL)、MODULE_AUTHOR、MODULE_DESCRIPTION等宏定义转换成内核能够识别的标准数据结构。模块的版本依赖Magic 校验记录当前模块是基于哪个版本的内核编译的。当你用insmod加载驱动时内核会检查这些信息防止你把一个为旧内核编译的驱动强行加载到新内核里从而避免系统崩溃即所谓的“版本魔术不匹配”。符号依赖信息如果你的驱动用到了其他内核模块导出的函数这些信息也会被记录在这里方便内核在加载时自动处理依赖关系。 里面到底写了什么如果你好奇完全可以打开simple_char.mod.c看一眼。你会发现它里面的代码非常枯燥基本全是自动生成的宏和结构体数组比如长这样仅作示意#includelinux/module.h#defineINCLUDE_VERMAGIC#includelinux/build-salt.h#includelinux/vermagic.h#includelinux/compiler.hBUILD_SALT;MODULE_INFO(vermagic,VERMAGIC_STRING);MODULE_INFO(name,KBUILD_MODNAME);__visiblestructmodule__this_module__section(.gnu.linkonce.this_module){.nameKBUILD_MODNAME,.initinit_module,#ifdefCONFIG_MODULE_UNLOAD.exitcleanup_module,#endif.archMODULE_ARCH_INIT,};总结一下simple_char.mod.c是编译内核模块时由系统自动生成的“中间产物”它负责把模块的身份信息和版本校验等元数据打包进最终的.ko文件里。你平时开发完全不用管它执行make clean时它也会被自动清理掉。没有看到makefile中调用kbuild啊 make -C /lib/modules/(shelluname−r)/buildM(shell uname -r)/build M(shelluname−r)/buildM(PWD) modules 这句话详细解释一下你观察得很仔细确实在这个Makefile里我们并没有直接写kmake这个词但你写的这句make -C ...实际上就是在调用 Linux 内核的构建系统也就是 Kbuild。在 Linux 内核开发中kbuild并不是一个独立的可执行程序的名字而是一整套构建规则和脚本的统称。这套规则的核心其实就是内核源码顶层目录下的那个Makefile。下面为你逐字逐句拆解这句核心命令make -C /lib/modules/$(shell uname -r)/build M$(PWD) modules1.make这就是 Linux 系统自带的标准 GNU Make 工具。2.-C /lib/modules/$(shell uname -r)/build-C这是标准make命令的一个选项意思是“Change Directory”切换目录。它会让make在执行任何操作之前先跳转到后面指定的那个目录里去。/lib/modules/$(shell uname -r)/build这是你当前运行的 Linux 系统的内核源码或内核头文件目录。$(shell uname -r)这是一个 Makefile 的语法它会执行系统命令uname -r来获取你当前的内核版本号比如5.15.0-76-generic。这个路径下通常有一个软链接指向了真正的内核源码或头文件目录。合起来的意思是让make工具暂时离开你当前的驱动目录带着内核顶层Makefile也就是 Kbuild 的核心规则去干活。3.M$(PWD)M这是专门传递给**内核顶层 MakefileKbuild**的一个特殊变量。它的作用是告诉内核的构建系统“虽然我切换到了内核目录但我真正要编译的模块源码其实是在M指定的这个目录里”。$(PWD)这也是 Makefile 的语法代表“Print Working Directory”当前工作目录也就是你存放simple_char.c和你的Makefile的这个文件夹。合起来的意思是告诉内核 Kbuild 系统编译完必要的依赖后请回到我这个驱动源码目录里把我的代码编译了。4.modules这是内核顶层 Makefile 里预定义好的一个编译目标Target。当你跳转到内核目录后执行make modules就会触发 Kbuild 里编译外部模块的整套复杂规则包括生成你刚才问的.mod.c文件、进行版本校验、最后链接成.ko等。 总结一下整个执行流程也就是 Kbuild 的“回调”机制你在驱动目录输入make你的Makefile开始执行。遇到make -C /内核目录 M$(PWD) modules。make带着指令跳转到了/内核目录找到了内核的顶层Makefile这时候 Kbuild 系统就被激活了。内核顶层Makefile看到M$(PWD)知道是要编译外部模块于是设置好各种复杂的环境变量和编译规则。内核顶层Makefile执行到modules目标时会**再次回调跳回**到你的驱动目录M指定的目录去读取你的obj-m simple_char.o并开始编译你的simple_char.c。编译完你的.o文件后Kbuild 系统会自动生成simple_char.mod.c并最终把所有东西打包链接成simple_char.ko。所以虽然你没看到kmake这个命令但通过make -C ... M... modules这种精妙的“跳转回调”机制你的驱动已经完整地享受了 Linux 内核 Kbuild 系统提供的全套专业编译服务
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2633983.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!