2024年7月

阅读翻译Mathematics for Machine Learning之2.8 Affine Subspaces

关于:

  • 首次发表日期:2024-07-24
  • Mathematics for Machine Learning官方链接:
    https://mml-book.com
  • ChatGPT和KIMI机翻,人工润色
  • 非数学专业,如有错误,请不吝指出

2.8 仿射空间

接下来,我们将更详细地考察从原点偏移的空间,即不再是向量子空间的空间。此外,我们还将简要讨论这些仿射空间之间映射的性质,这些映射类似于线性映射。

备注
。在机器学习文献中,线性和仿射之间的区别有时并不明确,以至于我们可以发现将仿射空间/映射称为线性空间/映射的参考文献。

2.8.1 仿射空间

定义 2.25
(仿射子空间)。设
\(V\)
为一个向量空间,
\(\boldsymbol{x}_0 \in V\)

\(U \subseteq V\)
为一个子空间。那么子集

\[\begin{align*}
L & =\boldsymbol{x}_0+U:=\left\{\boldsymbol{x}_0+\boldsymbol{u}: \boldsymbol{u} \in U\right\} \tag{2.130a} \\
& =\left\{\boldsymbol{v} \in V \mid \exists \boldsymbol{u} \in U: \boldsymbol{v}=\boldsymbol{x}_0+\boldsymbol{u}\right\} \subseteq V \tag{2.130b}
\end{align*}
\]

称为
\(V\)

仿射子空间

线性流形(linear manifold)

\(U\)
称为
方向

方向空间(direction space)

\(\boldsymbol{x}_0\)
称为
支点(support point)
。在第12章中,我们将这种子空间称为超平面。

注意,如果
\(\boldsymbol{x}_0 \notin U\)
,则仿射子空间的定义排除了
\(\mathbf{0}\)
。因此,对于
\(\boldsymbol{x}_0 \notin U\)
,仿射子空间不是
\(V\)
的(线性)子空间(向量子空间)。

仿射子空间的例子有
\(\mathbb{R}^3\)
中的点、线和平面,这些点、线和平面不(一定)通过原点。

备注
。考虑向量空间
\(V\)
的两个仿射子空间
\(L = \boldsymbol{x}_0 + U\)

\(\tilde{L} = \tilde{\boldsymbol{x}}_0 + \tilde{U}\)
。当且仅当
\(U \subseteq \tilde{U}\)

\(x_0 - \tilde{x}_0 \in \tilde{U}\)
时,
\(L \subseteq \tilde{L}\)

仿射子空间通常由参数描述:考虑一个
\(V\)

\(k\)
维仿射空间
\(L = \boldsymbol{x}_0 + U\)
。如果
\(\left(\boldsymbol{b}_1, \ldots, \boldsymbol{b}_k\right)\)

\(U\)
的一个有序基,那么每个元素
\(\boldsymbol{x} \in L\)
都可以唯一地描述为

\[\boldsymbol{x}=\boldsymbol{x}_0+\lambda_1 \boldsymbol{b}_1+\ldots+\lambda_k \boldsymbol{b}_k,
\tag{2.131}
\]

其中
\(\lambda_1, \ldots, \lambda_k \in \mathbb{R}\)
。这种表示称为具有方向向量
\(\boldsymbol{b}_1, \ldots, \boldsymbol{b}_k\)
和参数
\(\lambda_1, \ldots, \lambda_k\)

\(L\)
的参数方程。



**例 2.26(仿射子空间)**

  • 一维仿射子空间称为直线,可以写作
    \(\boldsymbol{y}=\boldsymbol{x}_0+\lambda \boldsymbol{b}_1\)
    ,其中
    \(\lambda \in \mathbb{R}\)

    \(U=\operatorname{span}\left[\boldsymbol{b}_1\right] \subseteq \mathbb{R}^n\)

    \(\mathbb{R}^n\)
    的一维子空间。这意味着直线由一个支点
    \(\boldsymbol{x}_0\)
    和一个定义方向的向量
    \(\boldsymbol{b}_1\)
    定义。参见图 2.13 了解示意图。
  • \(\mathbb{R}^n\)
    的二维仿射子空间称为平面。平面的参数方程为
    \(\boldsymbol{y}=\boldsymbol{x}_0+\lambda_1 \boldsymbol{b}_1+\lambda_2 \boldsymbol{b}_2\)
    ,其中
    \(\lambda_1, \lambda_2 \in \mathbb{R}\)

    \(U=\operatorname{span}\left[\boldsymbol{b}_1, \boldsymbol{b}_2\right] \subseteq \mathbb{R}^n\)
    。这意味着平面由一个支点
    \(\boldsymbol{x}_0\)
    和两个线性独立的向量
    \(\boldsymbol{b}_1, \boldsymbol{b}_2\)
    定义,这两个向量张成方向空间(span the direction space)。

  • \(\mathbb{R}^n\)
    中,
    \((n-1)\)
    维仿射子空间被称为超平面,相应的参数方程为
    \(\boldsymbol{y}=\boldsymbol{x}_0+\sum_{i=1}^{n-1} \lambda_i \boldsymbol{b}_i\)
    ,其中
    \(\boldsymbol{b}_1, \ldots, \boldsymbol{b}_{n-1}\)
    构成
    \(\mathbb{R}^n\)
    的一个
    \((n-1)\)
    维子空间
    \(U\)
    的基。这意味着超平面由一个支点
    \(\boldsymbol{x}_0\)

    \((n-1)\)
    个线性独立的向量
    \(\boldsymbol{b}_1, \ldots, \boldsymbol{b}_{n-1}\)
    定义,这些向量张成方向空间。在
    \(\mathbb{R}^2\)
    中,直线也是超平面。在
    \(\mathbb{R}^3\)
    中,平面也是超平面。


备注
(非齐次线性方程组和仿射子空间)。对于
\(\boldsymbol{A} \in \mathbb{R}^{m \times n}\)

