2024年9月

本文介绍基于
Python
语言,读取
Excel
表格文件数据,并将其中
符合我们特定要求

那一行
加以复制指定的次数,而
不符合要求

那一行
则不复制;并将所得结果保存为新的
Excel
表格文件的方法。

这里需要说明,在我们之前的文章
多次复制Excel符合要求的数据行:Python批量实现
中,也介绍过实现类似需求的另一种
Python
代码,大家如果有需要可以查看上述文章;而上述文章中的代码,由于用到了
DataFrame.append()
这一个在最新版本
pandas
库中取消的方法,因此有的时候可能会出现报错的情况;且本文中的需求较之上述文章有进一步的提升,因此大家主要参考本文即可。

首先,我们来明确一下本文的具体需求。现有一个
Excel
表格文件,在本文中我们就以
.csv
格式的文件为例;其中,如下图所示,这一文件中有一列(也就是
inf_dif
这一列)数据比较关键,我们希望对这一列数据加以处理——对于
每一行
,如果
这一行的这一列数据的值
在指定的范围内,那么就将这一行复制指定的次数(复制的意思相当于就是,新生成一个
和当前行
一摸一样数据的
新行
);而对于
符合我们要求的行
,其具体要
复制的次数
也不是固定的,也要根据
这一行的这一列数据的值
来判断——比如如果这个数据在
某一个值域内
,那么这一行就复制
10
次;而如果在
另一个值域内
,这一行就复制
50
次等。

image

知道了需求,我们就可以开始代码的书写。其中,本文用到的具体代码如下所示。

# -*- coding: utf-8 -*-
"""
Created on Thu Jul  6 22:04:48 2023

@author: fkxxgis
"""

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

original_file_path = "E:/01_Reflectivity/99_Model/02_Extract_Data/26_Train_Model_New/Train_Model_0715.csv"
result_file_path = "E:/01_Reflectivity/99_Model/02_Extract_Data/26_Train_Model_New/Train_Model_0715_Over_NIR_0717_2.csv"

df = pd.read_csv(original_file_path)
duplicated_num_0 = 70
duplicated_num_1 = 35
duplicated_num_2 = 7
duplicated_num_3 = 2

num = [duplicated_num_0 if (value <= -0.12 or value >= 0.12) else duplicated_num_1 if (value <= -0.1 or value >= 0.1) \
else duplicated_num_2 if (value <= -0.07 or value >= 0.07) else duplicated_num_3 if (value <= -0.03 or value >= 0.03) \
else 1 for value in df.inf_dif]
duplicated_df = df.loc[np.repeat(df.index.values, num)]

plt.figure(0)
plt.hist(df["inf_dif"], bins = 50)
plt.figure(1)
plt.hist(duplicated_df["inf_dif"], bins = 50)

duplicated_df.to_csv(result_file_path, index=False)

其中,上述代码的具体含义如下。

首先,我们需要导入所需的库,包括
numpy

pandas

matplotlib.pyplot
等,用于后续的数据处理和绘图操作。接下来,即可开始读取原始数据,我们使用
pd.read_csv()
函数读取文件,并将其存储在一个
DataFrame
对象
df
中;这里的原始文件路径由
original_file_path
变量指定。

随后,我们开始设置重复次数。在这里,我们根据特定的条件,为每个值设定重复的次数。根据
inf_dif
列的值,将相应的重复次数存储在
num
列表中。根据不同的条件,使用条件表达式(
if-else
语句)分别设定了不同的重复次数。

接下来,我们使用
loc
函数和
np.repeat()
函数,将数据按照重复次数复制,并将结果存储在
duplicated_df
中。

最后,为了对比我们数据重复的效果,可以绘制直方图。在这里,我们使用
matplotlib.pyplot
库中的
hist()
函数绘制了两个直方图;其中,第一个直方图是原始数据集
df

inf_dif
列的直方图,第二个直方图是复制后的数据集
duplicated_df

inf_dif
列的直方图。通过指定
bins
参数,将数据分成
50
个区间。

完成上述操作后,我们即可保存数据。将复制后的数据集
duplicated_df
保存为
.csv
格式文件,路径由
result_file_path
变量指定。

执行上述代码,我们将获得如下所示的两个直方图;其中,第一个直方图是原始数据集
df

inf_dif
列的直方图,也就是还未进行数据复制的直方图。

其次,第二个直方图是复制后的数据集
duplicated_df

inf_dif
列的直方图。

