『Linux从入门到精通』第 ㉒ 期 - 动静态库

news2024/11/18 15:26:20

在这里插入图片描述

文章目录

  • 💐专栏导读
  • 💐文章导读
  • 🐧什么是库?
  • 🐧为什么要有库?
  • 🐧写一个自己的库
    • 🐦方法一
    • 🐦方法二 静态库
    • 🐦标准化
    • 🐦方法三 动态库
    • 🐦配置动态库
      • 🐱环境变量
      • 🐱软链接
      • 🐱配置文件
  • 🐧静态库与动态库的区别
  • 🐧动态库的运作原理
      • 🐔为什么进程可以在运行时加载动态库?
      • 🐔为什么多个进程可以共享一个动态库

💐专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 Linux从入门到精通,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法

💐文章导读

本章我们将深入学习Linux中动静态库的使用及其原理。

在这里插入图片描述

🐧什么是库?

在学习生涯中,我们总是能谈到库,例如C语言标准库、C++标准库。那么到底什么是库呢?

在计算机科学中,术语“库”通常指的是库文件(Library),它是一组预编译的、可重用的代码和资源的集合,用于支持软件开发。库的目的是为开发人员提供一组常用的功能,以便在应用程序中进行调用,从而避免重复编写相同的代码。

库可以分为两大类:

  • 静态库(Static Library): 静态库在编译时被链接到应用程序中,形成一个独立的可执行文件。在程序运行时,静态库的代码被完全复制到应用程序中,因此应用程序不再依赖于原始的库文件。静态库的文件扩展名通常是.a(在Unix/Linux系统中)或.lib(在Windows系统中)。

  • 动态库(Dynamic Library): 动态库在运行时加载到内存中,多个应用程序可以共享同一个动态库的实例。这可以减小应用程序的大小,因为动态库的代码只需要存在一份,而且可以在运行时更新。动态库的文件扩展名通常是.so(在Unix/Linux系统中)或.dll(在Windows系统中)。

在编程中,开发人员通过包含库的头文件、链接库文件,以及在代码中调用库提供的函数或方法,可以轻松地利用库的功能。

在我们的Linxu机器上,系统已经为我们预装了C/C++的头文件和库文件。头文件提供方法说明,库文件提供方法的实现。头文件与库文件是有对应关系的,需要组合在一起使用。

一个可执行程序生成需要经历四个阶段:预处理、编译、汇编、链接。头文件在预处理阶段被引入,库文件在链接阶段被引入。

C/C++库文件

$ ls /usr/lib64/libc*
/usr/lib64/libc-2.17.so         /usr/lib64/libc_nonshared.a        /usr/lib64/libcroco-0.6.so.3.0.1    /usr/lib64/libc.so
/usr/lib64/libcap-ng.so.0       /usr/lib64/libcollection.so.2      /usr/lib64/libcrypt-2.17.so         /usr/lib64/libc.so.6
/usr/lib64/libcap-ng.so.0.0.0   /usr/lib64/libcollection.so.2.1.1  /usr/lib64/libcrypto.so.10          /usr/lib64/libcupscgi.so.1
/usr/lib64/libcap.so.2          /usr/lib64/libcom_err.so.2         /usr/lib64/libcrypto.so.1.0.2k      /usr/lib64/libcupsimage.so.2
/usr/lib64/libcap.so.2.22       /usr/lib64/libcom_err.so.2.1       /usr/lib64/libcryptsetup.so.12      /usr/lib64/libcupsmime.so.1
/usr/lib64/libcgroup.so.1       /usr/lib64/libcpupower.so.0        /usr/lib64/libcryptsetup.so.12.3.0  /usr/lib64/libcupsppdc.so.1
/usr/lib64/libcgroup.so.1.0.41  /usr/lib64/libcpupower.so.0.0.0    /usr/lib64/libcryptsetup.so.4       /usr/lib64/libcups.so.2
/usr/lib64/libcidn-2.17.so      /usr/lib64/libcrack.so.2           /usr/lib64/libcryptsetup.so.4.7.0   /usr/lib64/libcurl.so.4
/usr/lib64/libcidn.so           /usr/lib64/libcrack.so.2.9.0       /usr/lib64/libcrypt.so              /usr/lib64/libcurl.so.4.3.0
/usr/lib64/libcidn.so.1         /usr/lib64/libcroco-0.6.so.3       /usr/lib64/libcrypt.so.1

