当前位置: 首页 > news >正文

Linux - 实现一个简单的 shell

前言

之前我们对进程的替换,进程地址空间等等的概念进行了说明,本篇博客会基于这些知识点来 实现一个简单的 shell ,如有疑问,可以参考下述博客:
Linux - 进程程序替换 - C/C++ 如何实现与各个语言之间的相互调用 - 替换环境变量-CSDN博客

Linux - 进程控制(下篇)- 进程等待-CSDN博客

Linux - 进程控制(上篇)- 进程创建 和 进程终止-CSDN博客

Linux - 进程地址空间-CSDN博客

Linux - 环境变量 - 常规命令 和 内建命令-CSDN博客

因为本博客当中试下的 shell 只是非常简单的版本,全局上的变量基本只有父进程在使用,所以,很多的变量和数组都是 定义在全局当中的,其实这样是不好的,但是本篇博客的意义在于理解 Linux 当中 bash 的大概实现,而不是着手实现一个 shell。 

 简单 shell 实现

 在之前的博客当中,我们说,其实 Linux 当中的 bash(命令行解释器),本质上就是 创建子进程,创建完之后,这个子进程就是我们 在命令行当中运行命令的 可执行文件 生成的 进程。那么,为什么可以实现 bash 创建的子进程 执行其他的 可执行程序当中的代码呢?其实就是实现了 进程替换,进程替换 会替换掉 原本 进程当中 除环境变量之外的 代码和数据,所以,在替换之后,是从0 开始 执行新的程序的。

这就是整个过程。

而,像我们使用 "ls" "pwd" "cd" 这些命令,其实都是一个一个的可执行程序,一般是存储在 Linux 当中的 bin 目录下的,这也是 Linux 默认的 PATH 执行目录之一。

所以,当我们在命令行当中输入 某一个系统当中存在的命令之时,或者是运行我们自己书写的程序,本质上都是 bash 创建子进程,然后把这个子进程当中代码和数据 和 新的进程(可能是命令,也可能是我们的自己编写的程序)的代码 和数据进行替换。

然后在从 0 开始执行这个新的程序,所以,bash 就实现了一个 解析命令的作用。

首先我们来理解在 屏幕上打印的 这些,和我们输入的命令到底是什么?

在之前的一大串,其实本质上就是一个字符串,在这个字符串当中有 当前用户名,主机名,目录名等等信息,这些信息都是在环境变量当中有体现的:

 而后面我们输入的 "ls l -a" 这个命令,其实本质上就是我们输入了一个字符串,bash 解析 命令也是通过这个字符串来实现的。

而bash 解析这个命令也只是把这个字符串,按照 空格 或者其他方式把这个字符串分隔为了 多个字符串,然后,再把这些个字符串 作为参数,利用 exec*()系列函数,传入到 替换的 新的程序当中,所以,怎么运行,还是靠 这个命令的 可执行程序本身。

所以,现在我们先来解决一个问题,就是在控制台当中打印 前面一大串信息(命令行的打印)的实现:

构建一个简单的命令行

 如果想获取到 类似 bash 的 命令行当中一样的信息,我们除了可以利用 一些系统调用接口来获取之外,其实,通过环境变量来获取也是可以的。这些 信息在环境变量当中都是有的:
 

 通过这些环境变量,就可以获取到我们想要的 信息。

 上述就是我构建的 命令行,输出:
 

接下来是搞定 用户输入命令,我们要保存用户输入的命令,才能进行解析:

首先,因为用户输入命令是一个字符串,在这个字符串当中,不仅仅有字符和字母,还用用于分开各个选项参数的之前的关系的空格

所以,我们不能用 C 当中 的 scanf()这个函数来实现,因为 scanf()这个函数,读取缓冲区当中的 数据之时,读到 "\n" 或者是 空格就会结束读取,而 用户在输入命令之时,不得避免的要输入空格来分隔各个选项。

