【Linux】—— 进程程序替换
目录
序言
(一)替换原理
1、进程角度——见见猪跑
1️⃣ 认识 execl 函数
2、程序角度——看图理解
(二)替换函数
1、命名理解
2、函数理解
1️⃣execlp
2️⃣execv
3️⃣execvp
4️⃣execle
5️⃣execve
6️⃣execve
(三)自制shell
总结
序言
在前面的文章中,我已经详细的讲解了进程的创建。但是大家是否知道创建子进程的目的是什么呢?
- 其实很简单,无非就是让子进程帮我 (父进程) 执行特定的任务而已
此时又有一个问题被衍生出来了:那就是子进程如果指向一个全新的程序代码时呢?
- 基于上述这样的问题,就需要用到本节讲到的 — 程序替换
(一)替换原理
1、进程角度——见见猪跑
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。
1️⃣ 认识 execl 函数
execl 是一个在操作系统中用于进程替换的系统调用函数,它允许将当前的进程映像替换为另一个可执行文件的映像。下面是关于 execl 函数的详细解释:
int execl(const char *path, const char *arg0, ... /* (char *) NULL */);
参数说明:
- path:用于指定要替换为的新程序的路径。
- arg0:新程序的名称。该参数在新程序中被作为- argv[0]参数传递。
返回值:
- 如果调用成功,execl函数不会返回,因为进程被替换为了新的程序映像;
- 若发生错误,该函数将返回 -1,并设置全局变量 errno以指示错误类型。
💨 当我们到 man 手册中去查找时,查询如下:

接下来,我简单的写段代码对 execl 函数的返回值进行介绍:
   #include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/wait.h>int main(){pid_t id = fork();if(id == 0){//childprintf("我是子进程: %d\n", getpid());int n= execl("/bin/lssssss", "lssssss", "-a", "-ln", NULL); //lsssss: 不存在                                                                                                   printf("you can see me : %d\n",n);exit(0);}sleep(5);//父进程printf("我是父进程: %d\n", getpid());waitpid(id, NULL, 0);return 0 ;}
程序执行如下:

【说明】
-  在子进程中,我们打印出子进程的进程ID,并调用 execl()函数来将子进程的映像替换为/bin/lssssss这个不存在的命令;
- 由于该命令不存在,所以 execl()函数会失败,execl()函数将返回 -1。因此,打印出 you can see me:" 后的返回值将是 -1。
【结论】
- 如果替换成功,不会有返回值,如果替换失败,一定有返回值 ;
- 如果失败了,必定返回;只要有返回值,就失败了;
- 因此不用对该函数进行返回值判断,只要继续向后运行一定是失败的!
有了上述对 execl 函数的认识,我相信下面这段代码对大家来说就小菜一碟了:
   #include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/wait.h>int main(){printf("begin...\n");printf("begin...\n");printf("begin...\n");printf("begin...\n");// 执行进程替换execl("/bin/ls", "ls", "-l", NULL);printf("end...\n");printf("end...\n");printf("end...\n");printf("end...\n");      return 0;}- 程序输出执行结果如下:

【说明】

在上述示例中,execl 函数被调用来将当前进程替换为 /bin/ls,并传递 -l 参数给 ls 命令。如果 execl 函数执行成功,当前进程的映像将被替换为新的 ls 程序的映像。如果 execl 函数执行失败,将打印相应的错误信息。
需要注意的是,在调用 execl 函数时,需要指定新程序的完整路径,并确保该路径下的程序可执行。同时,还可以传递其他命令行参数给新程序,后续的参数通过参数列表传递,以 NULL 结束。
2、程序角度——看图理解

上诉这张图,我来给大家隆重介绍一下!!
- 那么实际上呢,我曾经讲过,当你出启动一个进程时,那你是不是就要有PCB啊,会创建一个PCB;
- 对于一个进程,也要有自己的虚拟利空间,也要有自己的列表,那么当前进程的代码数据,它都要经过页表映射到物理内存的特定区,所以呢,那么当我们当前的这个进程,它在执行代码时,如果执行了你刚刚所调用的系统调用exec等这样的接口时,它就会根据你所传入的程序的路径和你要执行的程序的名称及选项,把磁盘当中的一个其他的程序加载到我们对应的内存,用新程序的代码来替换,此时当用我们对应的当前进程去替换我们老进程的数据和代码,把数据和代码用新的程序全部给你重新替换一遍;
- 其中上图中右侧那部分基本不变啊,当然了,你替换的时候,如果空间要增多,那你就重新再去调整页面就行了;
- 反正呢,我们在替换时就相当于当前我们的进程的内核数据结构不变,而把这个进程所匹配的代码和数据用新的程序它的代码数据来进行替换,这个行为就叫做程序替换。
💨接下来,回答一个大家可能关心的问题?那就是进程进行程序替换有没有创建新进程呢?
- 答案是没有创建新的进程,为什么没有创建新的进程呢?很简单,因为我只是把一个新的程序加载到我们当前进程所对应的代码和数据段;
- 然后呢,我让CPU去调度当前进程,它就可以跑起来了,在这其中,我们并没有创建新的进程,因为当前进程的内核、PCB、地址空间,尤其是PCB的pid没有变化,
 