C/C++头文件

$ ls /usr/include/stdio.h
/usr/include/stdio.h
$ ls /usr/include/c++/4.8.5/iostream 
/usr/include/c++/4.8.5/iostream

🐧为什么要有库?

引入库的概念有很多重要的原因,它们有助于提高软件开发的效率、可维护性和可扩展性。以下是一些主要的原因:

  1. 代码重用: 库提供了一组通用的功能或工具,可以在多个项目中被重复使用。这样可以避免开发人员重复编写相同的代码,提高了开发效率。

  2. 模块化开发: 库使软件能够以模块化的方式构建。通过将不同的功能分解成独立的库,开发人员可以更容易地理解和维护代码。模块化开发还使得团队能够并行工作,每个成员专注于特定的任务或功能。

  3. 抽象和封装: 库提供了对底层实现的抽象,使开发人员可以专注于高层次的问题而不必关心底层的细节。这种抽象和封装的概念有助于隐藏复杂性,提高代码的可读性和可维护性。

  4. 提高可靠性: 库经过充分测试和验证,可以提供高质量的代码。开发人员使用库时,可以信任这些已经验证过的功能,减少了潜在的错误和漏洞。

  5. 快速开发: 使用库可以加速开发过程。通过利用现成的库,开发人员可以更快地构建应用程序,而不必从头开始编写每一个功能。

  6. 标准化: 某些库成为行业或社区标准,提供了一致的接口和实现。这种标准化有助于确保代码的一致性,同时使得不同项目之间更容易进行集成和交互。

  7. 可扩展性: 库使得软件的架构更具扩展性。通过将不同的模块组织为库,可以更容易地添加新功能、升级现有功能或替换特定的实现,而无需修改整个应用程序。

总体而言,引入库的概念有助于构建更可靠、可维护和可扩展的软件系统,提高了软件开发的效率和质量。

🐧写一个自己的库

为了更深入的理解库运作的原理,我们尝试自己写一个库,并交给其他小伙伴使用。

接下来,我们将实现一个加减运算的程序,并将给程序的源代码与头文件进行打包,并交给小伙伴——小黑使用。

$ touch add.c
$ touch add.h
$ touch sub.c
$ touch sub.h
/* add.h */
#pragma once 
int add(int a, int b);
/* add.c */
#include "add.h"
int add(int a, int b){
  return a + b;
}
/* sub.h */
#pragma once 
int sub(int a, int b);
/* sub.c */
#include "sub.h"
int sub(int a, int b){
  return a - b;
}

现在我们已经将计算器的源代码写好,现在我们想让小黑使用我们的成果,倘若我们直接把源文件以及头文件发给小黑,这种做法肯定是没问题的。

但是我们又想让小黑使用我们的成果,又不想让小黑看到我们的源代码,现在该怎么办呢?

🐦方法一

第一种方法是我们可以将源代码经过预处理、编译、汇编后形成二进制文件。小黑拿到该二进制文件后,再将它自己写的程序同样经过预处理、编译、汇编形成二进制文件,然后将两个二进制文件进行链接即可。

$ gcc -c *.c
$ ll
total 28
-rw-rw-r-- 1 hxy hxy   60 Feb 28 16:25 add.c
-rw-rw-r-- 1 hxy hxy   38 Feb 28 16:25 add.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:40 add.o
-rw-rw-r-- 1 hxy hxy   60 Feb 28 16:20 sub.c
-rw-rw-r-- 1 hxy hxy   39 Feb 28 16:19 sub.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:40 sub.o
drwxrwxr-x 2 hxy hxy 4096 Feb 28 16:40 xiaohei
$ cp *h xiaohei/
$ cp *o xiaohei/