如果要是用 scanf()函数读取多个 字符串的话,在格式化输出当中,每一个字符串都要写上 "%s" 来识别这个字符串,但是,我们不清楚用户要输入多少个字符串,所以这种方式是很挫的。虽然可以使用一些通配符来实现读取一行字符出纳,但是,有个函数可以更方便的实现读取一行字符串。所以,scanf()明显不能满足我们的要求。

 我们期望的是,从何命令行当中获取一样的字符串。所以,可以使用  fgets()函数来实现:
 

其中的 stream,可以是我们打开的文件对象;其实在 Linux 当中 系统启动之时 自动会打开的 stdin ,stdout , stderr 这三个流,所以,fgets()函数直接从 stdin 当中读取数据就行了。

此时我们就存储到了 用户输入的 命令字符串:
 

 因为用户输入的字符串当中,就算用户什么字符字母都没有输入,但是当用户按下回车的时候,"\n" 也是输入了,也就是说, fgets()函数不会读取失败,最少都是要读取一个 "\n" 。

所以,我们上述拿到的字符串是不干净的,不管用户怎么输入,在字符串当中都是有一个 "\n" 。所以,我们在读取到 commandline 字符串数组当中,那这个 "\n" 给 清楚
 

像上述 就把 用户输入的 字符串给优化为我们想要的命令字符串了。

识别保存 用户输入的命令,我们把它分装成一个函数:

命令的解析 

现在,上述只是 从用户的输入当中保存到了用户输入的命令,但是,这个命令还是我们不是我们最终想要的命令我们还需要把这么命令做 解析,把这个字符串解析出多字符串,然后解析出的字符串才是我们想要的 命令 和 命令的参数。

而且,在拿到 我们想要的字符串之后,我们还要向办法把这些个字符串都 存储起来,也就是,每一个字符串的地址保存在哪里呢?

很简单,就是用 字符串指针数组来存储,数组当中一个元素存储的一个 字符串,这就类似于 main()函数的 argv 参数,存储 外部调用 这个程序 所使用的 命令的参数一样。

定义 字符串指针数组:
 

 关于切割,可以使用 strtok()函数,这个函数就可以帮助我们 每一次调用,按照空格为分隔符,切割出一个子字符串。

所以,我们可以这样写:

 此时我们就可以,测试一下,上述是否 达到 分隔字符串的目录:

输出:
 

 发现,此时在 argc 数组当中,各个元素就存储了 用户输入的 各个命令 和 参数。

创建子进程,进行 子进程代码 和数据替换(程序替换),实现 运行用户输入的命令的操作。

在前言的 程序替换 这篇博客当中说了什么是程序替换,bash 在执行 用户输入的命令的时候,就是创建子进程,然后 把用户执行的命令(本质上也是一个可执行程序),把这个可执行程序当中的代码 替换到 子进程 当中,从0 开始执行;此时,子进程就在执行 用户输入的命令对应的可执行程序了。

所以,现在我们也是要实现类似的操作。

先是创建子进程:
 

父进程当中的 等待的一些操作,现在先不写,先完成子进程当中的 程序替换操作

程序替换有 6 个库函数可以使用 ,这里使用 execvpe()函数,因为 我们存储命令的方式就是使用 数组的方式来存储的,而且 ,我们实现的简单 shell 就是让他去完成一些 系统当中的 命令操作,所以没用 PATH 环境变量当中默认的路径就可以了。最后的 e 就是环境变量,这里我们还是自己传入(其实不用自己传也是可以的,子进程会继承父进程当中的环境变量)

 至此,子进程当中的 程序替换部分,就已经实现了,现在我们运行我们的 shell,已经可以运行 系统当中的命令了
 

当然,目前我们还没有写 shell 的结束方式,所以,目前是死循环在执行的。


 我们自己写的 shell 在运行之时会遇见的问题:

 当然,上述只是 实现一个简单的 程序替换,一些比较复杂的功能,比如 vim 还是会报出一下错误的;ll 也就是 ls - l 的简写也是不行的;因为我们现在实现的 shell 和 Linux 当中的  shell 差别还是蛮大的

但是,不重要,因为上述的简单的模块,已经可以帮助我们理解很多的 shell 当中是如何进行解析的,如果执行 用户输入的命令的···· 起码上述是能运行 系统当中的很多命令的。


