【DAPM杂谈之二】实践是检验真理的标准

本文主要分析DAPM的设计与实现

内核的版本是:linux-5.15.164,下载链接:Linux内核下载

主要讲解有关于DAPM相关的知识,会给出一些例程并分析内核如何去实现的

/*****************************************************************************************************************/

声明: 本博客内容均由芯心智库-CSDN博客原创,转载or引用请注明出处,谢谢!

创作不易,如果文章对你有帮助,麻烦点赞 收藏支持~感谢

/*****************************************************************************************************************/

目录

零、本文主要内容

一、虚拟寄存器的定义

1.1 实现寄存器的读写

2.1 基于kcontrol实现打印所有的寄存器

二、widget实现和route的定义

三、实验验证

3.1 验证环境

3.2 源码编译方法和加载ko

3.3 实验观察一下现象

四、总结


零、本文主要内容

前面讲了一下内核的官方文档,但是光是看文档并不能对DAPM有深入的了解,很多时候我们对原理很懂,但是在实践中总是遇到各种各样的问题.....本文就从应用的角度去看DAPM是如何调用的!

为什么要先从应用的角度去分析,而不从设计者的角度去分析内核中的调用流程呢?因为我觉得实践才是检验真理的唯一标准!先学会用好,再去深究为什么它可以让我用得这么丝滑。

要实践一下DAPM,首先需要一份具备声卡的代码,声卡的注册也是不小的一块内容,我要继续讲一下声卡要怎么写的话,怕是有点冗长了,你可以参考我的文章:

【ASOC全解析(一)】ASOC架构简介和欲解决的问题

我基于我之前的文章(有声卡,但是没有kcontrol、Widgets等等)进行二次开发演示DAPM的功能。

本文所有提交的代码可以见平台:https://github.com/xiaoWEN7/ASOC_DAPM/tree/master

一、虚拟寄存器的定义

1.1 实现寄存器的读写

因为我们的code并不是真实存在的codec,因此会有一个问题,就是我们的各种widget需要设定寄存器的时候,无法去设定,那么为了解决这个问题,我们需要原本的snd_soc_component_driver结构体下面定义一下:

.read = virtual_reg_read,
.write = virtual_reg_write,

这部分一般的codec驱动程序都是不定义的(通常使用regmap),但是为了更加容易去控制我们手动去指定寄存器的读写函数,参考代码调用:

/sound/soc/soc-component.c
static unsigned int soc_component_read_no_lock(
	struct snd_soc_component *component,
	unsigned int reg)
{
	int ret;
	unsigned int val = 0;

	if (component->regmap)
		ret = regmap_read(component->regmap, reg, &val);
	else if (component->driver->read) {
		ret = 0;
		val = component->driver->read(component, reg);
	}
	else
		ret = -EIO;

	if (ret < 0)
		return soc_component_ret(component, ret);

	return val;
}
static unsigned int soc_component_read_no_lock(
	struct snd_soc_component *component,
	unsigned int reg)
{
	int ret;
	unsigned int val = 0;

	if (component->regmap)
		ret = regmap_read(component->regmap, reg, &val);
	else if (component->driver->read) {
		ret = 0;
		val = component->driver->read(component, reg);
	}
	else
		ret = -EIO;

	if (ret < 0)
		return soc_component_ret(component, ret);

	return val;
}

我们定义component->driver->read的读写函数和虚拟一个寄存器reg_data:

/* 寄存器列表 */
enum reg {
	VCODEC_ADCL_REG,
	VCODEC_MIC1_REG,
	VCODEC_Output_mixer_REG,
	VCODEC_CTRL_NUM   //寄存器总数
};

static u32 reg_data[VCODEC_CTRL_NUM];  //虚拟的寄存器

static unsigned int virtual_reg_read(struct snd_soc_component *component, unsigned int reg)
{
	pr_info("virtual_reg_read : reg = %d reg_data[reg]=%d \n",reg,reg_data[reg]);
    return reg_data[reg];
}

static int virtual_reg_write(struct snd_soc_component *component, unsigned int reg, unsigned int value)
{
	pr_info("virtual_reg_write : reg = %d , value = %d\n",reg ,value);
    reg_data[reg] = value;
    return 0;
}