/*小黑视角*/
ll
total 20
-rw-rw-r-- 1 hxy hxy   38 Feb 28 16:43 add.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:45 add.o
-rw-rw-r-- 1 hxy hxy  200 Feb 28 16:26 main.c
-rw-rw-r-- 1 hxy hxy   39 Feb 28 16:43 sub.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:45 sub.o
/*小黑视角*/
$ gcc -c main.c 
$ gcc -o test add.o sub.o main.o
$ ls
add.h  add.o  main.c  main.o  sub.h  sub.o  test
$ ./test 
10 + 3 = 13
10 - 3 = 7

🐦方法二 静态库

方法一中我们需要将许多的 .o文件以及.h文件打包给对方,这种做法明显感觉不是特别优雅。接下来我们就是用静态库的方式。

  1. 先将我们的.o文件打包生成一个静态库,并发送给小黑;
$ ar -rc libcalculate.a *.o
$ ll
total 32
-rw-rw-r-- 1 hxy hxy   60 Feb 28 16:25 add.c
-rw-rw-r-- 1 hxy hxy   38 Feb 28 16:25 add.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:40 add.o
-rw-rw-r-- 1 hxy hxy 2688 Feb 28 18:31 libcalculate.a
-rw-rw-r-- 1 hxy hxy   60 Feb 28 16:20 sub.c
-rw-rw-r-- 1 hxy hxy   39 Feb 28 16:19 sub.h
-rw-rw-r-- 1 hxy hxy 1240 Feb 28 16:40 sub.o
drwxrwxr-x 2 hxy hxy 4096 Feb 28 18:23 xiaohei
$ cp libcalculate.a xiaohei

/* 小黑视角 */
$ ll
total 16
-rw-rw-r-- 1 hxy hxy   38 Feb 28 18:35 add.h
-rw-rw-r-- 1 hxy hxy 2688 Feb 28 18:33 libcalculate.a
-rw-rw-r-- 1 hxy hxy  200 Feb 28 16:26 main.c
-rw-rw-r-- 1 hxy hxy   39 Feb 28 18:35 sub.h

注意

这里我们需要注意库的命名规则。库的命名是以lib为开头,以.a或.so为结尾。例如 libcalculate.a 的真实名称为 calculate

在小黑拿到我们的库文件后,他就可以编译生成自己的程序了。但是这里有几个细节需要注意:

  • 因为我们的库是第三方的,编译器并不知道这个库的存在,所以我们需要指明库所在的路径;
  • 同样,我们需要告诉编译器该链接哪一个库;
  • 同理,我们还需指明头文件所在的路径。但是目前头文件就在当前路径下,所以可省略;

2.小黑进行编译链接;

/* 小黑视角 */
$ gcc -o test main.c -L . -l calculate -I .
$ ./test 
10 + 3 = 13
10 - 3 = 7
  • -L 选项:指明库所在的路径;
  • -l 选项: 告诉编译器链接哪一个库;
  • -I 选项:告诉编译器头文件的位置;

🐦标准化

上面方法二中我们演示了一个库文件的使用原理。在实际的项目开发中,我们并不会这么随意潦草。
再以小黑为例:

  1. 将头文件全部移至一个目录下;
  2. 将库文件全部移至一个目录下;
  3. 将头文件与库文件进行打包;
  4. 将打包好的文件上传至云端;
$ mkdir lib
$ mkdir include
$ cp *.h include/
$ cp *.a lib
$ tar -czf calcuate.tgz include lib

远在海外的小黑想用我们写好的库,于是在云端将压缩包下载到了本地;