可以看到,经过前述代码的处理,我们原始的数据分布情况已经有了很明显的改变。

至此,大功告成。

MySQL中的索引模型

Mysql中的索引使用的数据结构一般为搜索树,这里的搜索树,一般使用B树,这里补一下数据结构中的B树结构;说B树之前,先顺一个前置的知识点,平衡二叉树;

平衡二叉树

二叉树应该都不陌生,大学数据结构的基本入门,
二叉排序树
是基于二叉树上多了个“
有序
”的概念,简单来说,即
左 < 右

右<左

,反正就是,树是按着顺序建立的 。

相比较普通的二叉树,二叉排序树具有“顺序”的特点,但是当极端情况下,即单边顺序排列下去,二叉排序树就成了单链表了,失去了树的意义,于是在二叉排序树的基础上进一步加强,即:
满足二叉排序树特点同时又左右子树高度差小于等于1,就有了平衡二叉树

平衡二叉树的特点如下:

  1. 二叉排序树

  2. 任何一个节点的左子树或者右子树都是「平衡二叉树」(左右高度差小于等于 1)

B树

平衡二叉树已经很好了,其因为顺序、且限制了树的饱和,于是使得平衡二叉树在检索时,具有很高的性能,复杂度才O(logN),此时影响查询性能的瓶颈就演变到节点数量N了;根据树的特点,进行比对计算的次数取决于树的高度,如果节点的数量固定,我们可以通过控制每层的节点数来控制树的高度,不拘泥于“二叉”这一特性,变成多叉的平衡树(B-树)。

B树是一个绝对平衡树,所有的叶子节点在同一高度。在每个节点存储多个元素,在每个节点除了指针节点外,还存储相应的数据;相比二叉平衡查找树,在整个查找过程中,虽然数据的比较次数并没有明显减少,但是磁盘IO次数会大大减少;B树的高度一般2至3层就能满足大部分的应用场景,所以使用B树构建索引可以很好的提升查询的效率。

B树特点

  • 每个节点至多可以拥有m棵子树(m阶)。
  • 根节点,至少有2个节点。
  • 非根非叶的节点至少有的Ceil(m/2)个子树(Ceil表示向上取整,图中5阶B树,每个节点至少有3个子树,也就是至少有3个叉)。
  • 非叶节点中的信息包括[n,A0,K1,A1,K2,A2,…,Kn,An],,其中n表示该节点中保存的关键字个数,K为关键字且Ki<Ki+1,A为指向子树根节点的指针。
  • 从根到叶子的每一条路径都有相同的长度,也就是说,叶子节在相同的层,并且这些节点不带信息,实际上这些节点就表示找不到指定的值,也就是指向这些节点的指针为空。

B+树

尽管B树能较为明显的提升效率,但是它节点携带数据这一特征,随着存储数据的增长,必然导致空间的占用,如果使用到数据库索引时,意味着会树相应就会变高,一个页中可存储的数据量就会变少,磁盘IO次数就会变大。

所以引入了B树的另一增强B+树,相比于B树,B+树的特点在于:

  1. 内部节点只存储键值,不存储数据。数据只存储在叶子节点中,并且叶子节点包含了全部的键值和指向数据的指针。

  2. 叶子节点之间有双向链表链接,这使得范围查询更加高效,因为可以通过链表顺序访问叶子节点。

B+树的最底层叶子节点包含了所有的索引项。从图上可以看到,B+树在查找数据的时候,由于数据都存放在最底层的叶子节点上,所以每次查找都需要检索到叶子节点才能查询到数据。所以在需要查询数据的情况下每次的磁盘的IO跟树高有直接的关系,但是从另一方面来说,由于数据都被放到了叶子节点,所以放索引的磁盘块锁存放的索引数量是会跟这增加的,所以相对于B树来说,B+树的树高理论上情况下是比B树要矮的。也存在索引覆盖查询的情况,在索引中数据满足了当前查询语句所需要的全部数据,此时只需要找到索引即可立刻返回,不需要检索到最底层的叶子节点。

索引分类

逻辑划分

按功能分

主键索引

一张表只能有一个主键索引,不允许重复、不允许为 NULL,使用关键字
PRIMARY KEY
定义;
PRIMARY KEY
的通常基于B树(B-Tree)实现,本质上是特殊的唯一索引。

ALTER TABLE TableName ADD PRIMARY KEY(column_list);

唯一索引

