linux平台的无盘启动开发
by fanxiushu 2023-10-15 转载或引用请注明原始作者。
前一章节介绍的是linux平台下的虚拟磁盘驱动开发过程,主要讲述了 基于block的磁盘和基于SCSI接口的磁盘。
本文介绍的内容正是基于上文中的SCSI接口的虚拟磁盘实现的无盘启动。
同样的,linux系统下也有系统自己集成的无盘启动方案,
这点与windows类似,就连协议也能找到一样的,也就是windows和linux都可以使用iSCSI进行无盘启动。
但是linux系统可以选择的方案更多,除了大家都认可的基于底层磁盘块设备的无盘启动,
linux还能基于上层的文件系统进行无盘启动,比如linux可以配置NFS网络文件系统来无盘启动。
不过我认为还是基于底层块设备更好些,至少服务端对单个的镜像文件更好管理一些。
本文并不介绍linux系统自己集成的无盘启动方案,而是如前面文章阐述的windows平台的无盘启动系统一样。
全是自己开发的。
可以先看看下面演示视频:
linux的无盘启动
其实linux的无盘启动和windows的无盘启动的大体方向都是一致的:
1,在电脑引导阶段,需要开发 BIOS(UEFI)引导程序,
2,接着进入具体的操作系统阶段,需要开发对应的虚拟磁盘驱动来接管操作系统的启动和运行过程。
但是linux和windows的启动过程,有非常巨大的区别,这就造成了上面的第2个步骤中,开发的虚拟磁盘驱动的天差地别。
我们来回忆一下,前面章节讲述windows的启动阶段。
windows基本分成三个阶段:
1,boot-start阶段:运行 bios引导阶段winloader.efi(exe)程序加载到内存的windows基本内核和boot-start驱动程序,
2,initsystem阶段 :系统初始化
3,win logon阶段:系统登录
而且也说过,对于无盘启动来说,最重要的是boot-start阶段的处理问题,这是很麻烦的一件事。
具体都在以前的章节中讲述过。
而linux平台下的启动阶段,对无盘系统来说,就相对友好的多。
当然,同样的,bios引导阶段,会加载linux的引导程序,这个一般都是grub引导程序(当然可能也有其他引导程序,但是目前以grub为主),
而grub主要工作就是把 vmlinuz 基本内核文件 加载到内存中,
vmlinuz文件都可以在 /boot目录下找到,通常是vmlinuz为前缀加一些版本号什么的。
再然后就是加载 initramfs 虚拟文件系统到内存,
而 initramfs 就是我们能正常运行我们的虚拟磁盘驱动的关键,等下会讲述。
接着grub开始解压 initramfs等虚拟文件系统,并且挂载出一个虚拟文件系统,运行 vmlinuz 内核,把控制权力交接给linux内核。
现在我们来看看, linux为何会有 initramfs 这个虚拟文件系统。
linux和windows,或者说任何操作系统都一样,都存在一个尴尬的问题:
当BIOS引导程序把控制权交接给具体的操作系统,
操作系统不可避免的都需要访问磁盘,但是在最开始阶段,操作系统刚运行,磁盘驱动还没加载,
磁盘驱动文件不存在,需要从磁盘上加载磁盘驱动文件;但是这个时候操作系统无法访问磁盘。。。
这就陷入了一个荒诞的循环悖论中。
因此唯一的做法,就是在BIOS引导程序还在运行的阶段,把磁盘驱动文件首先加载到内存中。
windows的做法就是在BIOS运行阶段,winloader.exe程序把windows基本内核(ntoskrne.exe等基本文件),
以及处于 boot-start阶段(其中必然会包括磁盘驱动)加载到内存中。
因此到了windows的boot-start阶段,就开始运行加载到内存中的这些文件。
而linux的做法我们可以有一个非常简单的办法:
反正vmlinuz内核文件肯定首先被加载到内存中,我们把对应磁盘的驱动直接编译集成到vmlinuz内核文件中。
反正linux内核开源,而且是那种大一统的内核,不像windows的ntoskrnel内核,
但是这样弊端也非常明显,什么东西都朝vmlinuz内核文件中集成,会变得越来越臃肿和难以管理。
于是就诞生了 initramfs,顾名思义,
基于内存的文件系统,很像我们在内存中虚拟出一块磁盘,然后在上面加载对应的文件系统。
其实在2.6版本之前,是initrd,也就是直接生成内存磁盘。
2.6之后的版本,使用了initramfs来代替,因为initrd会直接开辟一块内存作为内存磁盘,这样会造成一些浪费。
而initramfs则是完全根据加载的文件来模拟虚拟文件系统,不存在initrd那样的浪费。
该如何解释initramfs展现出来的效果呢?
就是当grub把vmlinuz和initramfs加载到内存,并且initramfs初始化完成,vmlinuz接管控制权之后,
在没有访问真正的磁盘之前,
如果我们这时候登录到linux,会神奇的发现,像 /etc, /lib ,/bin ,/usr/lib 等目录就已经存在,而且里边有对应的文件了。
可是实际上我们还没真正访问本地磁盘。
这就是initramfs效果,它在内存中模拟出本地磁盘文件系统,vmlinuz内核像访问真正的目录文件那样访问这些目录中的文件。
于是,linux内核找到磁盘驱动文件位置,然后加载它,之后就能正常访问真正的本地磁盘系统了。
当然这个时候,linux内核会把真正的磁盘系统挂载上去,替换掉initramfs。
接下来我们需要做的,就是利用相关工具程序,把我们对应的磁盘驱动加到 initramfs 虚拟系统文件中。
当然,如果我们是在具体的电脑中安装对应的linux操作系统,安装程序就已经帮我们做好这一步了。
与windows做个对比,可以发现使用initramfs比起windows启动阶段显然简单得多,
这是不同系统内核的处理方式造成的,所以也不大可能搞得windows像linux那样的启动方式。
有了initramfs, 我们加载自己的无盘系统,就相对简单得多了。
我们主要在iniramfs中添加两个驱动:
1,网卡驱动,
2,我们的虚拟磁盘驱动,
网卡驱动则是对应网卡硬件厂商在linux下开发的驱动,
网卡驱动必须添加到initramfs中,否则虚拟磁盘无法访问网络,从而无法建立网络磁盘。
接着就是我们的虚拟磁盘驱动。
而虚拟磁盘驱动访问网络方式也不用像windows那样使用底层的NDIS协议,
可以直接使用TCP通信,因为TCP协议栈直接集成到vmlinuz内核中,
也不用担心像windows那样最早阶段连TCP协议栈都没建立起来。
这里以CentOS7,vmware虚拟机为例子,
简单阐述下如何把这两个驱动添加到 initramfs 中。
首先,假设我们开发的无盘启动的虚拟磁盘驱动 是 nbt_scsi.ko
把nbt_scsi.ko 复制到 /usr/lib/modules/$VERSION/kernel/drivers/block 目录中,
其中 $VERSION 对应的linux具体的内核版本,
然后使用 xz 命令 把 nbt_scsi.ko 压缩成 nbt_scsi.ko.xz 文件
运行 depmod -a 生成关联信息。
同时在 /etc/modules-load.d/ 目录中添加一个文件比如取名 nbt_scsi.conf
使用vi编辑,添加 如下两行:
nbt_scsi
e1000
其中e1000是vmware环境下,CentOS7系统对应的网卡驱动。
在 /etc/modules-load.d/ 目录下生成以上文本文件的目的是为了让linux内核自动加载上面两个驱动。
再然后则使用dracut ,如下运行:
dracut --force --add-drivers "nbt_scsi"
表示把 nbt_scsi 驱动添加到 initramfs 虚拟文件系统文件中。
(不同的linux发行版本,可能有不同的命令,但总体思路都是一样的)。
生成之后,如果不放心,
可以使用 lsinitrd |grep nbt_scsi 命令查看 initramfs中是否已经新添加了对应驱动文件。
同样的步骤,把 e1000 网卡驱动添加到 initramfs 中。
至此,CentOS7系统下的无盘配置就算完成,然后运行 nbt_scsi.ko 驱动,把服务端对应的镜像文件映射成本地scsi磁盘。
接着可以使用 dd 等命令,把磁盘数据copy到服务端镜像中。
再然后,我们就可以在别的也是同样支持e1000网卡的vmware虚拟机中无盘方式启动 centos7 了。
接着我们来看看nbt_scsi.ko 驱动中关于网络通信部分,
至于scsi接口的磁盘部分,上面一篇文章中已经阐述过,这里就不再重复了。
本文前面也说过,通信部分完全可以直接使用TCP通信,而且linux内核中socket与应用层的 BSD socket 接口一样的简单方便。
如下代码,我们就能封装成几乎跟应用层socket一样的接口方式:
kernel socket
struct socket* sock_create_v4(BOOLEAN is_tcp)
{
struct socket* sock = NULL;
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)
int ret = sock_create_kern(AF_INET, is_tcp ? SOCK_STREAM : SOCK_DGRAM , 0, &sock);
#else
int ret = sock_create_kern(&init_net, AF_INET, is_tcp ? SOCK_STREAM : SOCK_DGRAM, 0, &sock);
#endif
if (ret < 0) {
printk("sock_create_kern err=%d\n", ret);
return NULL;
}
if (is_tcp) {
int nodelay = 1;
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)
ret = kernel_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay, sizeof(int));
#else
ret = sock_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, KERNEL_SOCKPTR((char*)&nodelay), sizeof(int));
#endif
if (ret) {
printk("++ warnning: set tcp no delay err=%d\n", ret);
}
}
return sock;
}
void sock_destroy(struct socket* s)
{
if (s) {
sock_release(s);
}
}
typedef struct _sock_addr_v4
{
uint32_t ip;
uint16_t port;
} sock_addr_v4;
int sock_connect(struct socket* s, sock_addr_v4* svr_addr)
{
int ret;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = svr_addr->ip;
addr.sin_port = svr_addr->port;
ret = kernel_connect(s, (struct sockaddr*)&addr, sizeof(addr), 0);
if (ret < 0) {
printk("*** socket connect err=%d\n", ret);
}
return ret;
}
// 数据传输,包括接收或发送
int sock_transmit(
struct socket* sock, bool send, sock_addr_v4* addr,
char* buf, int size)
{
struct msghdr msg;
struct kvec iov;
struct sockaddr_in v4_addr;
int ret;
sock->sk->sk_allocation = GFP_NOIO;
iov.iov_base = buf;
iov.iov_len = size;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = MSG_NOSIGNAL;
if (addr) {
if (send) {
memset(&v4_addr, 0, sizeof(v4_addr));
v4_addr.sin_family = AF_INET;
v4_addr.sin_addr.s_addr = addr->ip;
v4_addr.sin_port = addr->port;
}
msg.msg_name = (struct sockaddr*)&v4_addr;
msg.msg_namelen = sizeof(struct sockaddr_in);
}
if (send) ret = kernel_sendmsg(sock, &msg, &iov, 1, size);
else {
ret = kernel_recvmsg(sock, &msg, &iov, 1, size, /*MSG_WAITALL*/ 0); ///完整接收size长度的数据,
if (ret > 0 && addr) {//收到数据了
addr->ip = v4_addr.sin_addr.s_addr;
addr->port = v4_addr.sin_port;
}
}
return ret;
}
显然,比起windows平台提供的TDI接口,或者WSK接口,不知道要简单多少倍了。
然后接下来就是磁盘数据的传输,这没啥好阐述的,按照SCSI接口提供的标准通信即可。
不过无盘启动的时候,依然会有问题,
其实主要就是grub加载内核之后,linux运行之后,并不会自动运行DHCP来配置本地IP地址,
如果没有对应的本地IP地址,TCP就无法路由,自然也无法通信。
至于这点,我们可以修改grub的启动参数,添加linux内核启动之后自动运行DHCP的支持,
也就是会多增加一些配置。
不过我自己开发的无盘系统,最主要的是基于windows平台的,
而在以前文章阐述过,底层网络通信基本都是直接使用NDIS通信,然后把链路层协议转成UDP来达到磁盘传输的目的。
所有协议的定义都和UDP有关,而且还会直接使用MAC地址来定位相关信息。
所以在linux平台的nbt_scsi驱动中,并没有使用TCP传输,或者只是作为辅助而已。
这样为了能跟windows中NDIS方式更好的结合。
一开始想在linux内核中寻找windows中类似NDIS的接口,甚至都想直接挂钩linux网卡驱动对应的通用接收函数。
到后来突然想到 PF_PACKET协议的套接口,它同样能接收和发送链路层数据包。
想到这一点,事情就好办了。
直接在nbt_scsi驱动使用 PF_PACKET协议的套接口。
同时为了加快接收速度,其实是替换了PF_PACKET 套接口的 sk_data_ready 回调函数。
这个实现过程就跟windows中使用NDIS协议驱动实现类似UDP套接口一样的复杂了。
有个好处就是:我不用再去理会本地IP地方分配问题,也不用去管DHCP,因为我在自己的协议中处理ip地址问题。
当然代价也是高昂的,等于自己利用链路层数据通信,自己完整实现了属于自己特色的UDP传输协议栈。
限于篇幅,这里不再赘述,有兴趣的同学可自行去研究。
相关文章:
linux平台的无盘启动开发
by fanxiushu 2023-10-15 转载或引用请注明原始作者。 前一章节介绍的是linux平台下的虚拟磁盘驱动开发过程,主要讲述了 基于block的磁盘和基于SCSI接口的磁盘。 本文介绍的内容正是基于上文中的SCSI接口的虚拟磁盘实现的无盘启动。 同样的,linux系统下也…...