/* 小黑视角 */
$ ll
total 8
-rw-rw-r-- 1 hxy hxy 788 Feb 28 19:01 calcuate.tgz
-rw-rw-r-- 1 hxy hxy 200 Feb 28 16:26 main.c

小黑将它进行解压看到了头文件与库文件;

$ tar xzf calcuate.tgz 
$ ll
total 16
-rw-rw-r-- 1 hxy hxy  788 Feb 28 19:01 calcuate.tgz
drwxrwxr-x 2 hxy hxy 4096 Feb 28 18:56 include
drwxrwxr-x 2 hxy hxy 4096 Feb 28 18:56 lib
-rw-rw-r-- 1 hxy hxy  200 Feb 28 16:26 main.c

最后小黑进行了编译链接等一系列操作,成功运行了自己的程序;

$ gcc -o test main.c -I ./include -L ./lib -l calculate
$ ./test 
10 + 3 = 13
10 - 3 = 7

以后的小黑会经常用到这个库,但是他觉得每次都要写这么长的指令有些麻烦。于是他将这个库的头文件全部移至系统的/usr/include目录下;将库文件移至/usr/lib目录下;

$ sudo cp include/*.h /usr/include/
$ sudo cp lib/* /lib64/

以后他每次使用这个库时,编译器会自动在这两个目录下寻找所程序所依赖的头文件与库文件;

$ gcc -o test main.c -lcalculate
$ ./test 
10 + 3 = 13
10 - 3 = 7

🐦方法三 动态库

以上我们尝试将自己的源文件制作为一个静态库供小黑使用,接下来我们在尝试制作一个动态库。

$ gcc -fPIC -c add.c sub.c
  • fPIC:产生位置无关码(position independent code);
gcc -shared -o libcalculate.so *.o
  • shared: 表示生成共享库格式;

接着我们把生成的.so文件放在lib目录下,将.h文件放到include目录下,并打包发给小黑(重复之前的操作)。

$ rm lib/libcalculate.a
$ rm calcuate.tgz 
$ cp *.so lib
$ tar -czf calculate.tgz lib include
$ cp calculate.tgz xiaohei/

小黑拿到压缩文件,解压后得到libinclude于是用我们的库来链接自己的程序。

/*小黑视角*/
$ tar xzf calculate.tgz 
$ ll
total 16
-rw-rw-r-- 1 hxy hxy 2343 Feb 29 16:39 calculate.tgz
drwxrwxr-x 2 hxy hxy 4096 Feb 28 18:56 include
drwxrwxr-x 2 hxy hxy 4096 Feb 29 16:38 lib
-rw-rw-r-- 1 hxy hxy  200 Feb 28 16:26 main.c
$
$ gcc -o test main.c -I include -L lib -lcalculate
$ ll
total 28
-rw-rw-r-- 1 hxy hxy 2343 Feb 29 16:39 calculate.tgz
drwxrwxr-x 2 hxy hxy 4096 Feb 28 18:56 include
drwxrwxr-x 2 hxy hxy 4096 Feb 29 16:38 lib
-rw-rw-r-- 1 hxy hxy  200 Feb 28 16:26 main.c
-rwxrwxr-x 1 hxy hxy 8432 Feb 29 16:49 test
$
$ ./test 
./test: error while loading shared libraries: libcalculate.so: cannot open shared object file: No such file or directory

小黑发现了事情的不妙,心想刚才不是还好好的吗?怎么运行时提示找不到库文件呢?

原来是因为在程序运行时,calculate.so并没有在系统的默认路径下,所以OS找不到!那么如何才能让OS找到我们的库呢?答案是需要我们自己来配置。

🐦配置动态库

配置动态库有三种方法:

  1. 环境变量:LD_LIBRARY_PATH (临时方案);
  2. 软链接方案;
  3. 配置文件方案

🐱环境变量

导入环境变量LD_LIBRARY_PATH:

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/..../lib/ # 你的.so文件存放路径

查看环境变量LD_LIBRARY_PATH:

$ echo $LD_LIBRARY_PATH
:/usr/local/protobuf/lib/:/home/hxy/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/..../lib/ # 你的.so

现在再来运行程序就可以成功运行了:

$ ./test 
10 + 3 = 13
10 - 3 = 7

🐱软链接

上面我们说在程序运行时,calculate.so并没有在系统的默认路径下,所以OS找不到我们的库,那么这个默认路径在哪里呢?

# 一般在这两个路径下
$ /lib
$ /lib64/

所以我们直接将库文件移动到这两个路径下也可以,但是还有比较优雅一点的方案,那就是为我们的库文件建立软链接。

$ sudo ln -s lib/libcalculate.so /lib64/libcalculate.so
$ ./test 
10 + 3 = 13
10 - 3 = 7

🐱配置文件

  1. 在系统的 /etc/ld.so.conf.d/ 目录下创建一个配置文件;
$ sudo touch /etc/ld.so.conf.d/calculate.conf
  1. 将动态库所在路径写入配置文件;
$ sudo vim /etc/ld.so.conf.d/calculate.conf
$ sudo cat /etc/ld.so.conf.d/calculate.conf
/home/.../lib # 你的.so文件路径
  1. 让配置文件生效;
$ sudo ldconfig

现在小黑的程序也能成功运行了;

$ ./test 
10 + 3 = 13
10 - 3 = 7

🐧静态库与动态库的区别

以上我们通过制作一个自己的静态库,对库文件有了基础的了解。那么库为何又要分为静态库与动态库呢?二者有何区别?

  1. 链接时机和方式:

    • 静态库: 在编译时被链接到目标程序中,链接器将库的代码和数据拷贝到最终的可执行文件中。因此,可执行文件在运行时独立于库文件。
    • 动态库: 在编译时并不直接链接到目标程序,而是在运行时由操作系统动态加载到内存中。动态库的链接发生在程序启动时(静态加载)或在运行时(动态加载)。
  2. 文件大小和内存占用:

    • 静态库: 链接时会将库的代码和数据完全复制到目标程序中,可能导致可执行文件较大。每个使用该库的可执行文件都包含一份库的拷贝。
    • 动态库: 多个程序可以共享同一个动态库的实例,因此相同的库只需要在内存中存在一份,可以减小程序的大小。
  3. 更新和维护:

    • 静态库: 如果库的代码或数据发生变化,需要重新编译并重新链接所有使用该库的程序。每个程序都需要更新以包含最新版本的库。
    • 动态库: 如果库的代码或数据发生变化,只需要替换库文件而无需重新编译和链接使用该库的程序。这使得动态库更容易更新和维护。
  4. 跨平台兼容性:

    • 静态库: 可执行文件与库的链接是在编译时完成的,因此在不同平台上可能需要不同版本的库。
    • 动态库: 由于动态库的加载是在运行时由操作系统完成的,因此相同的动态库文件可以在多个平台上使用。
  5. 运行时灵活性:

    • 静态库: 执行文件在编译时固定了对静态库的依赖,无法在运行时更改。
    • 动态库: 可以在运行时加载或替换动态库,这使得系统更加灵活。

🐧动态库的运作原理

🐔为什么进程可以在运行时加载动态库?

我们知道每个程序在运行时就变成一个进程,一个进程拥有自己的虚拟地址空间。

在程序运行时,我们只需要将库加载到内存当中,经过页表映射到进程的地址空间中,我们的代码执行库中的方法就依旧还是在自己的地址空间中进行函数跳转。

🐔为什么多个进程可以共享一个动态库

当多个进程同时运行时,按照同样的方式,将库中的地址映射到每个进程的地址空间中,那么如果每个程序使用的地址都是相同的,不会产生冲突吗?

还记得我们在用 gcc 生成动态库时用到的参数 - fPIC 吗?