数据列不允许重复,允许为 NULL 值;一张表可有多个唯一索引,索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一;一般使用
UNIQUE INDEX
定义

CREATE UNIQUE INDEX IndexName ON`TableName`(`字段名`(length));
# 或者
ALTER TABLE TableName ADD UNIQUE (column_list);

普通索引

一张表可以创建多个普通索引,一个普通索引可以包含多个字段,
允许数据重复
,允许 NULL 值插入;

CREATE INDEX IndexName ON`TableName`(`字段名`(length));
# 或者
ALTER TABLE TableName ADD INDEX IndexName(`字段名`(length));

按使用分

按使用划分其实就是单个列进行索引还是多个列组合索引。

  • 单例索引:一个索引只包含一个列,一个表可以有多个单例索引。
  • 组合索引:一个组合索引包含
    两个或两个以上
    的列。查询的时候遵循 mysql 组合索引的 “最左前缀”原则,即使用 where 时条件要按照建立索引的时候字段的排列方式放置索引才会生效。MySQL中可以使用
    唯一索引

    普通索引
    进行组合索引
--多列组合(普通索引)
CREATE INDEX <index_name> ON <table_name >(column1, column2, column3);
ALTER TABLE <table_name> ADD INDEX <index_name>(column1, column2,column3);--多列组合(唯一索引) CREATE UNIQUE INDEX <index_name> ON <table_name>(column1, column2,column3);
ALTER TABLE <table_name> ADD UNIQUE INDEX <index_name> (column1, column2,column3));

关于组合索引使用中的性能及使用原则,可直接跳至组合索引的使用原则

物理划分

之所以说根据物理划分是索引在物理存储原理上的特点。而且主要是针对B+树索引结构来讲

簇的含义:
为了提高某个属性(或属性组)的查询速度,把这个或这些属性(称为聚簇码)上具有相同值的元组集中存放在连续的物理块。

聚簇索引

聚簇索引(clustered index)不是单独的一种索引类型,而是一种数据存储方式。这种存储方式根据表的主键构造一棵B+树,且B+树叶子节点存放的都是表的行记录数据时,该主键索引为聚簇索引。

在聚簇索引中,其数据文件本身就是索引文件。 树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,进行查询时,根据搜索算法,在树上找到叶子节点后,数据也一并找到。这种情况也就是常说的
覆盖索引

注:每张表最多只能拥有一个聚簇索引

非聚簇索引

非聚簇索引又叫辅助索引或二级索引(在Mysql中primary key之外的均为辅助索引),与簇索引相反,数据和索引是分开的,B+Tree 叶节点的 data 域存放的是主键和该字段的值。在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果指定的内容 存在,则取出其 data 域中的主键,然后再在聚簇索引(Primary Key 索引树上再次搜索)。

假设我们针对user表查询

SELECT sex,age FROM user WHERE name = '陈芳';

此时user表存在基于主键ID创建的聚簇索引,和基于name创建的非聚簇索引,那么查询过程则是这样的:

首先因为使用了name列进行等值查询,此时会先使用Name索引的B+树,进行搜索,当找到name为陈芳的叶子节点时,会拿到其ID,再回到基于ID的聚簇索引B+树上进行基于ID=1的搜索,最终找到其叶子节点,拿到上面的性别和年龄,其中这种需要两段使用索引的过程就是常说的回表查询。

虽然
InnoDB

MyISAM
存储引擎都默认使用B+树结构存储索引,但是只有InnoDB的主键索引才是聚簇索引,
InnoDB中的辅助索引以及MyISAM使用的都是非聚簇索引

索引的使用

创建索引的原则


  • 经常需要搜索的列
    上,可以加快搜索的速度
  • 在作为
    主键
    的列上,强制该列的唯一性和组织表中数据的排列结构

  • 经常用在连接(JOIN)的列
    上,这些列主要是一外键,可以加快连接的速度

  • 经常需要根据范围(<,<=,=,>,>=,BETWEEN,IN)进行搜索的列上创建索引
    ,因为索引已经排序,其指定的范围是连续的

  • 经常需要排序(order by)的列
    上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
  • 在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。

数据类型对于索引的不同表现

VARCHAR类型