再次理解 何为内建命令 

 而且不止上述的问题,当我们使用 cd 命令移动当前位置之时,比如 cd .. 移动到上级路径:
 

发现,当前路径没有改变。 

 当我们切换路径之时,一点用都没有

 我们先来理解为什么不能?

当我们想要运行一个命令的可执行程序的时候,我们上述的操作是无脑的把 子进程当中的代码进行替换,所以,实际上,我们在命令行当中运行这个 命令的 可执行文件,不是父进程再跑,而是这个子进程再跑。但是我们运行的 命令行 是父进程啊,我们在屏幕上看到的 路径 是 父进程所在路径,所以,我们在屏幕上是看不到的。

换句话说,进程之间是具有独立性的,就算刚开始父子进程共用一个 代码 和 数据,但是一旦其中某一个 进程 对 代码和数据进行了修改,都会发生写时拷贝那么 子进程 执行 cd 命令,跟父进程有什么关系呢?

 没有关系!

所以,不是cd 命令不能再我们实现的 shell 当中运行,而是 cd 是在子进程当中运行的,而 我们看到的路径是父进程的。子进程 执行菜cd命令到 上级目录,根目录··· 一直 输入cd 命令,子进程夸夸跑,但是,一次cd 命令执行完,子进程就退出了,跟父进程有什么关系呢?

所以,我们不能单独的把这种 需要影响到 父进程的命令 无脑的使用 子进程的进程替换 来实现,换句话说,这样命令,不能让子进程去跑,而是让父进程去跑。这样的命令,称之为 -- 内建命令

而所有的内建命令都是在 shell 当中一个一个的函数来进行一个个命令的处理的,本质上,内建命令本质上就是一个一个的在shell 当中实现的函数。 


实现 cd 

要解决上述 cd 的话,就需要特殊判断,判断,如果当前用户输入的命令是 cd 命令的话,就进行特殊处理。

怎么处理呢?其是在 系统当中是有一些系统调用函数的,比如 chdir ( )这个函数就可以帮助我们 更改当前进程的一个工作路径

由上述可知,在 chdir 函数当中传入一个 路径,那么就可以更改的到这个路径当中,所以,因为用户输入的命令是一个字符串,而且已经被我们解析出了,在我们存储解析的字符串指针数组当中目的第一个元素,就是用户输入的 命令的名称。使用 strcmp()函数判断 argv[0] 这个元素 和 "cd" 这个字符串是否是一样的,来判断当前用户输入的是不 cd 命令。

 而且,因为用户在使用 cd 命令之时,一定是 cd 加上一个 绝对路径或者是相对路径,所以,cd 的命令 解析出来的 字符串指针数组一定是一个两个元素以上的。

其实此时使用 chdir()函数就可以实现 修改家目录的效果:

如上就是我们在 shell 当中自己实现的 cd 内建命令。 

但是,我打印当前工作路径的方式是按照 PWD 这个工作目录来打印的,如果我们单独的按照 PWD 来打印的话,此时 PWD 在 自己实现的 cd命令  修改之后是不会修改的,所以,shell 打印的命令行当中 ,打印当前工作目录的 字符串 还是 保存之前的工作目录 的字符串,不是修改之后的工作目录 的字符串:
 

此时我们查看当前的PWD环境变量:

 PWD 保存还是 发现还是 原来 父进程的工作路径

 所以,我们在 shell 当中不能仅仅是修改 父进程的当前工作路径,还应该修改 PWD,因为我们是靠 PWD 这个环境变量来在命令行当中打印 当前 父进程(也就是shell) 的工作路径的。

如何修改,其实在 系统当中专门有这个的调用接口: getcwd()函数,获取当前的工作路径

 他可以把当前的 工作路径,以字符串的形式覆盖 到 buf 这个字符串当中。

所以,我们每一次 执行 cd 这内建命令,就要重新刷新一下 PWD 环境变量当中值。

我们可以一个字符串数组来存储 PWD 当中的值:

这个 pwd 数组是全局的。 

那么我们在 getpwd()函数当中就不能这样写了,不能直接使用 getenv()函数来直接获取到 PWD 的值,而是要使用 getcwd()函数来刷新一下当前 PWD 的值,刷新到 当前的工作路径:

此时,我们在打印 命令行的 函数当中,把 getpwd()先调用,目的是刷新一下 PWD 环境变量,然后再把 pwd 传入到 printf()当中作为工作目录的 字符串:
 

 此时我们在实现之时就可以 实现 命令行当中 工作目录的实时打印了:
 

 其他一些内建命令实现

int buildCommand(char *_argv[], int _argc)
{if(_argc == 2 && strcmp(_argv[0], "cd") == 0){chdir(argv[1]);getpwd();sprintf(getenv("PWD"), "%s", pwd);return 1;}else if(_argc == 2 && strcmp(_argv[0], "export") == 0){strcpy(myenv, _argv[1]);  // 方式修改 _argv[1] 这个指针修改的到 myenv 环境变量putenv(myenv);return 1;}else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){if(strcmp(_argv[1], "$?") == 0){printf("%d\n", lastcode);lastcode=0;}else if(*_argv[1] == '$'){char *val = getenv(_argv[1]+1);if(val) printf("%s\n", val);}else{printf("%s\n", _argv[1]);}return 1;}// 特殊处理一下lsif(strcmp(_argv[0], "ls") == 0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0;
}

上述多了一个 strcpy ()多拷贝一次,是因为 ,系统当中添加环境变量,不是把 字符串当中的内容拷贝到 环境变量的存储空间当中,而是 把 这个字符串的首元素地址拷贝到 环境变量表当中。所以,如果我们直接使用 _argv[] 这个数组来 作为环境变量的 字符串地址的话,那么就可能会修改到 _argv[] 某一个元素之时,就会修改到这个 环境变量。

环境变量表当中存储的不是 字符串这个字符串,而是这些个字符串的 首元素地址。

因为,环境变量当中存储的数据是很重要的,所以,不能随便定义一个空间,就把这个空间作为这个环境变量表当中维护的空间,因为shell 当中不知道这个空间是否会被 其他人所修改,shell 要自己维护一块空间来作为存储环境变量的空间。

总结 (shell 导入环境变量的方式)

所以,当系统运行起来的时候,或者说我们进行登录的时候,系统就是要运行一个 shell 程序。比如在 Linux 当中就是 bash 命令行解释器。

而 ,我们知道 各个进程的 环境变量是来自于 bash 父进程的,那么 bash 的环境变量从那里来呢?

其实在 自己用户,当前的工作目录下,有一写配置文件,如 Linux 当中 .bash_profile 这个文件当中:

 所以,当用户登录的时候,shell 会读取当前用户目录下的 .bash_profile 这个配置文件在个文件当中存储了环境变量导入到方式,其实 这个文件也就是一个 shell 脚本文件,进行命令解释执行之后,就创建了 当前我们所使用的 环境变量。

完整代码:
 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];