-fPIC 是 GCC 编译器选项,用于生成位置无关码(Position Independent Code,PIC)。位置无关码是一种在内存中加载时不依赖于特定内存地址的机器码,通常用于共享库(动态链接库)的编译。

具体来说,使用 -fPIC 选项的目的是允许将生成的目标文件用于共享库,而这些库可以被多个进程加载到内存的不同地址上,而不会发生地址冲突。

它主要特点包括:

  1. 位置独立性: 生成的代码不依赖于特定的内存地址,可以在不同的内存地址空间中运行。这对于动态链接库是必要的,因为它们可能在不同的进程中加载并映射到不同的地址。

  2. 全局偏移表(Global Offset Table,GOT): 在运行时,PIC 代码使用全局偏移表,其中包含指向全局和共享库中的符号的指针。这些指针在加载库时进行重定位,以便正确地找到符号的位置。

  3. 避免绝对地址: 使用相对寻址或基于 GOT 的寻址,而不是绝对地址。这使得代码更容易在不同的地址空间中重定位。

本章的内容到这里就结束了!如果觉得对你有所帮助的话,欢迎三连~

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小马识途营销顾问解析舆情处置方法

大部分知名企业都逃不过负面舆情这一关,有负面不一定企业就不规范,产品就不好。其实,企业做大了,难以做到尽善尽美,有时候是同行不正当竞争造成的…… 总之,网络平台上面的负面舆情信息的影响不可小视&…

深入Linux内核(进程篇)—进程切换之ARM体系架构 简单总结

context_switch函数完成Arm架构Linux进程切换,调用两个函数: 调用switch_mm() 完成用户空间切换,刷新I-CACHE,处理ASID和TLB,页表转换基址切换(即把TTBR0寄存器的值设置为新进程的PGD)&#xf…

软件更新快讯-Obsidian更新-1.5.8 linux Appimage直装

更新内容 1.5.8: 从具有相同属性的文件导航时,固定属性不会显示。 修复了Home和End在导航文档顶部和底部时不总是起作用的问题。 Fixed properties not appearing when navigating from a file that has the same properties.Fixed Home and End not a…

uniapp实战:父子组件传参之子组件数量动态变化

需求说明 现有的设置单元列表,每个带有虚线加号的可以看做是一组设置单元,点击加号可以添加一组设置单元.点击设置单元右上角可以删除对应的设置单元. 实现思路说明 利用数组元素添加或是删除的方式实现页面数量动态变化.由于每个设置单元内容都相同所以单独封装了一个子组件.…

k8s 集群调度,标签,亲和性和反亲和性,污点和容忍,pod启动状态 排错详解

目录 pod启动创建过程 kubelet持续监听的原因 调度概念 调度约束 调度过程 优点 原理 优先级选项 示例 指定调度节点 标签基本操作 获取标签帮助 添加标签(Add Labels): 更新标签(Update Labels) 删除标…

怎么判断主机电源有没有坏?是电源开关键

如何判断主机电源是否坏了? 关闭计算机电源,然后打开主机箱并取出电源。 因为电源线都是接在主板上的,所以可以先拍张照片,这样就可以知道哪根线是哪根了。 然后轻轻地拉出线。 如下图所示,电源线已从主板等处拔掉。…

electron打包前端项目

1.npm run build 打包项目文件到disk文件夹 2.安装electron:npm install electron 打开后进到/dist里面 然后把这个项目的地址配置环境变量 配置环境变量:在系统变量的path中添加进去 配置成功后,electron -v看看版本。 3.创建主程序的入口文件main.…

如何修改“Ubuntu 主机名“Windows系统?

一、修改(node2) hostnamectl set-hostname node2 二、重启 sudo reboot now

Linux系统加固:限制root用户SSH远程登录

Linux系统加固:限制root用户SSH远程登录 一、前言二、禁止root用户远程登录系统1、执行备份2、先新建一个普通用户并设置高强度密码3、编辑/etc/ssh/sshd_config文件4、重启SSH服务5、补充:查看ssh协议版本 三、验证root用户是否可以远程登录系统 &#…