\(\boldsymbol{x} \in \mathbb{R}^m\)
,线性方程组
\(\boldsymbol{A} \boldsymbol{\lambda}=\boldsymbol{x}\)
的解要么是空集,要么是
\(\mathbb{R}^n\)
中维度为
\(n-\operatorname{rk}(\boldsymbol{A})\)
的仿射子空间。特别地,当
\(\left(\lambda_1, \ldots, \lambda_n\right) \neq (0, \ldots, 0)\)
时,线性方程
\(\lambda_1 \boldsymbol{b}_1 + \ldots + \lambda_n \boldsymbol{b}_n = \boldsymbol{x}\)
的解是
\(\mathbb{R}^n\)
中的一个超平面。


\(\mathbb{R}^n\)
中,每个
\(k\)
维仿射子空间都是非齐次线性方程组
\(\boldsymbol{A x}=\boldsymbol{b}\)
的解,其中
\(\boldsymbol{A} \in \mathbb{R}^{m \times n}\)

\(\boldsymbol{b} \in \mathbb{R}^m\)
并且
\(\operatorname{rk}(\boldsymbol{A})=n-k\)
。回想一下,对于齐次方程组
\(\boldsymbol{A x}=\mathbf{0}\)
,解是一个向量子空间,我们也可以将其视为一个特殊的仿射空间,其支点为
\(\boldsymbol{x}_0=\mathbf{0}\)

2.8.2 仿射映射

类似于我们在 2.7 节讨论的向量空间之间的线性映射,我们可以在两个仿射空间之间定义仿射映射。线性映射和仿射映射密切相关。因此,我们从线性映射中已经知道的许多性质,例如线性映射的复合(composition)是一个线性映射,也适用于仿射映射。

定义 2.26
(仿射映射)。对于两个向量空间
\(V, W\)
,一个线性映射
\(\Phi: V \rightarrow W\)
,以及
\(\boldsymbol{a} \in W\)
,映射

\[\begin{align*}
\phi: V & \rightarrow W \tag{2.132} \\
\boldsymbol{x} & \mapsto \boldsymbol{a} + \Phi(\boldsymbol{x}) \tag{2.133}
\end{align*}
\]

是从
\(V\)

\(W\)
的仿射映射。向量
\(\boldsymbol{a}\)
被称为
\(\phi\)
的平移向量。

  • 每一个仿射映射
    \(\phi: V \rightarrow W\)
    也是线性映射
    \(\Phi: V \rightarrow W\)

    \(W\)
    中的平移
    \(\tau: W \rightarrow W\)
    的复合,使得
    \(\phi = \tau \circ \Phi\)
    。映射
    \(\Phi\)

    \(\tau\)
    是唯一确定的(uniquely determined)。
  • 仿射映射
    \(\phi: V \rightarrow W, \phi^{\prime}: W \rightarrow X\)
    的复合
    \(\phi^{\prime} \circ \phi\)
    是仿射的。
  • 如果
    \(\phi\)
    是双射的,仿射映射保持几何结构不变。它们还保留维度和平行性。

由delete语句引起的锁范围扩大

阿里云月报中的一句话,出处:http://mysql.taobao.org/monthly/2022/01/01/

但是Ghost Record是可以跟正常的Record一样作为Key Range Lock的加锁对象的。可以看出这相当于把删除操作变成了更新操作,因此删除事务不再需要持有Next Key Lock

这句话意思是:假设delete语句物理删除数据,那么delete事务会持有gap lock,那么会造成锁扩大,而实际上delete操作会转为update操作,最终delete事务持有的gap lock退化为record lock,不会造成锁范围扩大

下面用SQL Server和MySQL做测试,看一下锁的情况

SQL Server 2012

use test
go
CREATE TABLE t (
id int NOT NULL primary key,
c int DEFAULT NULL,
d int DEFAULT NULL
)