char pwd[LINE_SIZE];// 自定义环境变量表
char myenv[LINE_SIZE];
// 自定义本地变量表const char *getusername()
{return getenv("USER");
}const char *gethostname()
{return getenv("HOSTNAME");
}void getpwd()
{getcwd(pwd, sizeof(pwd));
}void interact(char *cline, int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);char *s = fgets(cline, size, stdin);assert(s);(void)s;// "abcd\n\0"cline[strlen(cline)-1] = '\0';
}int splitstring(char cline[], char *_argv[])
{int i = 0;argv[i++] = strtok(cline, DELIM);while(_argv[i++] = strtok(NULL, DELIM)); // 故意写的=return i - 1;
}void NormalExcute(char *_argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if(id == 0){//让子进程执行命令//execvpe(_argv[0], _argv, environ);execvp(_argv[0], _argv);exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) {lastcode = WEXITSTATUS(status);}}
}int buildCommand(char *_argv[], int _argc)
{if(_argc == 2 && strcmp(_argv[0], "cd") == 0){chdir(argv[1]);getpwd();sprintf(getenv("PWD"), "%s", pwd);return 1;}else if(_argc == 2 && strcmp(_argv[0], "export") == 0){strcpy(myenv, _argv[1]);putenv(myenv);return 1;}else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){if(strcmp(_argv[1], "$?") == 0){printf("%d\n", lastcode);lastcode=0;}else if(*_argv[1] == '$'){char *val = getenv(_argv[1]+1);if(val) printf("%s\n", val);}else{printf("%s\n", _argv[1]);}return 1;}// 特殊处理一下lsif(strcmp(_argv[0], "ls") == 0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0;
}int main()
{while(!quit){// 1.// 2. 交互问题,获取命令行, ls -a -l > myfile / ls -a -l >> myfile / cat < file.txtinteract(commandline, sizeof(commandline));// commandline -> "ls -a -l -n\0" -> "ls" "-a" "-l" "-n"// 3. 子串分割的问题,解析命令行int argc = splitstring(commandline, argv);if(argc == 0) continue;// 4. 指令的判断 // debug//for(int i = 0; argv[i]; i++) printf("[%d]: %s\n", i, argv[i]);//内键命令,本质就是一个shell内部的一个函数int n = buildCommand(argv, argc);// 5. 普通命令的执行if(!n) NormalExcute(argv);}return 0;
}

相关文章:

Linux - 实现一个简单的 shell

前言 之前我们对进程的替换&#xff0c;进程地址空间等等的概念进行了说明&#xff0c;本篇博客会基于这些知识点来 实现一个简单的 shell &#xff0c;如有疑问&#xff0c;可以参考下述博客&#xff1a;Linux - 进程程序替换 - C/C 如何实现与各个语言之间的相互调用 - 替换…...

不同优化器的应用

简单用用&#xff0c;优化器具体参考 深度学习中的优化器原理(SGD,SGDMomentum,Adagrad,RMSProp,Adam)_哔哩哔哩_bilibili 收藏版&#xff5c;史上最全机器学习优化器Optimizer汇总 - 知乎 (zhihu.com) import numpy as np import matplotlib.pyplot as plt import torch # …...

学习网络编程No.9【应用层协议之HTTPS】

引言&#xff1a; 北京时间&#xff1a;2023/10/29/7:34&#xff0c;好久没有在周末早起了&#xff0c;该有的困意一点不少。伴随着学习内容的深入&#xff0c;知识点越来越多&#xff0c;并且对于爱好刨根问底的我来说&#xff0c;需要了解的知识就像一座大山&#xff0c;压得…...

PSP - 蛋白质复合物结构预测 Template Pair 特征 Mask 可视化

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/134333419 在蛋白质复合物结构预测中&#xff0c;在 TemplatePairEmbedderMultimer 层中 &#xff0c;构建 Template Pair 特征的源码&#xff0c…...

RK3568开发笔记-amixer开机设置音量异常

目录 前言 一、amixer介绍 1. 显示音频设备信息 2. 显示音量信息...

STM32两轮平衡小车原理详解(开源)

一、引言 关于STM32两轮平衡车的设计&#xff0c;我想在读者阅读本文之前应该已经有所了解&#xff0c;所以本文的重点是代码的分享和分析。至于具体的原理&#xff0c;我觉得读者不必阅读长篇大论的文章&#xff0c;只需按照本文分享的代码自己亲手制作一辆平衡车&#xff0c…...

区间内的真素数问题(C#)

题目&#xff1a;区间内的真素数 找出正整数 M 和 N 之间&#xff08;N 不⼩于 M&#xff09;的所有真素数。真素数的定义&#xff1a;如果⼀个正整数P 为素数&#xff0c;且其反序也为素数&#xff0c;那么 P 就为真素数。例如&#xff0c;11&#xff0c;13 均为真素数&#…...

eclipse安装lombok插件

lombok插件下载:Download 下载完成&#xff0c;lombok.jar放到eclipse根目录&#xff0c;双击jar运行 运行界面&#xff0c;点击Install安装。 安装完成&#xff0c;重启IDE&#xff0c;rebuild 项目。 rebuild 项目...

故障演练 | 微服务架构下如何做好故障演练

前言 微服务架构场景中&#xff0c;应用系统复杂切分散。长期运行时&#xff0c;局部出现故障时不可避免的。如果发生故障时不能进行有效反应&#xff0c;系统的可用性将极大地降低。 什么是故障演练 故障演练是指模拟生产环境中可能出现的故障&#xff0c;测试系统或应用在…...

Python爬虫-获取汽车之家车家号

前言 本文是该专栏的第9篇,后面会持续分享python爬虫案例干货,记得关注。 地址:aHR0cHM6Ly9jaGVqaWFoYW8uYXV0b2hvbWUuY29tLmNuL0F1dGhvcnMjcHZhcmVhaWQ9MjgwODEwNA== 需求:获取汽车之家车家号数据 笔者将在正文中介绍详细的思路以及采集方法,废话不多说,跟着笔者直接往…...

No195.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…...

pytest与testNg自动化框架

一.pytest 1.安装pytest&#xff1a; pip install pytest 2.编写用例 - 收集用例 - 执行用例 - 生成报告 3.pytest如何自动识别用例  识别规则如下&#xff1a; 1、搜索根目录&#xff1a;默认从当前目录中搜集测试用例&#xff0c;即在哪个目录下运行pytest命令&#xff0c;…...

数据库安全:Hadoop 未授权访问-命令执行漏洞.

数据库安全&#xff1a;Hadoop 未授权访问-命令执行漏洞. Hadoop 未授权访问主要是因为 Hadoop YARN 资源管理系统配置不当&#xff0c;导致可以未经授权进行访问&#xff0c;从而被攻击者恶意利用。攻击者无需认证即可通过 RESTAPI 部署任务来执行任意指令&#xff0c;最终完…...

前端---认识HTML

文章目录 什么是HTML&#xff1f;HTML的读取、运行HTML的标签注释标签标题标签段落标签换行标签格式化标签图片标签a标签表格标签列表标签表单标签form标签input标签文本框单选框复选框普通按钮提交按钮文件选择框 select标签textarea标签特殊标签div标签span标签 什么是HTML&a…...

竞赛 题目:基于FP-Growth的新闻挖掘算法系统的设计与实现

文章目录 0 前言1 项目背景2 算法架构3 FP-Growth算法原理3.1 FP树3.2 算法过程3.3 算法实现3.3.1 构建FP树 3.4 从FP树中挖掘频繁项集 4 系统设计展示5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于FP-Growth的新闻挖掘算法系统的设计与实现…...

保姆级jupyter lab配置清单

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…...

数据结构预算法--链表(单链表,双向链表)

1.链表 目录 1.链表 1.1链表的概念及结构 1.2 链表的分类 2.单链表的实现(不带哨兵位&#xff09; 2.1接口函数 2.2函数的实现 3.双向链表的实现&#xff08;带哨兵位&#xff09; 3.1接口函数 3.2函数的实现 1.1链表的概念及结构 概念&#xff1a;链表是一种物理存储结…...

数据结构线性表——栈

前言&#xff1a;哈喽小伙伴们&#xff0c;今天我们将一起进入数据结构线性表的第四篇章——栈的讲解&#xff0c;栈还是比较简单的哦&#xff0c;跟紧博主的思路&#xff0c;不要掉队哦。 目录 一.什么是栈 二.如何实现栈 三.栈的实现 栈的初始化 四.栈的操作 1.数据入栈…...

自定义 springboot 启动器 starter 与自动装配原理

Maven 依赖 classpath 类路径管理 Maven 项目中的类路径添加来源分为三类 自定义 springboot starter starter 启动器定义的规则自定义 starter 示例 自动装配 文章链接...

16 _ 二分查找(下):如何快速定位IP对应的省份地址?

通过IP地址来查找IP归属地的功能,不知道你有没有用过?没用过也没关系,你现在可以打开百度,在搜索框里随便输一个IP地址,就会看到它的归属地。 这个功能并不复杂,它是通过维护一个很大的IP地址库来实现的。地址库中包括IP地址范围和归属地的对应关系。 当我们想要查询202…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

均衡后的SNRSINR

本文主要摘自参考文献中的前两篇&#xff0c;相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程&#xff0c;其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt​ 根发送天线&#xff0c; n r n_r nr​ 根接收天线的 MIMO 系…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...