(k8s中)docker netty OOM问题记录

1、首先查看docker的内存占用情况: docker top 容器名 -u 查看内存cpu占用率(容器名来自kubectl describe pod xxx或者docker ps) 可以看出内存一直增长,作为IO代理这是不正常的。 2、修改启动参数和配置文件 需要注意的是为了…

WiFi模块推动远程医疗和健康监测的革命

随着科技的不断进步,WiFi模块在医疗领域的应用正推动着远程医疗和健康监测的革命。这一技术的引入不仅提高了医疗服务的效率,也为患者提供了更为便捷、智能的医疗体验。本文将深入探讨WiFi模块如何推动远程医疗和健康监测。 实时健康监测 WiFi模块在医疗…

灰度负载均衡和普通负载均衡有什么区别

灰度负载均衡(Gray Load Balancing)与普通负载均衡的主要区别在于它们服务发布和流量管理的方式。 灰度负载均衡 目的:主要用于灰度发布,即逐步向用户发布新版本的服务,以减少新版本可能带来的风险。工作方式&#x…

模拟算法题练习(一)

模拟算法介绍: 模拟算法通过模拟实际情况来解决问题,一般容易理解但是实现起来比较复杂,有很多需要注意的细节,或者是一些所谓很“麻模“的东西。 模拟题一般不涉及太难的算法,一般就是由较多的简单但是不好处理的部…

OD(12)之Mermaid思维导图(Mindmap)

OD(12)之Mermaid思维导图(Mindmap)使用详解 Author: Once Day Date: 2024年2月29日 漫漫长路才刚刚开始… 全系列文章可参考专栏: Mermaid使用指南_Once_day的博客-CSDN博客 参考文章: 关于 Mermaid | Mermaid 中文网 (nodejs.cn)Mermaid | Diagramming and charting tool…

JVM运行流程

⭐ 作者:小胡_不糊涂 🌱 作者主页:小胡_不糊涂的个人主页 📀 收录专栏:JavaEE 💖 持续更文,关注博主少走弯路,谢谢大家支持 💖 JVM 1. 运行流程2. 运行时数据区2.1 堆&am…

鸿蒙Harmony应用开发—ArkTS声明式开发(焦点事件)

焦点事件指页面焦点在可获焦组件间移动时触发的事件,组件可使用焦点事件来处理相关逻辑。 说明: 从API Version 8开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 目前仅支持通过外接键盘的tab键、方向键触发。不支…

BEANZ NFT 概览与数据分析

作者:stellafootprint.network 编译:cicifootprint.network 数据源:BEANZ NFT Collection Dashboard 2022 年 3 月 31 日,BEANZ NFT 的出现给 Azuki NFT 持有者带来了惊喜,成为 Azuki NFT 的亲密伙伴。这个 NFT …

生成式AI设计模式:综合指南

原文地址:Generative AI Design Patterns: A Comprehensive Guide 使用大型语言模型 (LLM) 的参考架构模式和心理模型 2024 年 2 月 14 日 对人工智能模式的需求 我们在构建新事物时,都会依赖一些经过验证的方法、途径和模式。对于软件工程师来说&am…

Linux设备模型(七) - Netlink

一,什么是netlink通信机制 Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。Netlink 是一种特殊的 socket,它是 Linux 所特有的。 Netlink 是一种在内核与用户应用间进行双向数…

助力智能化农田作物除草,基于YOLOv7【tiny/l/x】不同系列参数模型开发构建农田作物场景下玉米苗、杂草检测识别分析系统

在我们前面的系列博文中,关于田间作物场景下的作物、杂草检测已经有过相关的开发实践了,结合智能化的设备可以实现只能除草等操作,玉米作物场景下的杂草检测我们则少有涉及,这里本文的主要目的就是想要基于YOLOv7系列的模型来开发…