static struct snd_soc_component_driver soc_my_codec_drv = {
    .probe = my_codec_probe,
    .remove = my_codec_remove,
    .read = virtual_reg_read,
    .write = virtual_reg_write,
};

2.1 基于kcontrol实现打印所有的寄存器

上面虽然实现了寄存器的读写,要一次一次的核对Kernel log,这其实并不方便,我在这里实现一个kcontrol用于显示所有的寄存器数值:

先在snd_soc_component_driver我们使用controls:

.controls		    = my_controls,
.num_controls		= ARRAY_SIZE(my_controls),

然后实现my_controls:

static const char *const reg_date[] = { "VCODEC_ADCL_REG", "VCODEC_MIC1_REG", "VCODEC_Output_mixer_REG"};

int current_reg = 0;

// 定义一个枚举描述结构
static const struct soc_enum audio_reg_dump =
    SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(reg_date),
reg_date);

// 获取当前音频输出模式的函数
static int get_audio_reg(struct snd_kcontrol *kcontrol,
                                 struct snd_ctl_elem_value *ucontrol)
{
	int i;
	ucontrol->value.integer.value[0] = current_reg;
	pr_info("************audio_reg Dump************\n");
	for(i=0;i<VCODEC_CTRL_NUM;i++){
		pr_info("reg_data[%d] = %d \n",i,reg_data[i]);
	}
	pr_info("************audio_reg Dump************\n");
    return 0;
}

// 设置音频输出模式的函数
static int set_audio_reg(struct snd_kcontrol *kcontrol,
                                 struct snd_ctl_elem_value *ucontrol)
{
    current_reg = ucontrol->value.integer.value[0];
	pr_info("current_reg :  %d \n",current_reg);
    return 0;
}


static const struct snd_kcontrol_new my_controls[] = {
    SOC_ENUM_EXT("Get Audio reg", audio_reg_dump,
                 get_audio_reg, set_audio_reg),
};

这边我还顺便实现了基本的读写功能,这个是为什么呢?主要是展示后面的DAPM的特征用的。这一点我们文章往后会分析一下。

二、widget实现和route的定义

widget和route一般是需要同时出现了:

widget:抽象硬件成为一个一个小部件

route:描述这些部件之间的连接关系

所以要同时对这两个进行定义:

    .dapm_widgets		= vcodec_dapm_widgets,
	.num_dapm_widgets	= ARRAY_SIZE(vcodec_dapm_widgets),
	.dapm_routes		= vcodec_dapm_routes,
	.num_dapm_routes	= ARRAY_SIZE(vcodec_dapm_routes),

然后就是实现了,我这边假设一种硬件通路,可以让mic1这个硬件连接到ADC采集器并且ADC采集的数据直接输出到上层,硬件框架大概如下:

那么这个路由也是很明确的,应该这么写:

/* 定义音频路径(路由) */
static const struct snd_soc_dapm_route vcodec_dapm_routes[] = {
    /* MIC 输入路径 */
    {"ADCL Input", "MIC1 Boost Switch", "MIC1"},
    {"ADCL", NULL, "ADCL Input"},
    {"Output Switch", "Output_mixer", "ADCL"},
    /* 如果有其他输入输出路径,在此添加 */
    {"ADC Output", NULL, "Output Switch"},
};

MIC1可以通过一个开关去选择它到底要从ADCL Input采集,还是ADCR Input采集(我上面的路由没有抽象出ADCR的通路)。

然后如果走ADCL Input进行输入,那么会经过ADCL的控制器,这ADC控制器有很多设置,其中bit 2是使能开关,当然除了使能之外,还需要设置ADC的阻抗、通道选择、预分频器设置等等一堆设置,这些设置往往是真正需要工作的时候才开始设置,不然耗电还是挺高的。

最后经过ADCL的采集后,产生了数字信号,那么数字信号还可以被各种增益、削频等出来,也可以直接输出到上层。如果走Output Switch代表了ADC直接采集的数字信号会被输出到上层使用,如果走other swith就是其他的处理了。

综合上面,代码如下实现:

/* 定义事件处理函数 */
static int vcodec_capture_event(struct snd_soc_dapm_widget *w,
                                struct snd_kcontrol *kcontrol, int event)
{
    switch (event) {
    case SND_SOC_DAPM_WILL_PMU:
        pr_info("Vcodec capture event: POST_PMU\n");
        break;
    case SND_SOC_DAPM_WILL_PMD:
        pr_info("Vcodec capture event: POST_PMD\n");
        break;
    default:
        break;
    }
    return 0;
}

/* 定义控制项 */
static const struct snd_kcontrol_new mic1_input_mixer[] = {
    SOC_DAPM_SINGLE("MIC1 Boost Switch", VCODEC_MIC1_REG, 0, 1, 0),
    /* 如果有其他控件可以在这里继续添加 */
};

static const struct snd_kcontrol_new mic1_Output_mixer[] = {
    SOC_DAPM_SINGLE("Output_mixer", VCODEC_Output_mixer_REG, 0, 1, 0),
    /* 如果有其他控件可以在这里继续添加 */
};

/* 定义 DAPM Widget */
static const struct snd_soc_dapm_widget vcodec_dapm_widgets[] = {
    SND_SOC_DAPM_INPUT("MIC1"),
    SND_SOC_DAPM_MIXER("ADCL Input", SND_SOC_NOPM, 0, 0,
        mic1_input_mixer, ARRAY_SIZE(mic1_input_mixer)),
	/* 2表示偏移量,也就是设置这个寄存器的数值为 1<<2 = 4 */
    SND_SOC_DAPM_AIF_OUT_E("ADCL", "Capture", 0, VCODEC_ADCL_REG,
        2, 0, vcodec_capture_event,
        SND_SOC_DAPM_WILL_PMU | SND_SOC_DAPM_WILL_PMD),
	SND_SOC_DAPM_MIXER("Output Switch", SND_SOC_NOPM, 0, 0,
        mic1_Output_mixer, ARRAY_SIZE(mic1_Output_mixer)),
    SND_SOC_DAPM_OUTPUT("ADC Output"),  /* 输出口 */
};

/* 定义音频路径(路由) */
static const struct snd_soc_dapm_route vcodec_dapm_routes[] = {
    /* MIC 输入路径 */
    {"ADCL Input", "MIC1 Boost Switch", "MIC1"},
    {"ADCL", NULL, "ADCL Input"},
	{"Output Switch", "Output_mixer", "ADCL"},
    /* 如果有其他输入输出路径,在此添加 */
    {"ADC Output", NULL, "Output Switch"},
};

细心的你会发现,在DAPM中也定义了一些kcontrol,如下:

SOC_DAPM_SINGLE("MIC1 Boost Switch", VCODEC_MIC1_REG, 0, 1, 0),
SOC_DAPM_SINGLE("Output_mixer", VCODEC_Output_mixer_REG, 0, 1, 0),

DAPM的kcontrol是带有DAPM关键字的,而普通的kcontrol是不带DAPM关键字的,那么我们可以比较容易的推导出来,这个带了DAPM关键字的kcontrol要比普通的kcontrol要多做了一些事情,来实现DAPM的特征,这个具体带了什么功能,后续文章会继续讲解。

三、实验验证

3.1 验证环境

为了方便去验证DAPM,我这边直接在云服务器上面验证,我的云服务器的版本如下(你可以使用其他的云服务器,但是Linux Kernel需要是5.15左右):

# uname -a
Linux 5.15.0-113-generic #123-Ubuntu SMP Mon Jun 10 08:16:17 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.4 LTS
Release:        22.04
Codename:       jammy

我搞的更多的是安卓方面的开发,这边搭建一个tinyasla的环境(如果你熟悉原生的Linux环境,比如aplay、amixer,也可以自行搭建自己熟悉的):

git clone https://github.com/tinyalsa/tinyalsa
然后按照官方文档搭建:
make
sudo make install
sudo ldconfig

在云服务器上面,一般是关闭了音频模块的,手动打开一下,如果你使用实际的Ubuntu设备,是带有真实声卡的,可以不用下面的操作:

modprobe snd_soc_core

3.2 源码编译方法和加载ko

源码的编译,直接make就会生成ko:

make

生成结果:

# ls
codec.c  machine.c  Makefile  platform.c
# make
make -C /usr/src/linux-headers-5.15.0-113-generic M=`pwd` modules
make[1]: Entering directory '/usr/src/linux-headers-5.15.0-113-generic'
  CC [M]  /root/test_Asoc/new_ASOC/platform.o
  CC [M]  /root/test_Asoc/new_ASOC/codec.o
  CC [M]  /root/test_Asoc/new_ASOC/machine.o
  MODPOST /root/test_Asoc/new_ASOC/Module.symvers
  CC [M]  /root/test_Asoc/new_ASOC/codec.mod.o
  LD [M]  /root/test_Asoc/new_ASOC/codec.ko
  BTF [M] /root/test_Asoc/new_ASOC/codec.ko
Skipping BTF generation for /root/test_Asoc/new_ASOC/codec.ko due to unavailability of vmlinux
  CC [M]  /root/test_Asoc/new_ASOC/machine.mod.o
  LD [M]  /root/test_Asoc/new_ASOC/machine.ko
  BTF [M] /root/test_Asoc/new_ASOC/machine.ko
Skipping BTF generation for /root/test_Asoc/new_ASOC/machine.ko due to unavailability of vmlinux
  CC [M]  /root/test_Asoc/new_ASOC/platform.mod.o
  LD [M]  /root/test_Asoc/new_ASOC/platform.ko
  BTF [M] /root/test_Asoc/new_ASOC/platform.ko
Skipping BTF generation for /root/test_Asoc/new_ASOC/platform.ko due to unavailability of vmlinux
make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-113-generic'
# ls
codec.c        codec.mod.c       codec.o       machine.ko       .machine.mod.cmd    .machine.o.cmd      Module.symvers       .platform.ko.cmd   platform.mod.o
codec.ko       .codec.mod.cmd    .codec.o.cmd  .machine.ko.cmd  machine.mod.o       Makefile            .Module.symvers.cmd  platform.mod       .platform.mod.o.cmd
.codec.ko.cmd  codec.mod.o       .git          machine.mod      .machine.mod.o.cmd  modules.order       platform.c           platform.mod.c     platform.o
codec.mod      .codec.mod.o.cmd  machine.c     machine.mod.c    machine.o           .modules.order.cmd  platform.ko          .platform.mod.cmd  .platform.o.cmd
#

加载ko(注意machine.ko一定是最后加载的,因为是在machine.ko会注册声卡,dai link依赖前面两个ko):

# insmod codec.ko
# insmod platform.ko
# insmod machine.ko
# cat /proc/asound/pcm
00-00: MY-CODEC my_codec.0-0 :  : playback 1 : capture 1
#

通过cat /proc/asound/pcm我们可以看到声卡已经加载成功了。

3.3 实验观察一下现象

根据我们的源码,我们会注册三个控件,一个是普通的kcontrol,两个是DAMP类的kcontrol,可以输入命令查看一下:

# tinymix contents
Number of controls: 3
ctl     type    num     name                                            device  value
0       ENUM    1       Get Audio reg                                   0> VCODEC_ADCL_REG, VCODEC_MIC1_REG, VCODEC_Output_mixer_REG,
1       BOOL    1       ADCL Input MIC1 Boost Switch                    0Off
2       BOOL    1       Output Switch Output_mixer                      0Off
#
  • 我们先试一下普通的kcontrol
# tinymix -D 0 get 0
> VCODEC_ADCL_REG, VCODEC_MIC1_REG, VCODEC_Output_mixer_REG,
# tinymix -D 0 set 0 1
# tinymix -D 0 get 0
VCODEC_ADCL_REG, > VCODEC_MIC1_REG, VCODEC_Output_mixer_REG,
#

看一下Kernel log:

[22086.167666] ************audio_reg Dump************
[22086.167670] reg_data[0] = 0
[22086.167672] reg_data[1] = 0
[22086.167672] reg_data[2] = 0
[22086.167673] ************audio_reg Dump************
[22097.375953] ************audio_reg Dump************
[22097.375956] reg_data[0] = 0
[22097.375958] reg_data[1] = 0
[22097.375958] reg_data[2] = 0
[22097.375959] ************audio_reg Dump************
[22097.375962] current_reg :  1
[22105.824032] ************audio_reg Dump************
[22105.824035] reg_data[0] = 0
[22105.824037] reg_data[1] = 0
[22105.824038] reg_data[2] = 0
[22105.824039] ************audio_reg Dump************
可以看到,当 设定或者获取这个kcontrol的数值的时候,直接就打印出相关的读写函数的内容了,这个就是我们预先设定的内容。
  • 看一下DAPM的情况:
先设定一下带有DAPM关键字的kcontrol,再通过获取普通kcontrol的数值以打印出寄存器的数值:
# tinymix -D 0 set "ADCL Input MIC1 Boost Switch" 1
# tinymix -D 0 get 0
VCODEC_ADCL_REG, > VCODEC_MIC1_REG, VCODEC_Output_mixer_REG,
#

看一下Kernel log:

[22455.184560] virtual_reg_read : reg = 1 reg_data[reg]=0
[22455.184566] virtual_reg_read : reg = 1 reg_data[reg]=0
[22455.184572] virtual_reg_read : reg = 1 reg_data[reg]=0
[22455.184573] virtual_reg_write : reg = 1 , value = 1
[22457.360196] ************audio_reg Dump************
[22457.360199] reg_data[0] = 0
[22457.360201] reg_data[1] = 1
[22457.360202] reg_data[2] = 0
[22457.360203] ************audio_reg Dump************

你会发现直接就设定成功了。

但是细心的你想一下,下面这个寄存器怎么设定呢?似乎也没有引用出相关的kcontrol给我们进行设定:

这个就是DAPM的神奇之处了,即只有通路完整了,相关的寄存器才会被设置,在我的例程里面,这个相关的寄存器就是上图的寄存器:VCODEC_ADCL_REG。

我们只需要在设定一下output switch,那么通路就完整:

# tinymix -D 0 set "Output Switch Output_mixer" 1
# tinymix -D 0 get 0
VCODEC_ADCL_REG, > VCODEC_MIC1_REG, VCODEC_Output_mixer_REG,
#

看一下Kernel log的寄存器打印打印情况和调用情况:

[22799.483102] virtual_reg_read : reg = 2 reg_data[reg]=0
[22799.483109] virtual_reg_read : reg = 2 reg_data[reg]=0
[22799.483144] Vcodec capture event: POST_PMU
[22799.483146] virtual_reg_read : reg = 2 reg_data[reg]=0
[22799.483147] virtual_reg_write : reg = 2 , value = 1
[22799.483148] virtual_reg_read : reg = 0 reg_data[reg]=0
[22799.483149] virtual_reg_write : reg = 0 , value = 4
[22801.277420] ************audio_reg Dump************
[22801.277424] reg_data[0] = 4
[22801.277425] reg_data[1] = 1
[22801.277426] reg_data[2] = 1
[22801.277427] ************audio_reg Dump************
可以观察到,VCODEC_ADCL_REG寄存器被自动设定了,即reg_data[0] = 4!!
同时还打印了“Vcodec capture event: POST_PMU”,说明相关事件被调用了。
  • 如果此时把完整的通路变成不完整,那么对应的SND_SOC_DAPM_WILL_PMD事件和寄存器就会被自动调用与设定:
# tinymix -D 0 set "Output Switch Output_mixer" 0
# tinymix -D 0 get 0
VCODEC_ADCL_REG, > VCODEC_MIC1_REG, VCODEC_Output_mixer_REG,
#

Kernel log为:

[23118.456984] virtual_reg_read : reg = 2 reg_data[reg]=1
[23118.456990] virtual_reg_read : reg = 2 reg_data[reg]=1
[23118.457028] Vcodec capture event: POST_PMD
[23118.457030] virtual_reg_read : reg = 0 reg_data[reg]=4
[23118.457031] virtual_reg_write : reg = 0 , value = 0
[23118.457032] virtual_reg_read : reg = 2 reg_data[reg]=1
[23118.457033] virtual_reg_write : reg = 2 , value = 0
[23120.979284] ************audio_reg Dump************
[23120.979288] reg_data[0] = 0
[23120.979289] reg_data[1] = 1
[23120.979290] reg_data[2] = 0
[23120.979291] ************audio_reg Dump************

可以看到SND_SOC_DAPM_WILL_PMD事件被触发并且打印“Vcodec capture event: POST_PMD”,且不仅kcontrol对应的寄存器reg_data[2]被设定了,reg_data[0]也被自动设定了!!