而站在程序的角度,我们可以这样去进行理解:
- 假设当前我是一个进程,我家里闲的没事儿干,躺在那儿看电视呢,突然有一个人把我拉走了,让我就给它办一些它对应的事情,那么站在程序的角度呢,它是不是就相当于被动的被加载到的到了内存当中;
- 站在程序的角度,那么其中就相当于这个程序被加载了,是不是相当于这个程序就直接被加载到内存了?所以呢,我们也可以称我们对应的exec以及之后学习到的这些程序替换函数,我们就可以称它为叫做加载器。
(二)替换函数
在操作系统中,有几个常用的进程替换函数可以使用,包括 exec 系列函数。下面是对它们的简要介绍:
exec 系列函数用于执行一个新的程序映像,将当前进程替换为新程序。这些函数包括:
- execl:接收可变数量的参数作为命令行参数传递给新程序。
- execv:接收参数数组,其中第一个元素是新程序的路径,后续元素是命令行参数。
- execle:与- execl类似,但额外接收一个环境变量数组作为参数。
- execve:与- execv类似,但额外接收一个环境变量数组作为参数。
- execlp:与- execl类似,但允许通过环境变量- PATH自动搜索可执行文件的路径。
- execvp:与- execv类似,但允许通过环境变量- PATH自动搜索可执行文件的路径。
这些函数在调用成功时不会返回,因为进程映像已被替换为新程序。如果调用失败,它们将返回 -1,并设置全局变量 errno 指示错误类型。
- 接下来,我们通过 man手册去对其进行查询:

1、命名理解
这些函数原型看起来很容易混,但只要掌握了规律就很好记:
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
 

2、函数理解
在上述我们已经对 execl 函数进行了详解,接下来我逐个对剩余的函数进行解释。
1️⃣execlp
execlp 函数是 exec 系列函数之一,用于执行一个新的程序映像并替换当前进程。execlp 函数通过在系统的标准路径(由 PATH 环境变量指定)中搜索可执行文件来确定要执行的程序。
- execlp 函数的原型如下:
int execlp(const char *file, const char *arg, ...);
- file:参数是要执行的程序文件名称或路径。如果在 PATH中找到匹配的可执行文件,则只需提供文件名即可。
- arg:参数是要传递给新程序的命令行参数列表。需要以空指针结尾。
下面是一个使用 execlp 函数的示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){//childprintf("我是子进程: %d\n", getpid());char *const myargv[] = {"ls","-a","-l","-n",NULL};execlp("ls","-ls", "-a","-l","-n",NULL);                                                                                                                                     exit(1);}sleep(1);int status = 0;//父进程printf("我是父进程: %d\n", getpid());waitpid(id, &status, 0);printf("child exit code: %d\n", WEXITSTATUS(status));return 0 ;
}
输出展示:

2️⃣execv
execv 是一个系统调用函数,用于在当前进程的上下文中执行一个新的程序。
- 函数原型如下:
int execv(const char *path, char *const argv[]);
参数说明:
- path是一个字符串,表示要执行的程序的路径。
- argv是一个以 NULL 结尾的字符串数组,表示要传递给执行的程序的命令行参数。
execv 执行成功时不会返回,而是直接将当前进程替换为新的程序。如果 execv 调用失败,它会返回 -1,并且当前进程的状态不会改变。
下面是一个使用 execv 的示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){//childprintf("我是子进程: %d\n", getpid());char *const myargv[] = {"ls","-a","-l","-n",NULL};execv("/bin/ls", myargv); //lsssss: 不存在                                                                                                                                    exit(1);}sleep(1);int status = 0;//父进程printf("我是父进程: %d\n", getpid());waitpid(id, &status, 0);printf("child exit code: %d\n", WEXITSTATUS(status));return 0 ;
}
输出展示:

【说明】
- 使用 execv执行了 ls a l -n 命令。"/bin/ls"指定了要执行的程序的路径,而myargv数组包含了命令行参数。当 execv 成功执行时,当前进程就会被ls程序所替代,并且输出文件列表。
- 需要注意的是,execv 函数需要提供完整的可执行文件路径,并且命令行参数在数组 argv中以 NULL 结尾。
 3️⃣execvp
 
execvp 是一个系统调用函数,与 execv 类似,用于在当前进程的上下文中执行一个新的程序。它的参数形式稍有不同,主要是在指定程序路径时可以省略路径。
- 函数原型如下:
int execvp(const char *file, char *const argv[]);
参数说明:
- file是一个字符串,表示要执行的程序的路径。如果- file不包含斜杠字符(- /),那么系统会按照标准的搜索路径规则来查找可执行文件。
- argv是一个以 NULL 结尾的字符串数组,表示要传递给执行的程序的命令行参数。
与 execv 不同的是,execvp 可以在当前进程的环境变量 PATH 指定的路径中搜索要执行的程序,而不需要提供完整的路径。
下面是一个使用 execvp 的示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){//childprintf("我是子进程: %d\n", getpid());char *const myargv[] = {"ls","-a","-l","-n",NULL};execvp("ls", myargv);                                                                                                                               exit(1);}sleep(1);int status = 0;//父进程printf("我是父进程: %d\n", getpid());waitpid(id, &status, 0);printf("child exit code: %d\n", WEXITSTATUS(status));return 0 ;
}
输出展示:

【说明】
- 我们使用 execvp 执行了 ls a l -n  命令。由于 "ls" 是一个简单的命令,而不是一个具体的可执行文件路径,所以 execvp会在系统的PATH环境变量中搜索 "ls" 可执行文件,并执行该文件。当 execvp 成功执行时,当前进程就会被ls程序所替代,并且输出文件列表。
- 与 execv相比,execvp 更加灵活,因为它可以直接使用程序名称而不需要指定完整路径。
4️⃣execle
execle 是一个系统调用函数,用于在当前进程的上下文中执行一个新的程序,并且可以指定环境变量。
- 函数原型如下:
int execle(const char *path, const char *arg0, ..., const char *argn, char *const envp[]);
参数说明:
- path:是一个字符串,表示要执行的程序的路径。
- arg:到 argn是一系列以 NULL 结尾的字符串,表示要传递给执行的程序的命令行参数。
- envp:是一个以 NULL 结尾的字符串数组,表示要设置给新程序的环境变量。
与 execv 和 execvp不同的是,execle 可以显式地指定环境变量,而不是继承当前进程的环境变量。
下面是一个使用 execle 的示例:
- 首先,为了跟上述的代码区分开,我另外在创了一个文件,在里面放入了相应的信息,目的就是通过我们的【myproc】去调用other目录下的【otherproc】:

- otherproc.cc代码如下:
#include <iostream>
#include <unistd.h>
#include <stdlib.h>using namespace std;int main()
{for(int i = 0; i < 5; i++){cout << "----------------------------------------------------------------"<< endl;cout << "我是另一个程序,我的pid是: " << getpid() << endl;cout << " MYENV: " << (getenv("MYENV")==NULL?"NULL":getenv("MYENV")) << endl;cout << " PATH: " << (getenv("PATH")==NULL?"NULL":getenv("PATH")) << endl;cout << "----------------------------------------------------------------"<< endl;sleep(1);}return 0;
}【说明】
- 用于输出当前程序的PID和环境变量。程序会循环输出这些信息,并每秒钟输出一次;
- 程序会使用 getpid()函数获取当前进程的PID,并使用getenv()函数获取环境变量的值;
- 需要注意的是,getenv()函数用于获取指定环境变量的值。在程序中,使用了"MYENV"和"PATH"作为要获取的环境变量名,而对于 PATH来说在系统中默认是有的
输出展示:

接下来,我们退出 other 目录,对【myproc.c】进行改造:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){//childprintf("我是子进程: %d\n", getpid());char *const myenv[]={"MYENV=YouCanSeeMe",NULL};execle("./other/otherproc","otherproc",NULL,myenv);                                                                                                                             exit(1);}sleep(1);int status = 0;//父进程printf("我是父进程: %d\n", getpid());waitpid(id, &status, 0);printf("child exit code: %d\n", WEXITSTATUS(status));return 0 ;
}
- 输出展示:

- 上述不难看出,当我们调用 execle函数时,发生的是覆盖式的调入。老的数据会被覆盖掉;
- 此时即传入了我自己定义的环境变量
但是有一天,我不想传自己的环境变量了。此时,我想传系统的环境变量,此时我们可以怎么做呢?
💨 此时,我们需要引入一个概念:extern char **environ;
- 在 POSIX 标准中,全局环境变量是一个字符串指针数组,其中每个指针指向一个以 key=value格式表示的环境变量字符串;
- 通过使用 extern char **environ;的声明,我们可以在程序中访问这个全局环境变量数组。
接下来,我们改动一下代码:

输出展示:

【说明】
- 我们再进行程序此时呢我们可以发现,用我们的程序去运行 other 时,此时【myenv】当前是没有的,但是【PATH】此时还是有的。确实交给子进程了
当我不仅想传系统的环境变量,还想把自己的环境变量都传给子进程时,该怎么做呢?
此时我们需要在认识一个接口:putenv
putenv 是一个 C 语言标准库函数,用于设置环境变量的值。它可以添加新的环境变量或修改已存在环境变量的值。
- 函数原型如下:
int putenv(char *string);
- 参数 string是一个以"key=value"格式表示的字符串;
- key是要设置或修改的环境变量的名称;
- value是要将该环境变量设置为的值。
代码展示:

输出展示:

除了上述这样的做法之外,我们还可以像下述这样去进行操作:
- 我们在 myproc.c 中不进行 putenv 操作,我们在当前命令行中进行 export操作:

输出展示:

有了上述的理解。接下来解释一个问题:
在之前学习环境变量时,我们知道 环境变量具有全局属性,可以被子进程继承下去,但是是怎么办到的呢?
- 很简单,因为所有的指令都是bash的子进程,而bash执行所有的指令,都可以直接通过exec去执行;
- 我们要给子进程把bash的环境变量交给子进程,只需要调用 【execle】,然后再把我们指定的环境变量,直接以最后一个参数的形式传给子进程,此时子进程就拿到了!!!
5️⃣execve
  
 
execve 是一个系统调用函数,它会替换当前进程的映像,将其替换为新程序的映像,并开始执行新程序。
- 函数原型如下:
int execve(const char *filename, char *const argv[], char *const envp[]);
参数说明:
- 参数 filename是要执行的程序的路径;
- 第二个参数 argv[]是一个字符串数组,它包含了传递给新程序的命令行参数;
- 而 envp[]是一个字符串数组,它包含了传递给新程序的环境变量。
这个跟上述的 execle 函数是类似的。在这里就不做过多演示。
6️⃣execve
 事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册第2节,其它函数在man手册第3节
  

这些函数之间的关系如下图所示:

(三)自制shell
 我们可以用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。

然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
 所以要写一个shell,需要循环以下过程:
- 1. 获取命令行
- 2. 解析命令行
- 3. 建立一个子进程(fork)
- 4. 替换子进程(execvp)
- 5. 父进程等待子进程退出(wait)
 