VARCHAR
类型用于存储可变长度的字符串。

  • 前缀索引
    :由于
    VARCHAR
    类型的列可能很长,为了节省空间和提高效率,可以只为每个值的前几个字符创建索引,称为前缀索引。但是,前缀索引会降低索引的选择性,可能需要扫描更多的行来找到匹配的记录。
  • 区分度
    :在选择前缀长度时,应该考虑列的区分度。如果大多数行的前缀都相同,那么前缀索引的效果可能不佳。
  • 索引效率

    等值查询、
    IN
    子句查询

    等效率较高,
    VARCHAR
    类型的列在排序和比较时是按字典顺序进行的,这可能会影响
    范围查询
    的性能。
    VARCHAR
    类型的索引在没有使用函数或表达式的情况下性能较好,但如果在查询中对字符串列使用了函数(如
    CONCAT

    SUBSTRING
    等),索引可能失效,导致全表扫描。

数字类型

数字类型泛指数字相关类型,例如
INT、FLOAT、Double等

  • 选择性
    :整数类型的列通常具有很高的选择性,这意味着索引中的值分布均匀,可以有效地减少查询中需要检查的行数。
  • 索引效率

    等值查询、范围查询(
    >

    <

    BETWEEN
    等)

    效率较高,数字类型的索引在进行范围查询时查询性能上通常优于字符串类型。

日期类型

DATETIME
类型用于存储日期和时间。

  • 时区影响
    :在处理跨时区的数据时,需要注意时区转换可能对索引的使用和查询结果产生影响。
  • 函数影响
    :如果查询中使用了日期函数(如
    DATE_FORMAT
    ),可能会导致索引失效
  • 索引效率

    DATETIME
    类型的列在进行
    等值查询

    范围查询
    时效率较高,因为日期和时间的比较操作非常快速。

其他

  • 列的NULL值
    :如果列中包含NULL值,索引的行为可能会受到影响。例如,
    VARCHAR
    类型的列如果允许NULL值,可能会在索引中占用额外的空间。
  • 列的默认值
    :默认值可能会影响索引的选择性。例如,如果一个
    INT
    类型的列有一个默认值,那么很多行可能会有相同的默认值,这会降低索引的选择性。
  • 列的更新频率
    :频繁更新的列可能会使索引变得碎片化,从而影响索引的性能。因此,对于经常更新的列,可能需要定期重建索引。

组合索引的使用原则

在MySQL中使用组合索引时,"
最左前缀
"原则决定了索引的使用方式和查询优化的效果。这个原则指的是,
MySQL会按照组合索引中列的顺序,从左到右使用索引中的列
。只有当索引的最左列被包含在查询条件中时,索引才会被有效地使用。如果查询条件中跳过了索引中的某个列,那么该列及其右边的所有列都不会被用于索引查询。

例如:将tb_flow_visit表的
源ip、目的ip、访问时间
三个字段设置为组合索引(注意设置的顺序)

ALTER TABLE tb_flow_visit ADD INDEX commIndies (sip, dip,visit_time);

顺序优先

在组合索引中,MySQL会按照(
源ip、目的ip、访问时间
)这个顺序来匹配条件。如果
WHERE
子句中首先出现的是
sip字段匹配相关
的条件,那么索引才会被使用。

--有效索引查询(全部匹配)
SELECT * FROM tb_flow_visit WHERE sip = '192.168.1.1' AND dip = '192.168.1.2' AND visit_time = '2023-08-01';--有效索引查询(部分匹配)
SELECT * FROM tb_flow_visit WHERE sip = '192.168.1.1' AND dip = '192.168.1.2';

不能跳过

如果你的查询条件首先使用了
dip(或者visit_time)
而没有包含
sip
,那么这个组合索引将不会被使用,因为违反了最左前缀原则。

--无效索引,不能跳过sip
SELECT * FROM tb_flow_visit WHERE  dip = '192.168.1.2' AND visit_time = '2024-08-01';--无效索引,组合索引中,如过不按照索引顺序,即便单独使用其中任何一列也无效
SELECT * FROM tb_flow_visit WHERE dip = '192.168.1.2';
SELECT * FROM tb_flow_visit WHERE visit_time > '2024-08-01';;

部分匹配

如果
WHERE
子句中包含了从索引最左列开始的连续列,那么索引可以被部分使用。例如:使用了最左列
sip
的字段和
visit_time
字段,因此会有效使用
sip
列的索引,但不会使用
dip

visit_time
列的索引

SELECT * FROM tb_flow_visit WHERE sip = '192.168.1.1' AND visit_time > '2024-08-01';

范围查询限制