CREATE NONCLUSTERED INDEX [ix_t_c] ON [dbo].[t]
(
[c] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO



insert into t values(5,5,5),(10,10,10),(20,20,20),(25,25,25);

使用下面的执行顺序

在session1执行下面语句

--session 1
USE test
GO

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
GO

begin transaction

select id from t where c >10 and c <= 24

delete from t where c = 25


--commit

在session2执行下面语句

--session 2
USE test
GO


SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
GO


insert into t(id,c,d) values(27,27,27); (blocked)

申请的锁,情况如下

分析
:首先我们要关注的加锁对象是二级索引【ix_t_c】,可以看到有三个range锁,这里锁住的范围是

rangeS-S(10,20]

rangeX-X(20, 25]

rangeS-U[25, +∞) 正无穷

正因为rangeS-U 锁,session 2的insert操作被阻塞了,也就是删除 c=25 这行数据,导致键范围锁扩大到 正无穷


MySQL 8.0.28

set global transaction isolation level REPEATABLE READ;
select @@global.transaction_isolation;

use test;

CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;

insert into t values(5,5,5),(10,10,10),(20,20,20),(25,25,25);

SQL语句执行顺序跟SQL Server一样

在session1执行下面语句

--session 1
begin;
select id from t where c >10 and c <= 24 for update;
delete from t where c = 25;

--commit

在session2执行下面语句

--session 2
insert into t(id,c,d) values(27,27,27); (blocked)

申请的锁,情况如下

select * from performance_schema.data_locks\G
*************************** 1. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140111552409600:1217:140111564061632
ENGINE_TRANSACTION_ID: 7643
THREAD_ID: 331
EVENT_ID: 8
OBJECT_SCHEMA: test
OBJECT_NAME: t
PARTITION_NAME: NULL
SUBPARTITION_NAME: NULL
INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140111564061632
LOCK_TYPE: TABLE
LOCK_MODE: IX
LOCK_STATUS: GRANTED
LOCK_DATA: NULL
*************************** 2. row ***************************
ENGINE: INNODB
ENGINE_LOCK_ID: 140111552409600:59:5:1:140111564058528
ENGINE_TRANSACTION_ID: 7643
THREAD_ID: 331
EVENT_ID: 8
OBJECT_SCHEMA: test
OBJECT_NAME: t
PARTITION_NAME: NULL
SUBPARTITION_NAME: NULL
INDEX_NAME: c
OBJECT_INSTANCE_BEGIN: 140111564058528
LOCK_TYPE: RECORD
LOCK_MODE: X,INSERT_INTENTION
LOCK_STATUS: WAITING
LOCK_DATA: supremum pseudo-record
*************************** 3. row ***************************
ENGINE: INNODB
ENGINE_LOCK_ID: 140111552408792:1217:140111564055552
ENGINE_TRANSACTION_ID: 7642
THREAD_ID: 330
EVENT_ID: 12
OBJECT_SCHEMA: test
OBJECT_NAME: t
PARTITION_NAME: NULL
SUBPARTITION_NAME: NULL
INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140111564055552
LOCK_TYPE: TABLE
LOCK_MODE: IX
LOCK_STATUS: GRANTED
LOCK_DATA: NULL
*************************** 4. row ***************************
ENGINE: INNODB
ENGINE_LOCK_ID: 140111552408792:59:5:1:140111564052496
ENGINE_TRANSACTION_ID: 7642
THREAD_ID: 330
EVENT_ID: 12
OBJECT_SCHEMA: test
OBJECT_NAME: t
PARTITION_NAME: NULL
SUBPARTITION_NAME: NULL
INDEX_NAME: c
OBJECT_INSTANCE_BEGIN: 140111564052496
LOCK_TYPE: RECORD
LOCK_MODE: X
LOCK_STATUS: GRANTED
LOCK_DATA: supremum pseudo-record
*************************** 5. row ***************************
ENGINE: INNODB
ENGINE_LOCK_ID: 140111552408792:59:5:4:140111564052496
ENGINE_TRANSACTION_ID: 7642
THREAD_ID: 330
EVENT_ID: 12
OBJECT_SCHEMA: test
OBJECT_NAME: t
PARTITION_NAME: NULL
SUBPARTITION_NAME: NULL
INDEX_NAME: c
OBJECT_INSTANCE_BEGIN: 140111564052496
LOCK_TYPE: RECORD
LOCK_MODE: X
LOCK_STATUS: GRANTED
LOCK_DATA: 20, 20
*************************** 6. row ***************************
ENGINE: INNODB
ENGINE_LOCK_ID: 140111552408792:59:5:5:140111564052496
ENGINE_TRANSACTION_ID: 7642
THREAD_ID: 330
EVENT_ID: 12
OBJECT_SCHEMA: test
OBJECT_NAME: t
PARTITION_NAME: NULL
SUBPARTITION_NAME: NULL
INDEX_NAME: c
OBJECT_INSTANCE_BEGIN: 140111564052496
LOCK_TYPE: RECORD
LOCK_MODE: X
LOCK_STATUS: GRANTED
LOCK_DATA: 25, 25
*************************** 7. row ***************************
ENGINE: INNODB
ENGINE_LOCK_ID: 140111552408792:59:4:4:140111564052840
ENGINE_TRANSACTION_ID: 7642
THREAD_ID: 330
EVENT_ID: 12
OBJECT_SCHEMA: test
OBJECT_NAME: t
PARTITION_NAME: NULL
SUBPARTITION_NAME: NULL
INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140111564052840
LOCK_TYPE: RECORD
LOCK_MODE: X,REC_NOT_GAP
LOCK_STATUS: GRANTED
LOCK_DATA: 20
*************************** 8. row ***************************
ENGINE: INNODB
ENGINE_LOCK_ID: 140111552408792:59:4:5:140111564052840
ENGINE_TRANSACTION_ID: 7642
THREAD_ID: 330
EVENT_ID: 12
OBJECT_SCHEMA: test
OBJECT_NAME: t
PARTITION_NAME: NULL
SUBPARTITION_NAME: NULL
INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140111564052840
LOCK_TYPE: RECORD
LOCK_MODE: X,REC_NOT_GAP
LOCK_STATUS: GRANTED
LOCK_DATA: 25
8 rows in set (0.00 sec)

分析
:这里我们要关注的加锁对象依然是二级索引【c】,这里MySQL的情况跟SQL Server一样

LOCK_MODE: X
LOCK_STATUS: GRANTED
LOCK_DATA: supremum pseudo-record

锁住的范围是 [25, +∞) 正无穷, 所以session 2的insert操作被阻塞了,也就是删除 c=25 这行数据,导致gap lock 扩大到 正无穷

通过上面两个测试,可以知道,即使delete操作在数据表中留下了Ghost Records,但是delete事务造成的gap lock范围没有缩小为Ghost Record的 record lock

因此,阿里云内核月报中的说法有失偏颇,误导读者

本文版权归作者所有,未经作者同意不得转载。

在软件开发和测试过程中,我们经常会遇到需要对网络请求进行录制和回放的需求,以便进行调试、测试和分析。为了模拟真实的用户请求,我们通常会使用各种流量录制回放工具来记录并重放网络请求。

其中,
jvm-sandbox-repeater
是一款功能强大的流量录制回放工具,可以帮助我们轻松实现对网络请求的录制和回放。

1、介绍

jvm-sandbox-repeater
是一个基于
JVM-Sandbox
采用Java来实现的流量录制回放工具,或者可以理解为它是一个基于Java虚拟机的插件,可以直接运行中JVM中,无需对目标应用程序进行任何修改。

它可以在运行时自动拦截和记录 Java 应用程序的网络请求和响应。它是利用
JVM Sandbox
的字节码增强技术,对Java应用进行无侵入式的流量录制和回放。这意味着通过使用
jvm-sandbox-repeater
,我们可以在不修改源代码的情况下,即可轻松实现流量的录制和回放功能。

2、功能特性

jvm-sandbox-repeater 的主要功能特性包括有:

  • 无侵入式录制
    :jvm-sandbox-repeater 通过 JVM-Sandbox字节码增强技术,在运行时对Java应用进行录制,在运行时自动拦截和记录网络请求,无需对源代码进行任何修改。

  • 支持多种协议
    :jvm-sandbox-repeater 支持多种网络协议,包括HTTP/HTTPS、gRPC、Dubbo、TCP 等多种协议的拦截和记录。

  • 灵活的配置
    :jvm-sandbox-repeater 提供了丰富的配置选项,可以根据需要对录制和回放的行为进行定制。

  • 高性能
    :jvm-sandbox-repeater 基于 JVM-Sandbox 技术,具有较低的性能损耗,对应用程序的影响较小。

  • 高精度录制
    :该工具能够精确记录请求和响应的详细信息,包括请求头、请求体、响应状态码、响应体等,确保回放的准确性。

  • 灵活的回放策略
    :支持多种回放策略,如按照时间顺序回放、并发回放等,满足不同的测试需求。

  • 可视化界面
    :提供易于使用的可视化界面,方便用户进行流量的录制、管理和回放操作,降低使用门槛。

  • 高扩展性
    :基于JVM Sandbox平台,易于与其他工具进行集成和扩展。

3、应用场景

在工作中使用
jvm-sandbox-repeater
工具,主要就是借用其中的三类基本能力:

  • 流量录制
    :jvm-sandbox-repeater 可以实时捕获目标应用程序的网络请求,并将其记录下来。通过录制功能,获取目标应用程序的实际网络请求数据,用于后续的分析和调试。

  • 流量回放
    :jvm-sandbox-repeater 支持对录制的网络请求进行回放。通过回放功能,模拟目标应用程序的网络请求,以验证其正确性和稳定性。

  • 请求修改
    :jvm-sandbox-repeater 允许开发人员对录制的网络请求进行修改。通过修改功能,调整网络请求的参数和头部信息,以模拟不同的场景和测试用例。

借助上述这三类基本能力,在测试开发工作中,常用于如下场景应用:

  • 接口测试
    :通过录制生产环境的真实接口流量,在测试环境中进行回放,可以模拟各种复杂场景,验证接口的稳定性和性能。
  • 性能测试
    :利用录制的流量数据进行性能测试,能够更真实地反映系统的性能表现,帮助发现和解决性能瓶颈。
  • 兼容性测试
    :通过在不同版本的Java应用或不同环境下回放录制的流量,可以验证系统的兼容性。

4、原理一:JVM-Sandbox技术是如何工作的?

jvm-sandbox-repeater
是一个基于
JVM-Sandbox
实现的流量回放工具,但估计很多读者,对于JVM-Sandbox是什么,以及它是如何工作的还不太清楚,这里扩展一下。

JVM-Sandbox简单来说,它是一款Java应用沙箱,旨在为Java应用程序提供一个隔离、安全的运行环境。沙箱(Sandbox)技术的主要目的是保护系统安全和稳定性,同时提供更好的应用程序开发和测试环境。通过隔离程序运行的环境,沙箱技术可以防止程序访问到不应访问的资源和数据,从而降低安全风险。

JVM-Sandbox的工作原理可以概括为以下几个关键步骤:

1、类加载
:在JVM沙箱中,所有的类都由Java虚拟机的类加载器加载。类加载器负责从文件系统、网络或其他来源加载Java类,并将其转换为可执行代码。

2、字节码验证
:加载过程中,JVM会对字节码进行验证,以确保其符合Java语言规范。这一步骤的目的是检测潜在的安全漏洞,如类型转换错误、数组越界访问等。

3、安全检查
:经过字节码验证后,JVM会进一步进行安全检查,限制应用程序的行为,防止对系统造成危害。这包括对应用程序的访问权限和资源使用进行限制,例如文件系统访问、网络访问、系统调用等。

4、沙箱执行
:一旦通过了字节码验证和安全检查,JVM会将应用程序放入一个受控的沙箱环境中执行。这个沙箱环境限制了应用程序对系统资源的访问,确保它只能在限定的范围内执行。

此外,
JVM-Sandbox
还利用虚拟化技术创建隔离环境,并通过字节码增强将被mock(模拟)的方法添加到隔离环境中。当这些方法被调用时,JVM-Sandbox通过反射机制查找它们,并使用代理对象将其转发给沙箱中的虚拟机执行。执行完毕后,结果会返回给主程序。

5、原理二:字节码增强技术又是啥?

字节码增强技术
是一种对Java字节码进行修改以增强其功能的技术。它主要是在Java字节码生成之后,通过读取和修改字节码文件,实现对类、方法、字段等元素进行增强。这种技术相当于对应用程序的二进制文件进行修改,主要目的是减少冗余代码,提高性能等。

字节码增强的主要步骤包括:

  • 修改字节码
    :在内存中获取到原来的字节码,然后通过一些工具(如ASM、Javassist)来修改它的byte[]数组,得到一个新的byte数组。
  • 使修改后的字节码生效
    :这可以通过两种方法实现:一是自定义ClassLoader来加载修改后的字节码;二是在JVM加载用户的Class时,拦截并返回修改后的字节码,或者在运行时,使用Instrumentation.redefineClasses方法来替换掉原来的字节码。

字节码增强技术的应用非常广泛,例如可以用于APM(应用性能管理)工具,监控和管理应用软件性能和可用性,保证软件应用程序的正常运行;还可以用于Java问题定位工具,如BTrace

6、安装、使用

1. 下载或引入依赖

首先,你需要从 jvm-sandbox-repeater 的官方网站或 GitHub 仓库下载最新版本的插件 jar 文件。

或者直接在项目的 pom.xml 文件中添加 jvm-sandbox-repeater 的依赖:

<dependency>
    <groupId>com.github.jvm-sandbox-repeater</groupId>
    <artifactId>jvm-sandbox-repeater</artifactId>
    <version>最新版本</version>
</dependency>

2. 配置 Sandbox

将 jvm-sandbox-repeater 插件添加到 JVM 中,将下载的 jvm-sandbox-repeater 插件 jar 文件添加到目标应用程序的类路径中,或者通过 Javaagent 参数将其添加到 JVM 中。

在项目的启动参数中添加以下配置,启用 JVM-Sandbox 和 jvm-sandbox-repeater:

java -javaagent:path/to/jvm-sandbox-repeater.jar=config.yaml -jar your-application.jar

其中,config.yaml 是 jvm-sandbox-repeater 的配置文件,配置 jvm-sandbox-repeater 的相关参数,比如录制文件保存路径、回放速度等可以根据实际情况进行修改。

3. 编写配置文件

创建一个名为 config.yaml 的文件,用于配置 jvm-sandbox-repeater。以下是一个简单的示例:

repeater:
  enabled: true
  recordDir: /path/to/record/dir
  replayDir: /path/to/replay/dir
  filter:
    classes:
      - com.example.service.*
    methods:
      - com.example.service.UserService.getUserInfo

在这个示例中,我们启用了 jvm-sandbox-repeater,并指定了录制和回放的目录。同时,我们还定义了一个过滤器,只对 com.example.service 包下的 UserService 类的 getUserInfo 方法进行录制和回放。

4. 录制和回放

在目标应用程序运行时,jvm-sandbox-repeater 会实时捕获网络请求,并将其记录到指定的录制文件夹中。当需要回放请求时,可以使用 jvm-sandbox-repeater 提供的命令行工具或 API 进行操作。

# 录制请求
java -jar jvm-sandbox-repeater.jar record -c config.yaml

# 回放请求
java -jar jvm-sandbox-repeater.jar replay -c config.yaml

或者,你也可以在代码中使用 jvm-sandbox-repeater 提供的 API 进行录制和回放。以下是一个简单的示例:

import com.github.jvm.sandbox.repeater.Repeater;
import com.github.jvm.sandbox.repeater.Recorder;
import com.github.jvm.sandbox.repeater.Replayer;

public class MyApp {
    public static void main(String[] args) {
        // 初始化 Repeater
        Repeater repeater = new Repeater();
        repeater.init();

        // 开始录制请求
        Recorder recorder = repeater.startRecording();
        recorder.record("http://example.com/api/user", "GET");

        // 停止录制请求
        recorder.stopRecording();

        // 开始回放请求
        Replayer replayer = repeater.startReplaying();
        replayer.replay("http://example.com/api/user", "GET");

        // 停止回放请求
        replayer.stopReplaying();
    }
}

通过以上步骤,你可以使用 jvm-sandbox-repeater 对 Java 应用程序进行流量录制和回放。

5. 查看与分析结果

在回放过程中或回放结束后,可以通过jvm-sandbox-repeater提供的可视化界面查看回放的状态、结果以及性能数据等,进行结果分析。

7、总结

总的来说,
jvm-sandbox-repeater
是一款功能强大、易于使用的流量录制回放工具,可以帮助开发人员或测试人员轻松地实现对网络请求的录制和回放。通过
jvm-sandbox-repeater
,我们可以更加高效地进行调试、测试和分析,提高软件开发测试的效率和质量。如果你正在寻找一款优秀的流量录制回放工具,不妨试试
jvm-sandbox-repeater
,相信它会给你带来意想不到的惊喜!

jvm-sandbox-repeater
的项目源码:

git clone https://github.com/alibaba/jvm-sandbox-repeater.git

缘起

由于某个项目上甲方对服务器进行漏洞扫描,系统为:
openEuler 22.03 (LTS-SP4)
。提示现有OpenSSH版本存在漏洞,需要升级到openssh-9.8p1的版本(目前最新),遂有了这篇记录文章。

PS:切记!在升级SSH之前服务器上最好安装telnet或其他远程工具,以防升级失败导致无法链接上服务器。

1、检查版本

[root@localhost cyunzing]# ssh -V
OpenSSH_8.8p1, OpenSSL 1.1.1wa  16 Nov 2023

可以看到现在的OpenSSH版本是8.8p1,在漏洞包含的版本范围内,需要进行升级。
image

2、下载openssh源码包

编译opensssh时需要服务器上安装的openssl版本大于等于1.1.1,而从上面
ssh -V
的结果可以看到系统默认的OpenSSL版本就是1.1.1。所以不需要考虑升级openssl,直接升级openssh即可。

2.1、服务器不可以联网的情况

在可以链接互联网网络的电脑下载源码包,再上传到内网中的服务器。
下载地址:
https://mirrors.aliyun.com/pub/OpenBSD/OpenSSH/portable/openssh-9.8p1.tar.gz

2.2、服务器可以联网的情况

使用
curl
(openEuler系统最小化安装都自带了这个命令)命令下载,
-o
是指定下载后保存的文件名。

curl -o openssh-9.8p1.tar.gz https://mirrors.aliyun.com/pub/OpenBSD/OpenSSH/portable/openssh-9.8p1.tar.gz

2.3、解压openssh-9.8p1.tar.gz

上面两步结合具体情况任选一种即可,这里我选的是第一种,即
2.1、服务器不可以联网的情况
,保存在服务器的目录地址是
/root/cyunzing/updateBugs/
(目录要提前创建好),然后解压进入目录。

tar zxvf openssh-9.8p1.tar.gz
cd openssh-9.8p1/

3、编译安装openssh

通过
2.3、解压openssh-9.8p1.tar.gz
步骤解压、cd进入
openssh-9.8p1/
目录后执行以下命令,检查编译环境有无问题和进行编译安装。

./configure
make -j4 && make install

4、安装完毕

重启sshd服务

这一步如果没卡住不动就是没问题了。

systemctl restart sshd

5、检查是否安装成功

新开一个远程链接窗口
,执行
ssh -V
对比未升级前的版本信息,判断是否升级完成。

[root@localhost openssh-9.8p1]# ssh -V
OpenSSH_9.8p1, OpenSSL 1.1.1wa  16 Nov 2023

image

liwen01 2024.07.21

前言

UBI (Unsorted Block Images)文件系统是一种用于裸 flash 的文件系统管理层。它是专为管理原始闪存设备而设计,特别适用于嵌入式系统。与 YAFFS2 和
JFFS2
不同的是,它可以提供整个 flash 空间的磨损平衡,并且有良好的扩展性,适用于大容量的
nand flash

(一)MTD、UBI 与 UBIFS

前面介绍的 JFFS2 和 YAFFS2 都是运行在 MTD 之上,而 UBIFS 只能运行在 UBI 之上,UBI 又只能运行于 MTD 之上,所以这里就涉及到 3 个子系统:MTD、UBI、UBIFS。

  • MTD
    提供了对底层闪存硬件的抽象和基本管理。
  • UBI
    在 MTD 之上增加了一层管理,处理闪存的复杂性并提供逻辑卷管理。
  • UBIFS
    是在 UBI 卷上运行的文件系统,充分利用 UBI 的特性,提供高效可靠的文件存储。

这里需要特别注意,
这里所说的闪存,是指裸 flash,而不是经过FTL转换后的 U 盘、SD、TF、SSD 等设备

在 Linux 中,经过 FTL 转换后的 U 盘、SD、TF、SSD 等设备,它们属于块设备,是模拟传统磁盘设计的一种数据结构,以扇区 sector 为读写单位。

而 MTD,它既不是字符设备,也不是块设备,它只是 MTD 设备

(1) MTD (Memory Technology Device)

MTD 是
Linux 内核
中的一个子系统,用于支持不同类型的闪存设备,如
NOR Flash
和 NAND Flash。MTD 提供了一个抽象层,使得文件系统和用户空间程序可以方便地访问底层的闪存硬件。

  • MTD 设备
    :在 Linux 系统中,MTD 设备通常以 /dev/mtdX 和 /dev/mtdblockX 的形式出现,其中 X 是设备编号。
  • MTD 子设备
    :一个 MTD 设备可以被划分为多个子设备,每个子设备可以独立使用。

(2)UBI (Unsorted Block Images)

UBI 是一个在 MTD 设备之上的管理层,专门为 NAND Flash 设计。UBI 处理了 NAND Flash 固有的一些复杂性,如坏块管理和磨损均衡(wear leveling)。UBI将闪存划分为逻辑擦除块,并对它们进行管理。

  • 坏块管理
    :UBI 能够检测和管理坏块,确保数据写入时不会使用坏块。
  • 磨损均衡
    :UBI 通过均匀分布擦写操作,延长闪存的使用寿命。
  • 逻辑卷
    :UBI 支持在 MTD 设备上创建多个逻辑卷,每个卷可以独立使用。

(3)UBIFS (UBI File System)

UBIFS 是专门为 UBI 设计的文件系统,直接在 UBI 卷上运行。UBIFS 充分利用 UBI 的功能,提供了高效和可靠的文件存储解决方案。

  • 动态特性
    :UBIFS 支持动态调整文件系统大小,根据需要分配和回收空间。
  • 日志结构
    :UBIFS 使用日志结构文件系统,减少数据损坏的风险并提高写入性能。
  • 压缩
    :UBIFS 支持多种压缩算法,节省存储空间。

UBIFS 并不是唯一可以在UBI上运行的文件系统,理论上绝大部分文件系统都可以在 UBI 上运行。除了 UBIFS,其它文件系统在 UBI 上使用效率都不高。

(二)镜像文件制作

(1)UBIFS 镜像文件制作

(a)准备测试文件

新建4个测试目录,在目录中创建测试问价,文件使用 /dev/urandom 写入随机数

biao@ubuntu:~/test/ubifs/ubifs_urandom$ tree
.
├── test1
│   ├── file1
│   ├── file1_1
│   └── file1_2
├── test2
│   ├── file2
│   ├── file2_1
│   └── file2_2
├── test3
│   ├── file3
│   ├── file3_1
│   └── file3_2
└── test4
    ├── file4
    ├── file4_1
    └── file4_2

4 directories, 12 files
biao@ubuntu:~/test/ubifs/ubifs_urandom$

文件大小信息如下:

biao@ubuntu:~/test/ubifs$ du ubifs_urandom   
1904    ubifs_urandom/test3
168     ubifs_urandom/test2
1504    ubifs_urandom/test1
1476    ubifs_urandom/test4
5056    ubifs_urandom
biao@ubuntu:~/test/ubifs$

(b)制作 UBIFS 镜像文件

mkfs.ubifs -r ubifs_urandom -m 2048 -e 129024 -c 10000 -o ubifs_urandom.ubifs

各参数的作用:

-r, -d, --root=DIR       build file system from directory DIR
-m, --min-io-size=SIZE   minimum I/O unit size
-e, --leb-size=SIZE      logical erase block size
-c, --max-leb-cnt=COUNT  maximum logical erase block count
-o, --output=FILE        output to FILE

上面命令的作用是:将 ubifs_urandom 目录里面的文件打包成一个页大小为 2048(2KB)、逻辑擦除块大小为 129024(126KB)、最大逻辑块为 10000 的 UBIFS 镜像文件 (ubifs_urandom.ubifs)。

这里有几点需要注意:

  • -m 是页大小,不是子页大小
  • -e 设置的逻辑擦除块大小要与UBI里的相同,不然挂载的时候会报错误

(2)UBI 镜像文件制作

在 UBIFS 的基础上,制作一个UBI镜像文件

(a)制作 UBI 镜像配置文件

创建配置文件 ubinize.cfg

[ubifs]
mode=ubi
image=ubifs_urandom.ubifs
vol_id=0
vol_size=256MiB
vol_type=dynamic
vol_name=ubifs_urandom
vol_flags=autoresize
  • image 为我们上面制作的UBIFS文件系统镜像文件
  • vol_id 指定卷 ID,这个是在有多个卷的时候使用
  • vol_size 定义卷的大小
  • vol_type 设置为动态卷,卷的大小可以变化
  • vol_name 卷的名字
  • vol_flags 自动调整大小

(b)制作 UBI 镜像文件

ubinize -o ubi.img -m 2048 -O 512 -p 128KiB ubinize.cfg

UBI 镜像文件和 UBIFS 的镜像文件,都需要根据实际 Flash 的参数进行设置

上面命令的作用是:将一个 ubifs 镜像文件制作成一个页大小为 2048(2KB)、子页大小为 (256Byte),物理擦除块大小为 128KB 的 UBI 镜像文件。

(三) 挂载 UBIFS 文件系统

为了方便调试,我们这里直接使用PC机上的虚拟 MTD 设备来仿真Flash。Linux 内核中有 3 种 MTD 设备模拟器可用:

  • mtdram:在 RAM 中模拟 NOR 闪存;
  • nandsim:在 RAM 中模拟 NAND 闪存;
  • block2mtd:在块设备上模拟 NOR 闪存。

(1)加载 nandsim 模块

这里仿一个 1GiB, 2048 bytes page的 nand flash。

sudo modprobe nandsim first_id_byte=0xec second_id_byte=0xd3 third_id_byte=0x51 fourth_id_byte=0x95

可以通过 /proc/mtd 和 /dev/mtd0 查看模拟的 nandflsh 信息

biao@ubuntu:~/test/ubifs$ cat /proc/mtd 
dev:    size   erasesize  name
mtd0: 40000000 00020000 "NAND simulator partition 0"
biao@ubuntu:~/test/ubifs$ mtdinfo /dev/mtd0 
mtd0
Name:                           NAND simulator partition 0
Type:                           nand
Eraseblock size:                131072 bytes, 128.0 KiB
Amount of eraseblocks:          8192 (1073741824 bytes, 1024.0 MiB)
Minimum input/output unit size: 2048 bytes
Sub-page size:                  512 bytes
OOB size:                       64 bytes
Character device major/minor:   90:0
Bad blocks are allowed:         true
Device is writable:             true
biao@ubuntu:~/test/ubifs$

上面加载 nandsim 的时候,有定义4个ID值,具体值需要根据芯片手册的数据来设置。

第一字节为制造商代码、第二字节为设备代码、第三、四字节为Flash特定参数

下面是几个示例:

modprobe nandsim first_id_byte=0x20 second_id_byte=0x33 - 16MiB, 512 bytes page;
modprobe nandsim first_id_byte=0x20 second_id_byte=0x35 - 32MiB, 512 bytes page;
modprobe nandsim first_id_byte=0x20 second_id_byte=0x36 - 64MiB, 512 bytes page;
modprobe nandsim first_id_byte=0x20 second_id_byte=0x78 - 128MiB, 512 bytes page;
modprobe nandsim first_id_byte=0x20 second_id_byte=0x71 - 256MiB, 512 bytes page;
modprobe nandsim first_id_byte=0x20 second_id_byte=0xa2 third_id_byte=0x00 fourth_id_byte=0x15 - 64MiB, 2048 bytes page;
modprobe nandsim first_id_byte=0xec second_id_byte=0xa1 third_id_byte=0x00 fourth_id_byte=0x15 - 128MiB, 2048 bytes page;
modprobe nandsim first_id_byte=0x20 second_id_byte=0xaa third_id_byte=0x00 fourth_id_byte=0x15 - 256MiB, 2048 bytes page;
modprobe nandsim first_id_byte=0x20 second_id_byte=0xac third_id_byte=0x00 fourth_id_byte=0x15 - 512MiB, 2048 bytes page;
modprobe nandsim first_id_byte=0xec second_id_byte=0xd3 third_id_byte=0x51 fourth_id_byte=0x95 - 1GiB, 2048 bytes page;

(2)挂载 UBIFS 文件系统

(a) 加载 UBI 内核模块

sudo modprobe ubi mtd=0

这里将 ubi 加载到了 mtd 的设备 0 上

(b) 分离 MTD 上的设备 0

sudo ubidetach /dev/ubi_ctrl -m 0

(c)格式化 MTD 设备并写入 UBI 镜像文件

sudo sudo ubiformat /dev/mtd0 -s 512 -f ubi.img

(d)UBI设备附加回 MTD 设备 0 上

sudo ubiattach /dev/ubi_ctrl -m 0 -O 512

(e)挂载 UBIFS 到指定目录

sudo mount -t ubifs ubi0 /home/biao/test/ubifs/ubifs_simulator

(f)查看挂载状态

biao@ubuntu:~/test/ubifs$ df -h
Filesystem      Size  Used Avail Use% Mounted on
......
ubi0            927M  4.8M  923M   1% /home/biao/test/ubifs/ubifs_simulator
......
biao@ubuntu:~/test/ubifs$ 
biao@ubuntu:~/test/ubifs/ubifs_simulator$ ls
test1  test2  test3  test4

可以看到制作的 UBIFS 镜像文件已经被挂载到了 ubi0 卷上,挂载目录上的文件也就是我们的测试文件。

(四) UBIFS 镜像文件分析

上面我们制作了两个镜像文件 UBIFS 和 UBI,然后再将 UBI 镜像文件加载到 PC机上的 NAND Flash 模拟器 nandsim 上。要了解 UBIFS 的工作原理,我们有必要对它在 Flash 上的数据结构进行分析。

(1)UBIFS 数据结构

ubifs-media.h 中可以看到 UBIFS 所有数据结构定义,下面这个是通用数据结构,有
幻数
、crc校验、序列号、长度、节点类型、节点组类型这些信息,其中有效节点有11种。

通用头部结构体定义如下

/**
 * struct ubifs_ch - common header node.
 * @magic: UBIFS node magic number (%UBIFS_NODE_MAGIC)
 * @crc: CRC-32 checksum of the node header
 * @sqnum: sequence number
 * @len: full node length
 * @node_type: node type
 * @group_type: node group type
 * @padding: reserved for future, zeroes
 *
 * Every UBIFS node starts with this common part. If the node has a key, the
 * key always goes next.
 */
struct ubifs_ch {
	__le32 magic;
	__le32 crc;
	__le64 sqnum;
	__le32 len;
	__u8 node_type;
	__u8 group_type;
	__u8 padding[2];
} __packed;

节点类型定义:

enum {
	UBIFS_INO_NODE,
	UBIFS_DATA_NODE,
	UBIFS_DENT_NODE,
	UBIFS_XENT_NODE,
	UBIFS_TRUN_NODE,
	UBIFS_PAD_NODE,
	UBIFS_SB_NODE,
	UBIFS_MST_NODE,
	UBIFS_REF_NODE,
	UBIFS_IDX_NODE,
	UBIFS_CS_NODE,
	UBIFS_ORPH_NODE,
	UBIFS_NODE_TYPES_CNT,
};

(2)UBIFS 节点布局

我们前面通过 mkfs.ubifs 制作生成的 UBIFS 镜像文件,它包含 5 种节点类型,在镜像文件中的布局如下图。

最开始是超级快、后面是两个Master、 最后面是Index Node,它们各自的功能如下:

Superblock Node
: 存储文件系统的基本信息,如大小、状态、版本等。

Master Node
: 保存文件系统的当前状态,包括对日志头和根索引节点的指针。

Commit Start Node
:标记一个提交操作的开始。它用于在文件系统崩溃时确定哪些数据是已提交的。

Data Node
: 存储文件的数据。每个数据节点都与一个文件的特定部分对应。

Index Node
:用于构建UBIFS的索引结构,类似于传统文件系统中的索引节点(inode)。

(3)UBIFS 节点工作原理

挂载文件系统

  1. 读取superblock和master node以恢复文件系统的基本状态和重要元数据指针。
  2. 初始化其他必要的数据结构和缓存。

文件操作

  1. 创建文件:在索引树中添加新的索引节点,并分配相应的数据节点来存储文件内容。
  2. 读写文件:通过索引节点找到相应的数据节点,然后执行读写操作。
  3. 修改文件:修改的数据会写入新的数据节点,并更新相应的索引节点。

提交

  1. 写入 commit start node 以标记提交的开始。
  2. 将所有修改的数据节点和索引节点写入闪存。
  3. 更新 master node 以反映最新的文件系统状态。

崩溃恢复:

  1. 检查 commit start node 以确定哪些提交操作已完成。
  2. 通过 master node 恢复文件系统的最新一致状态。

(4)UBIFS 节点分析

这里我们只分析superblock节点,其它node也类似
使用 hexdum 查看 一个 ubifs 镜像文件,最开始的位置就是superblock

00000000  31 18 10 06 e6 e4 54 b6  d9 05 00 00 00 00 00 00  |1.....T.........|
00000010  00 10 00 00 06 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 08 00 00 00 00 02 00  35 00 00 00 64 00 00 00  |........5...d...|
00000030  00 00 16 00 00 00 00 00  04 00 00 00 02 00 00 00  |................|
00000040  01 00 00 00 01 00 00 00  08 00 00 00 00 01 00 00  |................|
00000050  04 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 00 00 00 00  00 ca 9a 3b 1c fe ed 83  |...........;....|
00000070  7a ef 48 f7 83 2c 10 74  b9 36 09 9b 00 00 00 00  |z.H..,.t.6......|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
.........

对上面数据镜像解析,可以看到如下信息:

(五) UBI 镜像文件分析

(1) UBI 数据布局

UBI 主要的数据结构是 ubi_ec_hdr 和 ubi_vid_hdr :

ubi_ec_hdr
: (Erase Counter Header) 包含擦除计数信息头部结构,主要作用是记录和管理物理擦除块的擦除次数。

ubi_vid_hdr
: (Volume Identifier Header) 包含卷标识信息头部结构,主要作用是管理和识别物理卷块中的数据。

一个UBI卷被分成多个块,每个块都有这两个头部。ubi_ec_hdr 记录每个块被擦除的次数,帮助管理块的寿命和可靠性。而 ubi_vid_hdr 则确保每个块在卷中的正确位置和数据完整性。

它们在镜像文件或是 flash 中的数据布局如下:

(2) 数据分析

查看 ubi.img 镜像文件的前4KB 数据,这里需要特别注意的是,
UBI的数据是按大端模式存储的,与之前分析的镜像文件有所不同

biao@ubuntu:~/test/ubifs$ hexdump -s 0 -n 4096 -C ubi.img 
00000000  55 42 49 23 01 00 00 00  00 00 00 00 00 00 00 00  |UBI#............|
00000010  00 00 02 00 00 00 08 00  20 d2 d3 a0 00 00 00 00  |........ .......|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 92 3a 9d cd  |.............:..|
00000040  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000200  55 42 49 21 01 01 00 05  7f ff ef ff 00 00 00 00  |UBI!............|
00000210  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000230  00 00 00 00 00 00 00 00  00 00 00 00 b8 25 64 a8  |.............%d.|
00000240  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000800  00 00 08 21 00 00 00 01  00 00 00 00 01 00 00 0d  |...!............|
00000810  75 62 69 66 73 5f 75 72  61 6e 64 6f 6d 00 00 00  |ubifs_urandom...|
00000820  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000890  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000008a0  00 00 00 00 00 00 00 00  8c 7e c0 aa 00 00 00 00  |.........~......|
000008b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
......

ubi_ec_hdr 的数据解析如下

ubi_vid_hdr 的数据解析如下

(3) UBI工作原理

UBI 的工作原理可以通过下面官方的演示视频来介绍:

坏快管理实现原理可以查看下面视频:

(六)优缺点

(1)优点

(a)耐用性和可靠性

磨损均衡
:UBI能够有效地管理闪存的擦除次数,确保所有的擦除块均匀地使用,从而延长闪存的寿命。

坏块管理
:UBI能够检测并处理坏块,确保数据不被写入这些损坏的区域,提高文件系统的可靠性。

崩溃恢复
:UBI在系统崩溃或掉电后具有良好的恢复能力,能够尽量减少数据损失。

(b)动态大小管理

动态分区调整
:UBI允许动态调整分区的大小,这对于存储需求变化较大的应用非常有用。

灵活的空间管理
:UBI可以灵活地管理闪存空间,支持动态的文件系统分配和调整。

(c)支持大容量闪存
:UBI支持大容量的闪存设备,适合用于需要大量存储空间的嵌入式系统中。

(2)缺点

(a)复杂性增加

设计复杂
:UBI的实现较为复杂,需要在内核中增加额外的层次来管理闪存,这增加了系统设计和维护的复杂性。

调试困难
:由于其复杂的机制,UBI的问题排查和调试比传统文件系统更加困难。

(b)资源消耗

内存占用
:UBI需要额外的内存来维护其数据结构,对于内存资源有限的嵌入式系统可能会带来一定的压力。

CPU消耗
:UBI的运行需要额外的CPU资源来执行磨损均衡和垃圾回收等任务,可能会影响系统的整体性能。

(c)不适合所有应用

专用性强
:UBI主要针对原始闪存设备进行优化,对于使用其他存储介质(如eMMC、SD卡等)的系统,其优势可能不明显。

特定领域应用
:UBI的设计主要面向嵌入式系统,对于桌面或服务器系统,其他文件系统(如EXT4、XFS等)可能更为适合。

结尾

总的来说,UBI文件系统在需要高可靠性、高耐用性和灵活空间管理的嵌入式系统中表现出色,但其复杂性和资源消耗也需要在具体应用中进行权衡。

----------------End----------------
如需获取更多内容
请关注 liwen01 公众号