根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了
实现代码:
  
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define MAX 1024
#define ARGC 64
#define SEP " "int split(char *commandstr, char *argv[])
{assert(commandstr);assert(argv);argv[0] = strtok(commandstr, SEP);if(argv[0] == NULL) return -1;int i = 1;while((argv[i++] = strtok(NULL, SEP)));return 0;
}void debugPrint(char *argv[])
{for(int i = 0; argv[i]; i++){printf("%d: %s\n", i, argv[i]);}
}int main()
{while(1){char commandstr[MAX] = {0};char *argv[ARGC] = {NULL};printf("[zhangsan@mymachine currpath]# ");fflush(stdout);char *s = fgets(commandstr, sizeof(commandstr), stdin);assert(s);// 保证在release方式发布的时候,因为去掉assert了,所以s就没有被使用// 而带来的编译告警, 什么都没做,但是充当一次使用(void)s;commandstr[strlen(commandstr)-1] = '\0';int n = split(commandstr, argv);if(n != 0) continue;//debugPrint(argv);// version 1pid_t id = fork();assert(id >= 0);(void)id;if(id == 0){//childexecvp(argv[0], argv);exit(1);}int status = 0;waitpid(id, &status, 0);}
}💨 整体代码:进程替换代码
总结
以上便是关于进程程序替换的全部内容。接下来,简单回顾下本文都讲了什么!!
进程程序替换是指在一个正在运行的进程中,用另外一个可执行程序替换当前进程的执行内容,从而使新的程序代码开始执行。
进程程序替换通常用于实现进程的动态更新、功能扩展或进程间通信。在 Linux 系统中,常用的进程程序替换函数是 exec 函数族,包括 execl、execle、execlp、execv、execvp 等。
这些函数可以加载新的可执行文件,并用其替换当前进程的执行内容,从而运行新的程序。替换后,新的程序将继承原进程的一些属性,如进程 ID、文件描述符等。
需要注意以下几点:
- 替换后,原有的程序代码、数据和堆栈信息都会被新的程序取代,因此原有进程的状态将完全丢失。
- 替换的新程序需具备执行权限,并与原进程使用相同的用户身份执行,否则可能会导致权限问题。
- 替换后,新程序的命令行参数、环境变量等可以与原进程不同,从而实现不同的功能。
到此,本文便讲解完毕了。感谢大家的观看与支持!!!

相关文章:
 
【Linux】—— 进程程序替换
目录 序言 (一)替换原理 1、进程角度——见见猪跑 1️⃣ 认识 execl 函数 2、程序角度——看图理解 (二)替换函数 1、命名理解 2、函数理解 1️⃣execlp 2️⃣execv 3️⃣execvp 4️⃣execle 5️⃣execve 6️⃣execve…...
 
idea创建javaweb项目,jboss下没有web application
看看下图这个地方有没有web application...
 
广东灯具3D扫描抄数建模服务3D测绘出图纸三维逆向设计-CASAIM
灯具三维逆向建模是一种将实际物体转换为数字模型的过程。通过逆向工程技术,可以将现有的灯具进行3D扫描,然后利用专业的逆向设计软件将其转换为准确的三维模型。 以下是CASAIM实施灯具三维逆向建模的一般步骤图: 1. 扫描:三维扫…...
Nginx反向代理-负载均衡、webshell实践
目录 1.nginx反向代理-负载均衡 1)搭建web项目 2)修改 nginx.conf的配置 2.webshell 实践 1)异或操作绕过 2)取反绕过 3)php语法绕过 1.nginx反向代理-负载均衡 1)搭建web项目 首先通过SpringBoo…...
 