在MySQL中,最左原则的范围查询限制是指在使用组合索引时,如果查询条件中包含了范围查询(如
>

<

BETWEEN

LIKE
等),那么这个范围查询必须应用于组合索引的最左列或者紧挨着最左列的列(如果最左列是常量条件)。如果范围查询跳过了最左列或者在非最左列上使用,那么索引将不会被有效使用; 例如在最左列(sip)上使用了范围查询(如sip
> '192.168.1.2'
),那么后续的列仍然可以被索引使用,但最左列之后的列必须是等值查询,不能是范围查询。

--无效索引
SELECT * FROM tb_flow_visit WHERE  sip > '192.168.1.2' AND dip > '192.168.2.1';AND  visit_time = '2024-08-01';--有效索引
SELECT * FROM tb_flow_visit WHERE  sip > '192.168.1.2' AND dip = '192.168.2.1' AND visit_time = '2024-08-01';

23.1 什么是快速系统调用

系统调用是操作系统为3特权级任务提供服务的一种手段。在32位操作系统中,我们通过中断实现了系统调用。由于系统调用是一个使用非常频繁的机制,且中断也不是专门为系统调用设计的,因此,64位CPU提供了系统调用的专用机制:快速系统调用。

快速系统调用由专用的
syscall
指令发起,并由专用的
sysret
指令返回。
syscall
必须从3特权级转移到0特权级,
sysret
必须从0特权级返回到3特权级。快速系统调用全程使用寄存器传参,并且系统调用函数的
cs:rip
是预设好的,因此,
syscall/sysret
均不需要参数。

综上,快速系统调用的整套机制都是非常固定的,这就带来了高效率。

23.2 快速系统调用的安装

在使用快速系统调用之前,需要先安装好快速系统调用所需的组件,这涉及到4个MSR。

23.2.1
IA32_EFER

快速系统调用这个功能在初始状态下是关闭的,其开关位于
IA32_EFER
的第0位。这个MSR我们已经见过了,它的编号为
0xc0000080

23.2.2
IA32_STAR

这个MSR的低32位是保留位;第32~47位用于设定
syscall
使用的0特权级段选择子;第48~63位用于设定
sysret
使用的3特权级段选择子。

注意,这里没有说设定的是"代码段选择子",而仅仅是"段选择子",这是因为选择子的设定有一套比较奇怪的定义:

  • 对于第32~47位,其数值本身会被视为0特权级代码段选择子;这个数值加8得到的数值会被视为0特权级数据段选择子
  • 对于第48~63位,其数值本身会被视为3特权级兼容模式代码段选择子;这个数值加8得到的数值会被视为3特权级数据段选择子;这个数值加16得到的数值会被视为3特权级IA32-e模式代码段选择子。那么,当执行
    sysret
    时,其到底选择哪个代码段呢?这个问题将在下文中讨论

段选择子是描述符索引值左移3位得到的,因此加8即为GDT中的下一个描述符。也就是说,第32~47位设定的是两个连续的段描述符中的第一个;第48~63位设定的是三个连续的段描述符中的第一个。不过,由于我们的操作系统从不使用兼容模式代码段,因此在GDT中并没有定义这个描述符。

这个MSR的编号为
0xc0000081

23.2.3
IA32_LSTAR

这个MSR用于设定系统调用函数的地址,其编号为
0xc0000082

23.2.4
IA32_FMASK

这个MSR用于设定RFLAGS屏蔽掩码。具体来说,当执行
syscall
时,
rflags
会变成这样:
rflags &= ~IA32_FMASK
。在我们的操作系统中,这个MSR用于屏蔽IF位,屏蔽掩码为
0x200

这个MSR的编号为
0xc0000084

23.3
syscall
的执行细节

当执行
syscall
时,CPU会执行以下操作:

  • rcx = rip
  • r11 = rflags
  • cs = IA32_STAR[32:47]
  • rip = IA32_LSTAR
  • rflags &= ~IA32_FMASK

也就是说,
rcx

r11
会被
syscall
使用,它们不能用于传参。此外,
syscall
不会对
rsp
做任何处理,这是一个很重要的问题,我们将在下文中讨论。

23.4
sysret
的执行细节

当执行
sysret
时,CPU会执行以下操作:

  • rip = rcx
  • rflags = r11
  • 如果
    sysret
    没有64位前缀,则:
    cs = IA32_STAR[48:63]
    ;否则:
    cs = IA32_STAR[48:63] + 16

也就是说:

  1. 操作系统需要保护
    rcx

    r11
  2. sysret
    需要具有64位前缀