在实际的代码中,这些事件的回调就可以用于去设定相关的寄存器了,写代码的工程师可以根据自己IC的特征,来对各种事件进行响应,比如上电需要设定哪些寄存器,下电需要设定哪些寄存器。

四、总结

通过上面这一堆假设的寄存器和通路,我们可以知道DAPM对比与kcontrol的一个特征:即通路完整后,会自动进行相关寄存器的上电与调用相关的事件去处理。

在实际的应用中,往往我们用这个特征进行硬件电路的抽象,将一些控制器的寄存器封装到DAPM中,让DAPM帮我们管理器件的上电和下电。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/952741.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Qt】事件、qt文件

目录 Qt事件 QEvent QMouseEvent QWheelEvent QKeyEvent QTimerEvent Qt文件 QFile QFileInfo Qt事件 在Qt中用一个对象表示一个事件&#xff0c;这些事件对象都继承自抽象类QEvent。事件和信号的目的是一样的&#xff0c;都是为了响应用户的操作。有两种产生事件的方…

线形回归与小批量梯度下降实例

1、准备数据集 import numpy as np import matplotlib.pyplot as pltfrom torch.utils.data import DataLoader from torch.utils.data import TensorDataset######################################################################### #################准备若干个随机的x和…

消息队列使用中防止消息丢失的实战指南

消息队列使用中防止消息丢失的实战指南 在分布式系统架构里&#xff0c;消息队列起着举足轻重的作用&#xff0c;它异步解耦各个业务模块&#xff0c;提升系统整体的吞吐量与响应速度。但消息丢失问题&#xff0c;犹如一颗不定时炸弹&#xff0c;随时可能破坏系统的数据一致性…

【优选算法篇】:深入浅出位运算--性能优化的利器

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;优选算法篇–CSDN博客 文章目录 一.位运算一.位运算概述二.常见的位运算操作符三.常见的位运…

创业AI Agents系统深度解析

Agents 近日&#xff0c;AI领域的知名公司Anthropic发布了一份题为《构建高效的智能代理》的报告。该报告基于Anthropic过去一年与多个团队合作构建大语言模型&#xff08;LLM&#xff09;智能代理系统的经验&#xff0c;为开发者及对该领域感兴趣的人士提供了宝贵的洞见。本文…

【Spring Boot】Spring 事务探秘:核心机制与应用场景解析

前言 &#x1f31f;&#x1f31f;本期讲解关于spring 事务介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么废话不多说直…

centos7.6 安装nginx 1.21.3与配置ssl

1 安装依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel2 下载Nginx wget http://nginx.org/download/nginx-1.21.3.tar.gz3 安装目录 mkdir -p /data/apps/nginx4 安装 4.1 创建用户 创建用户nginx使用的nginx用户。 #添加www组 # groupa…

夯实前端基础之HTML篇

知识点概览 HTML部分 1. DOM和BOM有什么区别&#xff1f; DOM&#xff08;Document Object Model&#xff09; 当网页被加载时&#xff0c;浏览器会创建页面的对象文档模型&#xff0c;HTML DOM 模型被结构化为对象树 用途&#xff1a; 主要用于网页内容的动态修改和交互&…

Elasticsearch:向量数据库基础设施类别的兴衰

过去几年&#xff0c;我一直在观察嵌入技术如何从大型科技公司的 “秘密武器” 转变为日常开发人员工具。接下来发生的事情 —— 向量数据库淘金热、RAG 炒作周期以及最终的修正 —— 教会了我们关于新技术如何在更广泛的生态系统中找到一席之地的宝贵经验。 更多有关向量搜索…

【华为云开发者学堂】基于华为云 CodeArts CCE 开发微服务电商平台

实验目的 通过完成本实验&#xff0c;在 CodeArts 平台完成基于微服务的应用开发&#xff0c;构建和部署。 ● 理解微服务应用架构和微服务模块组件 ● 掌握 CCE 平台创建基于公共镜像的应用的操作 ● 掌握 CodeArts 平台编译构建微服务应用的操作 ● 掌握 CodeArts 平台部署微…

计科高可用服务器架构实训(防火墙、双机热备,VRRP、MSTP、DHCP、OSPF)