第六阶|见道明心的笔墨(上)从书法之美到生活之美——林曦老师的线上直播书法课
如果你有需要,可以找我的,我这边有老师的所有课程 如果你有需要,可以找我的,我这边有老师的所有课程...
nbcio-boot从3.0升级到3.1的出现用户管理与数据字典bug
升级后出现 系统管理里的用户管理出现下面问题 2023-08-17 09:44:38.902 [http-nio-8080-exec-4] [1;31mERROR[0;39m [36mo.jeecg.common.exception.JeecgBootExceptionHandler:69[0;39m - java.lang.String cannot be cast to java.lang.Long java.lang.ClassCastException:…...
 
Curson 编辑器
Curson 汉化与vacode一样 Curson 自带chat功能 1、快捷键ctrlk(代码中编辑) 2、快捷键ctrll 右侧打开窗口...
Shell编程学习之函数的应用
Shell编程中的函数:伪代码表示: function 函数名(){函数体}注意事项: 1.函数无参数; 2.函数无返回值类型; 3.function可以不写; 4.函数不被调用,就不会执行; 5.函数名不能使用…...
 
Fork/Join框架
是什么 Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。 Fork: 把一个大任务切分为若干子任务并行的执行 Join: 合并这些子任务的执行结果,最后…...
LeetCode_字符串_中等_468.验证 IP 地址
目录 1.题目2.思路3.代码实现(Java) 1.题目 给定一个字符串 queryIP。如果是有效的 IPv4 地址,返回 “IPv4” ;如果是有效的 IPv6 地址,返回 “IPv6” ;如果不是上述类型的 IP 地址,返回 “Nei…...
 
ABAP Der Open SQL command is too big.
ABAP Der Open SQL command is too big. DBSQL_STMNT_TOO_LARGE CX_SY_OPEN_SQL_DB 应该是选择条件中 维护的条件值条数太多了...
 
QChart类用来 管理 图表的:数据序列(series)、图例(legend)和坐标轴(axis)
QChart类用来 管理 图表的:数据序列(series)、图例(legend)和坐标轴(axis) 1、数据序列类 继承关系 2、坐标轴类 的继承关系 3、图例类 什么是图例? 图例:是集中于地图…...
Servlet+JDBC实战开发书店项目讲解第10篇:在线客服功能实现
在线客服功能实现 实现思路 要实现在线客服功能,您可以考虑以下步骤: 创建一个用于存储客户消息和回复的数据库表。您可以使用JDBC连接到数据库,并使用SQL语句创建表格。 在您的Servlet中,创建一个用于处理客户消息和回复的POS…...
 
CVE-2023-21292 AMS框架层高危漏洞分析
文章目录 前言漏洞细节故事起源漏洞利用漏洞修复 总结 前言 本周在分析 Google 官方发布的 Android 2023 年8 月安全公告 涉及的漏洞补丁的时候,遇到一个有意思的漏洞:CVE-2023-21292。 之所以说它有意思是因为这个漏洞早在去年年底就在某平台上被国外…...
cuda、cuDNN、深度学习框架、pytorch、tentsorflow、keras这些概念之间的关系
当讨论CUDA、cuDNN、深度学习框架、pytorch、tensorflow、keras这些概念的时候,我们讨论的是与GPU加速深度学习相关的技术和工具。 CUDA(Compute Unified Device Architecture): CUDA是由NVIDIA开发的一种并行计算平台和编程模型&…...
 
第二讲:BeanFactory的实现
BeanFactory的实现 1. 环境准备2. 初始化DefaultListableBeanFactory3. 手动注册BeanDefinition4. 手动添加后置处理器5. 获取被依赖注入的Bean对象6. 让所有的单例bean初始化时加载7. 总结 Spring 的发展历史较为悠久,因此很多资料还在讲解它较旧的实现,…...
 
vue2+Spring Boot2.7 大文件分片上传
之前我们文章 手把手带大家实现 vue2Spring Boot2.7 文件上传功能 将了上传文件 但如果文件很大 就不太好处理了 按正常情况甚至因为超量而报错 这里 我弄了个足够大的文件 我们先搭建 Spring Boot2.7 环境 首先 application.yml 代码编写如下 server:port: 80 upload:path:…...
 
Vite更新依赖缓存失败,强制更新依赖缓存
使用vitets开发一段时间了,感觉并不是想象中的好用,特别是出现些稀奇古怪的问题不好解决,比如下面这个问题 上午9:50:08 [vite] error while updating dependencies: Error: ENOENT: no such file or directory, open E:/workspace-dir/node…...
 
Linux命令200例:tail用来显示文件的末尾内容(常用)
🏆作者简介,黑夜开发者,全栈领域新星创作者✌。CSDN专家博主,阿里云社区专家博主,2023年6月csdn上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 &…...
 
【Unity每日一记】进行发射,位置相关的方法总结
👨💻个人主页:元宇宙-秩沅 👨💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨💻 本文由 秩沅 原创 👨💻 收录于专栏:uni…...
 
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
 
相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
FOPLP vs CoWoS
以下是 FOPLP(Fan-out panel-level packaging 扇出型面板级封装)与 CoWoS(Chip on Wafer on Substrate)两种先进封装技术的详细对比分析,涵盖技术原理、性能、成本、应用场景及市场趋势等维度: 一、技术原…...
 
Ray框架:分布式AI训练与调参实践
Ray框架:分布式AI训练与调参实践 系统化学习人工智能网站(收藏):https://www.captainbed.cn/flu 文章目录 Ray框架:分布式AI训练与调参实践摘要引言框架架构解析1. 核心组件设计2. 关键技术实现2.1 动态资源调度2.2 …...
 
Pandas 可视化集成:数据科学家的高效绘图指南
为什么选择 Pandas 进行数据可视化? 在数据科学和分析领域,可视化是理解数据、发现模式和传达见解的关键步骤。Python 生态系统提供了多种可视化工具,如 Matplotlib、Seaborn、Plotly 等,但 Pandas 内置的可视化功能因其与数据结…...
【2D与3D SLAM中的扫描匹配算法全面解析】
引言 扫描匹配(Scan Matching)是同步定位与地图构建(SLAM)系统中的核心组件,它通过对齐连续的传感器观测数据来估计机器人的运动。本文将深入探讨2D和3D SLAM中的各种扫描匹配算法,包括数学原理、实现细节以及实际应用中的性能对比,特别关注…...