上述第1点将在下文中讨论;第2点在nasm中可使用
o64 sysret
实现。

23.5 系统调用的实现

请看本章代码
23/Syscall.h

第3行,声明了
syscallInit
函数。这个函数是用汇编语言实现的。

接下来,请看本章代码
23/Syscall.s

第15~18行,将
IA32_EFER
的第0位置1,打开快速系统调用功能。

第20~23行,设定
IA32_STAR
。在GDT中,3号描述符是0特权级代码段,4号描述符是0特权级数据段,这两个段描述符对应于
IA32_STAR
的第32~47位;5号描述符是3特权级数据段,6号描述符是3特权级代码段,没有兼容模式代码段,因此,这里应强行将4号描述符安装到
IA32_STAR
的第48~63位,使得5号和6号描述符处于正确的位置。

第25~29行,将系统调用函数
syscallHandle
的地址安装到
IA32_LSTAR

第31~34行,将屏蔽掩码
0x200
安装到
IA32_FMASK

至此,快速系统调用准备完毕。

syscallHandle
函数为系统调用函数。在32位操作系统中,系统调用由中断实现,中断发生时,CPU会自动切换到0特权级栈,由于0特权级栈是操作系统提供的,所以能够保证它的安全。那么,什么叫"安全的栈"?如果不切换栈,到底有什么问题?请看下例:

void test()
{
    char s[] = "666";
    __asm__ __volatile__("syscall");
}

将这段代码翻译成汇编语言,可以是:

test:
	mov dword [rsp - 4], '666'
	syscall
	ret

可以发现:这个函数的
rsp
是没有也不需要实际减去4的,但如果将这样的
rsp
提供给系统调用函数使用,就是错误的,因为系统调用函数不知道栈到底应该怎么用。这就是不安全栈带来的问题,因此,在系统调用时,切换到一个安全的栈是有必要的。

然而,
syscall
不会自动切换栈,我们需要手动完成这个操作。0特权级栈在TSS中,TSS的地址是
0xffff800000092000
,但想要使用这个地址,就必须先用一个寄存器周转64位立即数。用哪个寄存器呢?无关乎ABI,似乎用哪个都不完美。此时,我们之前设定的
IA32_GS_BASE
派上了用场,使用
gs
就可以直接操作TSS了。不仅如此,我们的操作系统的TSS是延长到128字节的,104字节以后的一小段内存可用于在换栈前备份当前的
rsp
。至此,换栈问题就完美解决了。

第44行,将
rsp
备份到
[TSS + 104]

第45行,切换到0特权级栈。

第47~48行,保护
rcx

r11
。现在的栈是安全的,可以放心使用。

第50~51行,调用
rax
指定的函数。

第53~54行,恢复
rcx

r11

第56行,恢复3特权级栈。

第58行,从快速系统调用返回。

第60~63行,定义了系统调用表。1号系统调用保留给后续章节使用。

接下来,请看本章代码
23/Start.s

_start
函数是3特权级任务的真正入口,其用于使任务在结束后自动退出。

23.6 编译与测试

本章代码
23/Makefile
增加了
Syscall.s

Start.s
的编译与链接命令。

本章代码
23/Kernel.c

23/Test.c
测试了0与2号系统调用。

Dify简介

Dify是一个开源的大语言模型(Large Language Model, LLM)应用开发平台。它融合了后端即服务(Backend as a Service, BaaS)和LLMOps的理念,旨在帮助开发者,甚至是非技术人员,能够快速搭建和部署生成式AI应用程序。

Dify的主要特点包括:

  1. 简化开发流程
    :通过提供一系列工具和服务来简化大语言模型应用的开发流程,使得即使是不具备深厚技术背景的个人也能构建复杂的AI应用。
  2. 支持多种模型
    :Dify支持多种大型语言模型,比如GPT系列模型等,这为用户提供了灵活的选择,可以根据具体需求选择最适合的模型。
  3. LLMOps支持
    :LLMOps是指针对大型语言模型的开发、部署、维护和优化的一整套实践和流程。Dify提供了LLMOps的支持,帮助用户更高效地管理和利用这些模型。
  4. 社区与资源
    :作为一个开源项目,Dify拥有活跃的技术社区,提供了丰富的学习资源和技术支持,便于用户学习和交流经验。
    总之,Dify的目标是降低创建生成式AI应用程序的技术门槛,使得更多人能够参与到这一领域的创新中来。无论是个人开发者还是企业团队,都可以借助Dify快速实现从想法到产品的转化。