一、项目介绍 需求分析&#xff1a; &#xff08;1&#xff09;总部和分部要求网络拓扑简单&#xff0c;方便维护&#xff0c;网络有扩展和冗余性&#xff1b; &#xff08;2&#xff09;总部分财务部&#xff0c;人事部&#xff0c;工程部&#xff0c;技术部&#xff0c;提供…

【C++入门】详解合集

目录 &#x1f495;1.C中main函数内部———变量的访问顺序 &#x1f495;2.命名空间域 namespace &#x1f495;3.命名空间域&#xff08;代码示例&#xff09;&#xff08;不要跳&#xff09; &#x1f495;4.多个命名空间域的内部重名 &#x1f495;5.命名空间域的展开 …

预编译SQL

预编译SQL 预编译SQL是指在数据库应用程序中&#xff0c;SQL语句在执行之前已经通过某种机制&#xff08;如预编译器&#xff09;进行了解析、优化和准备&#xff0c;使得实际执行时可以直接使用优化后的执行计划&#xff0c;而不需要每次都重新解析和编译。这么说可能有一些抽…

qemu搭建虚拟的aarch64环境开发ebpf

一、背景 需求在嵌入式环境下进行交叉编译&#xff0c;学习ebpf相关技术&#xff0c;所以想搭建一个不依赖硬件环境的学习环境。 本文使用的环境版本&#xff1a; 宿主机&#xff1a; Ubuntu24.02 libbpf-bootstrap源码&#xff1a; https://github.com/libbpf/libbpf-boots…

深度学习从入门到实战——卷积神经网络原理解析及其应用

卷积神经网络CNN 卷积神经网络前言卷积神经网络卷积的填充方式卷积原理展示卷积计算量公式卷积核输出的大小计算感受野池化自适应均值化空洞卷积经典卷积神经网络参考 卷积神经网络 前言 为什么要使用卷积神经网络呢&#xff1f; 首先传统的MLP的有什么问题呢&#xff1f; - …

2015年西部数学奥林匹克几何试题

2015/G1 圆 ω 1 \omega_1 ω1​ 与圆 ω 2 \omega_2 ω2​ 内切于点 T T T. M M M, N N N 是圆 ω 1 \omega_1 ω1​ 上不同于 T T T 的不同两点. 圆 ω 2 \omega_2 ω2​ 的两条弦 A B AB AB, C D CD CD 分别过 M M M, N N N. 证明: 若线段 A C AC AC, B D BD …

《Spring Framework实战》14:4.1.4.5.自动装配合作者

欢迎观看《Spring Framework实战》视频教程 自动装配合作者 Spring容器可以自动连接协作bean之间的关系。您可以通过检查ApplicationContext的内容&#xff0c;让Spring自动为您的bean解析协作者&#xff08;其他bean&#xff09;。自动装配具有以下优点&#xff1a; 自动装配…

JVM之垃圾回收器概述(续)的详细解析

ParNew(并行) Par 是 Parallel 并行的缩写&#xff0c;New 是只能处理的是新生代 并行垃圾收集器在串行垃圾收集器的基础之上做了改进&#xff0c;采用复制算法&#xff0c;将单线程改为了多线程进行垃圾回收&#xff0c;可以缩短垃圾回收的时间 对于其他的行为&#xff08;…

有一台服务器可以做哪些很酷的事情

有一台服务器可以做哪些很酷的事情 今天我也来简单分享一下&#xff0c;这几年来&#xff0c;我用云服务器做了哪些有趣的事情。 服务器推荐 1. 个人博客 拥有个人服务器&#xff0c;你可以完全掌控自己的网站或博客。 与使用第三方托管平台相比&#xff0c;你能自由选择网站…

灌区闸门自动化控制系统-精准渠道量测水-灌区现代化建设

项目背景 本项目聚焦于黑龙江某一灌区的现代化改造工程&#xff0c;该灌区覆盖广阔&#xff0c;灌溉面积高达7.5万亩&#xff0c;地域上跨越6个乡镇及涵盖17个村庄。项目核心在于通过全面的信息化建设&#xff0c;强力推动节水灌溉措施的实施&#xff0c;旨在显著提升农业用水的…