【GO入门】环境配置及Vscode配置
1 GO环境配置 欢迎来到Go的世界,让我们开始探索吧! Go是一种新的语言,一种并发的、带垃圾回收的、快速编译的语言。它具有以下特点: 它可以在一台计算机上用几秒钟的时间编译一个大型的Go程序。Go为软件构造提供了一种模型&…...

家政服务小程序,家政维修系统,专业家政软件开发商;家政服务小程序,家政行业软件开发
家政服务小程序,家政维修系统,专业家政软件开发商; 家政服务小程序,家政行业软件开发解决方案,家政软件经验丰富实践,系统高度集成,提供师傅端、用户端、… 家政服务app开发架构有 1、后台管理端…...

英语——语法——从句——状语从句——笔记
一、概念 状语从句(Adverbial Clause)是指句子用作状语时,起副词作用的句子。状语从句中的从句可以修饰谓语。 状语从句根据其作用可分为时间、地点、原因、条件、目的、结果、让步、方式和比较等九 种状语从句。状语从句一般由连词(从属连词…...

Linux 学习的六个过程
Linux 上手难,学习曲线陡峭,所以它的学习过程更像一个爬坡模式。这些坡看起来都很陡,但是一旦爬上一阶,就会一马平川。 1、抛弃旧的思维习惯,熟练使用 Linux 命令行 在 Linux 中,无论我们做什么事情&…...

『heqingchun-ubuntu系统下安装nvidia显卡驱动3种方法』
ubuntu系统下安装nvidia显卡驱动3种方法 一、安装依赖 1.更新 sudo apt updatesudo apt upgrade -y2.基础工具 sudo apt install -y build-essential python图形界面相关 sudo apt install -y lightdm注:在弹出对话框选择"lightdm" 二、第一种:使用…...
[paddle]paddleseg中eiseg加载模型参数的模型下载地址
图片标注 以下内容为2D图片标注模型下载及EISeg2D图片标注流程,具体如下: 模型准备 在使用EISeg前,请先下载模型参数。EISeg开放了在COCOLVIS、大规模人像数据、mapping_challenge,Chest X-Ray,MRSpineSegÿ…...

标定板生成网址,可以直接打印,matlab标定工具箱
Camera Calibration Pattern Generator – calib.io matlab 打开标定的成像 cameraCalibrator 点击完成之后 命令行中输入 cameraParams.IntrinsicMatrix...

React高级特性之受控和非受控组件
一、受控组件 受控组件:input框自己的状态被React组件状态控制 // 类组件引入React import React from reactclass InputComponent extends React.Component{state {message: zm66666}changeHandler (e) > {this.setState({message: e.target.value})}render…...

Android 14 正式发布,已经在 AOSP 中上线
本心、输入输出、结果 文章目录 Android 14 正式发布,已经在 AOSP 中上线前言总结主要更新内容机型支持优化性能的数据体现字体放大、多媒体支持加强Android 14 增加了对 10 位高动态范围 (HDR) 图像的支持提供了新的图形和尺寸管理用户体验 与隐私安全弘扬爱国精神Android 14…...

软件开发介绍
一、软件开发整体介绍 作为一名软件开发工程师,我们需要了解在软件开发过程中的开发流程,以及软件开发过程中涉及到的岗位角色,角色的分工、职责,并了解软件开发中涉及到的三种软件环境。 1.1 软件开发流程 第一阶段:…...
Go 匿名函数与闭包
Go 匿名函数与闭包 匿名函数和闭包是一些编程语言中的重要概念,它们在Go语言中也有重要的应用。让我们来详细介绍这两个概念,并提供示例代码来帮助理解。 文章目录 Go 匿名函数与闭包一、匿名函数(Anonymous Function)二、闭包函…...
html关闭空标签
常见的空标签有以下几种示例: <br>:表示换行,没有闭合标签。<hr>:表示水平线,没有闭合标签。<img>:表示图片,没有闭合标签。<input>:表示输入框࿰…...

Java实现B树
1.介绍 B树是一种自平衡的搜索树数据结构,常用于数据库和文件系统中的索引结构。它具有以下好处和功能: 高效的查找操作:B树的特点是每个节点可以存储多个关键字,并且保持有序。通过在节点上进行二分查找,可以快速定位…...

crontab报错/var/spool/cron : Permission denied和 -bash: chattr: command not found
crontab报错/var/spool/cron : Permission denied和 -bash: chattr: command not found 1、第一种情况2、第二种情况3、第三种情况 1、第一种情况 centos7下修改定时任务crontab -e的时候,控制台输出“crontab: installing new crontab”,表示任务添加成…...

06在IDEA中创建Java和Web工程,了解不同工程下的类路径,在IDEA中执行Maven命令
创建Java/Web模块 类路径的概述 IDEA中普通java项目中类路径的开始就是以src目录开始的路径,编译后的字节码文件和配置文件最终都会放在out目录下 Maven生成的目录结构中src/main目录下的java和resources目录都可以看作类路径的开始,编译后的字节码文件或资源文件会放在targ…...
自定义redission装配和集成分布式开源限流业务组件ratelimiter-spring-boot-starter的正确姿势
自定义redission装配和集成分布式开源限流业务组件ratelimiter-spring-boot-starter的正确姿势 文章目录 1.说明1.1 pom依赖1.2 引入redisson不引入redisson-spring-boot-starter依赖1.3 引入redisson-spring-boot-starter不引入redisson,启动类排除redisson-spring-boot-start…...

Ceph分布式存储的简单介绍与Ceph集群的部署搭建
文章目录 1. 存储的概述1.1 单机存储设备1.1.1 DAS(直接附加存储)1.1.2 NAS(网络附加存储)1.1.3 SAN(存储区域网络) 1.2 单机存储的缺陷1.3 分布式存储(软件定义的存储 SDS)1.4 分布…...
【环境搭建】linux docker安装nexus3
1、shell输入 docker run -dti \--nethost \--namenexus3 \--privilegedtrue \--restartalways \--ulimit nofile655350 \--ulimit memlock-1 \--memory1G \--memory-swap-1 \-e INSTALL4J_ADD_VM_PARAMS"-Xms512m -Xmx512m -XX:MaxDirectMemorySize1g" \-v /etc/lo…...

Java多线程下载文件
JVM是支持多线程程序的,当程序需要同时执行两个或多个任务,实现一些需要等待的任务时,如用户输入、文件读写、网络操作、搜索等多线程程序比单线程程序更具优势,可充分利用CPU资源,完成时间更短,提高应用程…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...