开源地址:

开源地址:
https://github.com/langgenius/dify

Dify安装(本文Centos)

克隆 Dify 代码到本地
git clone https://github.com/langgenius/dify.git

然后
进入到源代码中的 docker 目录下,一键启动

cd dify/docker
cp .env.example .env
docker compose up -d

注意在下载镜像过程中可能会网络超时的情况:

作者多次失败,解决办法如下:

编辑sudo vim /etc/docker/daemon.json

{

"registry-mirrors": [
"
https://docker.1panel.live
",
"
https://docker.nju.edu.cn
",
"
https://docker.m.daocloud.io
",
"
https://dockerproxy.com
",
"
http://hub-mirror.c.163.com
",
"
https://docker.mirrors.ustc.edu.cn
",
"
https://registry.docker-cn.com
"
]
}

重启 Docker 服务

# 重启 Docker 服务
sudo systemctl daemon-reload
sudo systemctl restart docker

重新下载镜像和启动容器

docker compose up -d

Dify访问(本文Centos)

访问地址:
http://192.168.0.100

首次设置管理员账号和密码

主界面:



后续部分,我们将深入探讨Dify的实际应用案例,展示如何利用这一平台来构建和优化生成式AI应用。通过具体的项目实例,我们将演示从概念设计到实际部署的全过程,包括如何选择合适的语言模型、集成第三方服务以及调整模型参数以适应特定业务场景。此外,我们还将分享一些最佳实践,帮助读者理解如何高效地使用Dify来解决现实世界中的挑战。


自从互联网普及之后,用于视频直播的流媒体技术就发展起来。这几十年中,比较有影响的主要有MMS、RTSP、RTMP、HLS、SRT、RIST几种,分别介绍如下。

1、MMS协议

MMS全称Microsoft Multimedia Server,意思是微软多媒体服务器,它是微软公司在上世纪九十年代发布的多媒体服务器解决方案,可用于传输微软音视频格式的流媒体直播数据。
MMS协议的直播地址形如mms://***,可通过MMS传输的视频格式为WMV,音频格式为WMA,音视频数据封装之后的文件格式为ASF。
MMS协议内部又分为MMSU和MMST,其中MMSU表示MMS协议结合UDP数据传送。如果MMSU连接失败,服务器会尝试使用MMST,这个MMST表示MMS协议结合TCP数据传送。
因为MMS协议由微软公司提出,不兼容其他格式的音视频数据流,所以随着WMV/WMA标准的式微,MMS协议也逐渐无人问津了。

2、RTSP协议

RTSP全称Real Time Streaming Protocol,意思是实时流传输协议,它是网景公司和RealNetworks公司在上世纪九十年代联合提出的多媒体实时传输协议。
RTSP协议的直播地址形如rtsp://***,早期可通过RTSP传输的视频格式为RM,音频格式为RA,音视频数据封装之后的文件格式为RM或RMVB。后来RTSP协议增加支持MPEG的音视频标准,即支持传输视频格式H.264,音频格式AAC。
RTSP协议的安全版本是RTSPS,也就是给RTSP协议增加了TLS/SSL支持。RTSPS使用了TLS/SSL协议来加密和保护数据传输,以防止数据在传输过程中被窃听和篡改。
因为RTSP提出较早,对服务端的复杂度要求比较高,以至流媒体服务器SRS干脆放弃支持RTSP协议,直播录制软件OBS Studio也没支持该协议。在流媒体服务器中,EasyDarwin、MediaMTX、ZLMediaKit支持RTSP协议。手机直播软件则有RTMP Streamer支持RTSP协议。

3、RTMP协议

RTMP全称Real Time Messaging Protocol,意思是实时消息传输协议,它是Adobe公司在零零年代提出的流媒体数据传输协议。
RTMP协议的直播地址形如rtmp://***,可通过RTMP传输的视频格式为H.264,音频格式为MP3或者AAC,音视频数据封装之后的文件格式为FLV或F4V。
RTMP协议的安全版本是RTMPS,也就是给RTMP协议增加了TLS/SSL支持。RTMPS采用安全套接字层 (SSL) 和传输层安全性 (TLS) 两种加密协议,使数据传输更加安全。
RTMP提出时间较早,最后一次更新时间在2012年,以至于未能支持HEVC和AV1等后期的音视频编码标准。又因为FLV格式没落已久,以至HTML5规范干脆移除了Flash插件,导致如今浏览器都不支持rtmp链接,连FFmpeg也迟至6.1版才给rtmp协议支持hevc格式。
不过好在RTMP的稳定性高,服务端的实现相对容易,并且之前的移动互联网爆发迅速,新的流媒体协议未能及时推出,使得RTMP协议被大量应用于网络直播领域。
在流媒体服务器中,MediaMTX、ZLMediaKit、SRS都支持RTMP协议。在直播软件中,电脑端的OBS Studio支持RTMP协议,手机端的RTMP Streamer和SRT Streamer都支持RTMP协议。
通过RTMP协议实现直播功能的说明参见之前的文章《利用RTMP协议构建电脑与手机的直播Demo》和《使用RTMP Streamer开启APP直播推流》。

4、HLS协议

HLS全称HTTP Live Streaming,意思是基于HTTP的流媒体传输协议,它是苹果公司于2009年提出的一种由于传输音视频的协议交互方式。
HLS采用HTTP协议传输音视频数据,访问地址形如“http://***.m3u8”。HLS协议通过将音视频流切割成TS切片及生成m3u8的播放列表文件,并通知客户端通过HTTP协议下载播放列表文件,按照列表文件中的顺序下载切片文件并播放,从而实现边下载边播放,类似于实时在线播放的效果。
由于HLS在传输层只采用HTTP协议,因此它具备HTTP协议的网络优势,比如很方便透过防火墙或者代理服务器,可简单的实现媒体流的负载均衡。因为HLS协议把视频流分片传输,使得在直播时延时较大,所以HLS更多用于视频点播领域。
关于HLS协议的更多说明参见之前的文章《分析SRS对HLS协议里TS包的插帧操作》和《解析H.264码流中的SPS帧和PPS帧》。

5、SRT协议

SRT全称Secure Reliable Transport,意思是安全可靠传输协议,它由由Haivision 和 Wowza共同创建的SRT联盟提出。
SRT协议协议的直播地址形如srt://***,它引入了AES加密算法,无需像RTSP和RTMP那样引入专门的SSL证书。作为较新的流媒体协议,SRT支持更多的音视频封装格式。只是该协议的支持库libsrt在2017年才开源,因此未能在移动互联网时代大量铺开,目前主要应用于大型电视直播领域。
FFmpeg从4.0开始支持集成第三方的libsrt库。在流媒体服务器中,MediaMTX、ZLMediaKit、SRS都支持SRT协议。在直播软件中,电脑端的OBS Studio从在25.0开始支持SRT协议,手机端的和SRT Streamer支持SRT协议,而RTMP Streamer不支持SRT协议,只有其升级版才支持SRT协议。
通过SRT协议实现直播功能的说明参见之前的文章《利用SRT协议构建手机APP的直播Demo》和《使用SRT Streamer开启APP直播推流》。

6、RIST协议

RTST全称Reliable Internet Stream Transport,意思是可信赖的互联网流媒体协议,它由2017年成立的RIST工作组提出。
RIST是一个在传输层使用UDP协议,并在应用层提供可靠性和流控制功能的流传输协议。它并不是一个纯粹的应用层协议,而是在传输层和应用层之间操作的协议。RIST和SRT具有相同的加密级别,都支持大容量流媒体和前向纠错功能。
RIST协议的制定时间比SRT还晚,虽然晚制定会多考虑新功能,比如RIST支持点到多点广播,而SRT不支持;但是晚制定拖累了各开源软件对RIST的支持力度,比如OBS Studio早在25.0开始支持SRT,迟至27.0才开始支持RIST,另一个直播录制软件RootEncoder已支持SRT尚未支持RIST,流媒体服务器MediaMTX已支持SRT尚未支持RIST。
FFmpeg从4.4开始支持集成第三方的librist库。在流媒体服务器中,MediaMTX、ZLMediaKit、SRS都不支持RIST协议。在直播软件中,电脑端的OBS Studio从在27.0开始支持SRT协议,手机端尚未有开源软件支持RIST协议。

总的来说,目前国内占据主要市场份额的直播协议仍是RTMP,不过拥有更好性能的SRT协议正在逐步迎头赶上,比如腾讯视频云、京东视频云等等就引入了SRT协议。有关直播系统的搭建说明参见之前的文章《从0开始搭建直播系统的开源软件架构》。

更多详细的FFmpeg开发知识参见
《FFmpeg开发实战:从零基础到短视频上线》
一书。