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

『 Linux 』进程替换( Process replacement ) 及 简单Shell的实现(万字)

文章目录

    • 🦄 进程替换
      • 🦩 execl()函数
      • 🦩 execlp()函数
      • 🦩 execle()函数
      • 🦩 execv()函数
      • 🦩 execvp()函数
      • 🦩 execvpe()函数
      • 🦩 execve()函数
    • 🦄 简单Shell命令行解释器的实现
      • 🦩 大致框架与命令行提示符
      • 🦩 获取用户输入信息
      • 🦩 将缓冲区内的字符串进行分块
      • 🦩 分析并执行指令
      • 🦩 对cd命令进行处理
      • 🦩 简单Shell实现代码演示(供参考)


🦄 进程替换

请添加图片描述

在『 Linux 』Process Control进程控制(万字)-CSDN博客 中提到了些进程控制中的概念,但是在这篇文章当中对于进程替换的概念以及用法并没有完全;

在本篇文章中将对上篇文章中的进程替换的各个接口进行补充;

进程替换,按照字面意义上即为一个进程在运行过程当中替换为另一个进程;

在之前的博客当中可能提到过, 当一个程序被加载进内存当中时对应的内存会新生成一个对应的进程;

而在进程替换当中可以完美的对上面的理论进行一个反驳,即并不是每个程序加载到内存当中都会新生成一个对应的进程;

以该图为例,该图中一个正在执行的进程经过了进程替换,将磁盘中的程序的代码和数据加载到了被替换的进程对应的PCB结构体当中;

当然在物理内存当中需要对应的为该新载入的进程的数据代码开辟一块新的内存空间;

但实际上在进程地址空间来看的话也仅仅只是将对应的映射关系进行修改;

当新的程序代码数据被加载进物理内存时,随着进程逐渐发生替换,对应的原有的代码和数据也将渐渐被释放;

因为只是仅仅的发生映射关系的转换,故对应的PIDmm_struct内的数据都不会作修改;

在上篇文章中简单的使用了execl()进程替换函数进行了进程替换的演示;

#include <unistd.h>#include <iostream>using namespace std;int main() {cout << "hello world1" << endl;cout << "hello world1" << endl;cout << "hello world1" << endl;printf("当前程序为myproc 且PID为:%d \n", getpid());execl("./test_/mytest", "mytest", NULL);cout << "hello world2" << endl;cout << "hello world2" << endl;cout << "hello world2" << endl;return 0;
}

这段程序中替换的程序的代码如下:

#include <iostream>
#include<unistd.h>using namespace std;int main() {printf("当前程序为mytest 且PID为:%d\n", getpid());return 0;}

且该程序运行后的最终结果为:

$ ./myproc 
hello world1
hello world1
hello world1
当前程序为myproc 且PID为:14115 
当前程序为mytest 且PID为:14115

该段程序可以清楚证明对应的发生进程替换时对应的PID不会发生变化;

在上篇博客当中只介绍了一个exec家族的函数;

但是这样的函数一共有7个;

虽然7个接口函数实际的功能结果相同,但是对应的在传参上中有所不同;


🦩 execl()函数

请添加图片描述

  • execl()的函数原型:

    int execl(const char *path, const char *arg0, ... /* (char *) NULL */);
    

    该函数的功能为执行指定的路径下的可执行文件,并用传递给它的参数替换原有的程序;

    这意味着原始的程序将被新程序替换,原始程序的代码将不再执行,而被path参数所指定的可执行文件加载并开始执行;

  • 参数:

    path参数是可执行文件的路径;

    arg0表示要传递给程序的第一个参数,通常是新程序的名称且他为一个字符串;

    ...可选的参数列表,这些参数将作为进程替换后新程序的命令行参数传递,且参数列表必须以空指针(char*)NULL结尾;

  • 示例:

    #include <unistd.h>
    #include <stdio.h>int main() {printf("This is the original program\n");execl("/bin/ls", "ls", "-l", NULL);printf("This is the original program\n");perror("execl");exit(-1);
    }
    

    在该段代码中的原始程序若是未被新进程所替换时将会打印出两次This is the original program\n;

    而运行该段代码的结果为:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76536 Mar 14 13:51 mytest
    -rw-rw-r-- 1 _USER _USER   357 Mar 14 13:51 test.cpp
    

    当进程发生替换了之后,原有进程的代码数据将被替换,故对应的代码不会执行;

    在使用进程替换时需要使用errno指定出对应的问题;


🦩 execlp()函数

请添加图片描述

  • execlp()函数原型:

    int execlp(const char *file, const char *arg0, ... /* (char *) NULL */);
    

    execlp()函数的工作方式与execl()类似,但不同之处在于它不需要指定文件的完整路径;

    它会在系统的PATH环境变量中搜索file参数所指定的可执行文件,找到后执行它;

  • 参数:

    file为要执行的可执行文件,可以是一个简单的文件名而不需要完整的路径名;

    arg0为要传递给新程序的第一个参数,一般来说这个参数为需要执行新程序的名称;

    ...为可选参数,这些参数将作为新程序的命令行参数并进行传递;

  • 示例:

    int main() {printf("This is the original program\n");execlp("ls", "ls", "-l", NULL);printf("This is the original program\n");perror("execlp");exit(1);
    }
    

    在该段程序当中原始程序中将输出一条消息后调用execlp()函数执行ls命令;

    若是进程替换成功将会执行ls -l的命令,若是未替换成功将会退出并返回1同时打印出第二句This is the original program;

    该程序运行的结果如下:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76528 Mar 14 14:07 mytest
    -rw-rw-r-- 1 _USER _USER   582 Mar 14 14:07 test.cpp
    

🦩 execle()函数

请添加图片描述

  • execle()函数原型

    int execle(const char *path, const char *arg0, ..., char *const envp[]);
    

    该函数与execl()函数和execlp()函数类似,execle()函数会将当前的进程替换为指定路径下的可执行文件;

    但是该函数与前两者不同的是,execle()函数允许你传递一个自定义的环境变量数组给新程序;

    一般这个环境变量数组通过envp参数进行传递;

  • 参数:

    path参数表示要执行的可执行文件路径的字符串;

    arg0表示要传递给新程序的第一个参数,一般情况下该参数为新程序的名字;

    ...表示可选参数列表,这些参数将作为新程序的命令行参数并进行传递,且参数列表必须以空指针(char*)NULL进行结尾;

    envp[]指向一个以NULL结束的环境变量数组,其中每个元素都是形如NAME=VALUE的字符串;

  • 示例:

    int main() {printf("This is the original program\n");char *env[] = {(char *)"MYVAR=Hello", NULL};execle("/usr/bin/env", "env", NULL, env);printf("This is the original program\n");perror("execle");exit(1);
    }
    

    在该示例当中,原始程序将输出一条消息后调用execle()函数来执行/usr/bin/env的命令;

    由于第一个参数指定了完整的路径,故execle()函数将会直接执行该命令;

    同时通过env参数传递了一个自定义的环境变量数组给新的程序;

    最终的执行结果为:

    $ ./mytest 
    This is the original program
    MYVAR=Hello
    

🦩 execv()函数

请添加图片描述

  • execv()函数原型

    int execv(const char *path, char *const argv[]);
    

    该函数的工作方式与execlexeclp函数类似,与之不同的是该函数使用了不同的参数传递方式;

    execv()函数将参数作为一个字符串数组传给新的程序而不是通过函数参数列表进行传递使得该函数在传参时能够更加灵活;

  • 参数:

    path参数表示要执行的可执行文件的路径的字符串;

    argv[]指向一个以NULL结尾的字符串数组,每个元素都表示新程序的命令行参数;argv[0]通常是新程序的名称,后序的参数依次排列且最后一个元素必须是NULL;

  • 示例:

    int main() {printf("This is the original program\n");char *args[] = {(char *)"ls", (char *)"-l", NULL};execv("/bin/ls", args);printf("This is the original program\n");perror("execv");return 1;
    }
    

    在该示例当中,原始程序将输出一条消息并用execv()函数执行/bin/ls的命令且带-l参数;

    args数组包含了要传递给ls命令的参数列表;

    execl()execlp()函数不同,execv()函数将参数作为一个字符串传递给新的程序;

    最终的执行结果为:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76568 Mar 14 14:41 mytest
    -rw-rw-r-- 1 _USER _USER  1142 Mar 14 14:41 test.cpp
    

🦩 execvp()函数

请添加图片描述

  • execvp()函数原型:

    int execvp(const char *file, char *const argv[]);
    

    该函数的工作方式与execv()函数类似,但该函数不要求指定可执行文件的完整路径;

    该函数将在系统的PATH环境变量中搜索file参数指定的可执行文件并执行;

  • 参数:

    file表示要执行的可执行文件的名称,该参数可以是一个简单的文件名而不需要包含完整的路径;

    argv[]指向一个以NULL结束的字符串数组,每个元素表示新程序的命令行参数,argv[0]通常表示新程序的名字,后面的参数依次排列且最后一个元素必须是NULL;

  • 示例:

    int main() {printf("This is the original program\n");char *args[] = {(char *)"ls", (char *)"-l", NULL};execvp("ls", args);printf("This is the original program\n");perror("execvp");return 1;
    }
    

    在该示例中原始程序将输出一条消息并调用execvp()函数执行ls命令并带有-l参数;

    由于ls并未指出完整的路径故execvp()将在PATH中搜索ls可执行文件并执行找到的第一个匹配项;

    execv()函数类似,若是execvp()函数调用失败 (例如指定的可执行文件不存在) 将返回-1并设置errno指示错误类型;

    最终的执行结果为:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76560 Mar 14 14:53 mytest
    -rw-rw-r-- 1 _USER _USER  1416 Mar 14 14:53 test.cpp
    

🦩 execvpe()函数

请添加图片描述

  • execvpe()函数原型:

    int execvpe(const char *file, char *const argv[], char *const envp[]);
    

    execvpe()函数的工作方式与execvp()函数类似,与之不同的是该函数额外提供了一个参数允许指定自定义的环境变量;

  • 参数:

    file表示要执行的可执行文件的名称,它可以是一个简单的文件名而不需要包含完整的路径;

    argv[]指向一个以NULL结束的字符串数组,每个元素表示新程序的命令行参数且argv[0]通常表示新程序的名称,后序的参数依次排列;数组的最后一个元素必须是NULL指针;

    envp[]指向一个以NULL结束的环境变量数组,其中每个元素都是形如NAME=VALUE的字符串;

  • 示例:

    int main() {printf("This is the original program\n");char *args[] = {(char*)"ls", (char*)"-l", NULL};char *env[] = {(char *)"MYVAR=Hello", NULL};execvpe("ls", args, env);printf("This is the original program\n");perror("execvpe");return 1;
    }
    

    在该示例中,原始程序将输出一条消息并调用execvp()函数执行ls命令并带有-l参数;

    同时传递了一个自定义的环境变量数组给新的程序;

    最终的执行结果:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76600 Mar 14 15:24 mytest
    -rw-rw-r-- 1 _USER _USER  1696 Mar 14 15:24 test.cpp
    

🦩 execve()函数

请添加图片描述

  • execve()函数原型:

    int execve(const char *filename, char *const argv[], char *const envp[]);
    

    execve()函数的工作方式是将当前进程的映像(image)替换为指定路径下的可执行文件;

    与其他exec系列函数不同的是该函数为一个系统调用,将直接与操作系统内核交互并执行新的程序,而其他exec函数通常是标准库提供的函数,最终将调用execve()系统调用来执行新的程序;

    execve()函数直接与操作系统内核进行通信,提供了更直接更底层的接口,可直接控制程序的执行;

    其他exec函数则是再标准库中实现的高层接口,或许会做出一些额外的处理(路径搜索,参数组织等)后再调用execve();

    由于execve()函数直接暴露了系统调用的细节故提供了更大的灵活性和控制性;

    用户程序可以直接操作参数和环境变量使得可以自行管理文件描述符等从而实现更复杂的执行需求;

  • 参数:

    filename指向要执行的可执行文件路径的字符串;

    argv[]参数指向一个以NULL结束的字符串数组,每个元素表示新程序的命令行参数;argv[0]通常为新程序的名称,后序的参数依次排列,数组的最后一个元素必须是NULL指针;

    envp[]指向一个以NULL结束的环境变量数组,其中每个元素都是形如NAME=VALUE的字符串;

  • 示例:

    int main() {printf("This is the original program\n");char *args[] = {(char *)"ls", (char *)"-l", NULL};char *env[] = {(char*)"MYVAR=Hello", NULL};execve("/bin/ls", args, env);printf("This is the original program\n");perror("execve");return 1;
    }
    

    在这个示例中,原始程序将输出一条消息后调用execve函数来执行/bin/ls命令,并带有-l参数;

    并且传递了一个自定义的环境变量数组给新的程序;

    最终运行结果:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76600 Mar 14 16:05 mytest
    -rw-rw-r-- 1 _USER _USER  2073 Mar 14 16:05 test.cpp
    

🦄 简单Shell命令行解释器的实现

请添加图片描述

命令行解释器(Command Line Interpreter) 是一种与操作系统进行交互的软件程序;

其允许用户命令行界面(CLI)输入命令并根据命令控制OS与其对应的应用程序;

命令行解释器通常称为Shell;

它充当了用户和操作系统之间的中间层并提供了一种文本方式来执行各项操作;

命令行解释器的主要功能包括:

  • 解释和执行命令
  • 管理文件系统
  • 进程管理
  • 环境配置
  • 用户交互
  • 脚本执行

🦩 大致框架与命令行提示符

请添加图片描述

在一般的情况下在Shell当中将会显示对应的命令行提示符使用户方便进行输入;

一般的情况下命令行提示符只需要打印即可;

同时Shell必然是一个常驻进程,即一般情况下进程不退出,需要使用循环进行控制;

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>#include <cstring>
#include <iostream>
using namespace std;int main() {//   cout << "hello world" << endl;/*命令行解释器是一个常驻进程,一般情况常驻进程不退出*/while (true) {// 1.打印出提示信息printf("[SilverChariot@local MyShell]# ");fflush(stdout);}
}

使用fflush()刷新输出缓冲区防止在循环当中打印换行;


🦩 获取用户输入信息

请添加图片描述

当打印完提示信息时需要获取用户的输入信息;

声名一个数组充当字符串缓冲区并使用fgets()函数获取对应的用户输入信息(需要提前使用memset()对空间进行初始化);

#define NUM 1034  // 保存完整的命令行字符串的大小
char cmd_line[NUM];  // 缓冲区   - 用于保存完整的命令行字符串/*......
*/memset(cmd_line, '\0', sizeof cmd_line);  // 将缓冲区进行初始化// 2.获取用户输入信息(指令 及 选项 )if (fgets(cmd_line, sizeof cmd_line, stdin) == nullptr)continue;  // 如果从输入流中获取数据失败则进行下一次循环 该次循环不算cmd_line[strlen(cmd_line) - 1] = '\0';  // 由于输入换行后该缓冲区将会存储一个换行并且进行打印// 故需要将改缓冲区的换行修正为'\0'// cout << "echo :" << cmd_line << endl;// --debug 用于打印是否正确

当用户输入完输入信息时为了能够让计算机识别结束输入流一般会输入一个\n;

为了防止\n不被打印需要在对应的cmd_line[strlen(cmd_line) - 1]处置为\0;

由于是一个循环,若是从输入流中获取数据失败则进行下一次循环continue;

在该处可以将用户的输入信息进行打印从而判断该处逻辑是否出现对应问题;


🦩 将缓冲区内的字符串进行分块

请添加图片描述

由于需要在后期对用户的输入信息进行分析故需要先将用户的输入信息进行分块;

声名一个字符串数组char* []用户保存分块后的命令行字符串子串;

C++中可以使用substr()对字符串进行分块;

C语言当中则可以使用strtok()对字符串进行分块;

char *g_argv[SIZE];  // 用于保存打散后的命令行字符串子串#define SIZE 32   // 保存打散后命令字符串子串的数组大小
#define SEP " "   // 作为分隔符// 3.将缓冲区内的字符串进行分块 即命令行字符串解析工作/*......
*/g_argv[0] =strtok(cmd_line, SEP);  // 第一次调用strtok函数的时候需要传入原始字符串int index = 1;while (g_argv[index++] = strtok(nullptr, SEP)) {;  // 第二次调用时若是需要分割的是原始字符串则传入空null}

根据strtok()函数对字符串进行分块;

strtok()函数参考【std::string::substr】在此不作赘述;


🦩 分析并执行指令

请添加图片描述

当数据拆分完毕后需要对指令进行分析与执行;

一般情况下由子进程对指令进行执行,父进程则负责分析以及等待子进程退出;

使用fork()创建子进程并使用对应的进程替换接口使子进程能够运行对应的命令;

此处使用的进程替换接口为execvp()函数,具体参考上文的对于execvp()函数的解释;

// 5.创建进程 子进程执行指令 父进程等待分析指令pid_t id = fork();if(id == 0){//子进程cout << "子进程进行执行" << endl;execvp(g_argv[0], g_argv);exit(1);} else if (id > 0) {// 父进程int status = 0;waitpid(-1, &status,0);if(WIFEXITED(status)){cout << "WEXITSTATUS:" << WEXITSTATUS(status) << endl;}} else {exit(-1);}

🦩 对cd命令进行处理

请添加图片描述

当到这一步时大部分的指令都能够执行;

但是对应的cd命令并不能在该处编写的Shell中起作用;

原因是需要发生目录变化时一般为父进程发生变化,子进程的目录变化并不影响父进程;

故需要在fork()创建子进程前使用strcmp()cd进行特殊处理;

若是遇到cd命令时则可以使用chdir()接口函数进行路径的变化;

// 4.用于cd命令 需要在父进程阶段进行if(strcmp("cd",g_argv[0]) == 0){// if (g_argv[1] != nullptr && chdir(g_argv[1]) != 0) {//   cerr << "chdir failed: " << strerror(errno) << endl;// }if (g_argv[1] != nullptr) chdir(g_argv[1]);continue;}

🦩 简单Shell实现代码演示(供参考)

请添加图片描述

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>#include <cstring>
#include <iostream>
using namespace std;#define NUM 1034  // 保存完整的命令行字符串的大小
#define SIZE 32   // 保存打散后命令字符串子串的数组大小
#define SEP " "   // 作为分隔符char *g_argv[SIZE];  // 用于保存打散后的命令行字符串子串
char cmd_line[NUM];  // 缓冲区   - 用于保存完整的命令行字符串// shell 运行原理 : 子进程执行命令,父进程等待以及解析命令int main() {//   cout << "hello world" << endl;/*命令行解释器是一个常驻进程,一般情况常驻进程不退出*/while (true) {// 1.打印出提示信息printf("[SilverChariot@local MyShell]# ");fflush(stdout);memset(cmd_line, '\0', sizeof cmd_line);  // 将缓冲区进行初始化// 2.获取用户输入信息(指令 及 选项 )if (fgets(cmd_line, sizeof cmd_line, stdin) == nullptr)continue;  // 如果从输入流中获取数据失败则进行下一次循环 该次循环不算cmd_line[strlen(cmd_line) - 1] ='\0';  // 由于输入换行后该缓冲区将会存储一个换行并且进行打印// 故需要将改缓冲区的换行修正为'\0'// cout << "echo :" << cmd_line << endl;// --debug 用于打印是否正确// 3.将缓冲区内的字符串进行分块 即命令行字符串解析工作g_argv[0] =strtok(cmd_line, SEP);  // 第一次调用strtok函数的时候需要传入原始字符串int index = 1;while (g_argv[index++] = strtok(nullptr, SEP)) {;  // 第二次调用时若是需要分割的是原始字符串则传入空null}/*//用于debug for (index = 0; g_argv[index]; ++index) {printf("g_argv[%d] : %s\n", index, g_argv[index]);} */// 4.用于cd命令 需要在父进程阶段进行if(strcmp("cd",g_argv[0]) == 0){// if (g_argv[1] != nullptr && chdir(g_argv[1]) != 0) {//   cerr << "chdir failed: " << strerror(errno) << endl;// }if (g_argv[1] != nullptr) chdir(g_argv[1]);continue;}// 5.创建进程 子进程执行指令 父进程等待分析指令pid_t id = fork();if(id == 0){//子进程cout << "子进程进行执行" << endl;execvp(g_argv[0], g_argv);exit(1);} else if (id > 0) {// 父进程int status = 0;waitpid(-1, &status,0);if(WIFEXITED(status)){cout << "WEXITSTATUS:" << WEXITSTATUS(status) << endl;}} else {exit(-1);}}return 0;
}

相关文章:

『 Linux 』进程替换( Process replacement ) 及 简单Shell的实现(万字)

文章目录 &#x1f984; 进程替换&#x1f9a9; execl()函数&#x1f9a9; execlp()函数&#x1f9a9; execle()函数&#x1f9a9; execv()函数&#x1f9a9; execvp()函数&#x1f9a9; execvpe()函数&#x1f9a9; execve()函数 &#x1f984; 简单Shell命令行解释器的实现&a…...

【Linux】从零开始认识进程 — 前篇

我从来不相信什么懒洋洋的自由。我向往的自由是通过勤奋和努力实现的更广阔的人生。。——山本耀司 从零开始认识进程 1 认识冯诺依曼体系2 操作系统3 进程3.1 什么是进程&#xff1f;&#xff1f;&#xff1f;3.2 进程管理PCB 3.3 Linux中的进程深入理解 3.4 进程创建总结 送给…...

公众号留言功能恢复了,你的开通了吗?

了解公众号的人都知道&#xff0c;腾讯在2018年3月宣布暂停新注册公众号的留言功能&#xff0c;这之后注册的公众号都不具备留言功能。 这成了很多号主运营人的一块心病&#xff0c;也包括我。 没有留言&#xff0c;就好似一个人玩单机游戏&#xff0c;无法与读者互动&#xff…...

C语言葵花宝典之——文件操作

前言&#xff1a; 在之前的学习中&#xff0c;我们所写的C语言程序总是在运行结束之后&#xff0c;就会自动销毁&#xff0c;那如果我们想将一个结果进行长期存储应该如何操作呢&#xff1f;这时候就需要我们用文件来操作。 目录 1、什么是文件&#xff1f; 1.1 程序文件 1.2…...

SSM框架,MyBatis-Plus的学习(下)

条件构造器 使用MyBatis-Plus的条件构造器&#xff0c;可以构建灵活高效的查询条件&#xff0c;可以通过链式调用来组合多个条件。 条件构造器的继承结构 Wrapper &#xff1a; 条件构造抽象类&#xff0c;最顶端父类 AbstractWrapper &#xff1a; 用于查询条件封装&#xf…...

边缘计算网关的工作原理及其在工业领域的应用价值-天拓四方

随着物联网技术的快速发展&#xff0c;物联网时代已经悄然来临。在这个时代&#xff0c;数以亿计的设备相互连接&#xff0c;共享数据&#xff0c;共同构建智慧的世界。边缘计算网关通过将计算能力和数据存储推向网络的边缘&#xff0c;实现了对海量数据的实时处理&#xff0c;…...

下载指定版本的pytorch

下载网址&#xff1a;https://download.pytorch.org/whl/torch_stable.html 参考博客网址&#xff1a;https://blog.csdn.net/wusuoweiieq/article/details/132773977...

STL:List从0到1

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f648;个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&…...

利用高分五号02星高光谱数据进行地物识别

高分五号02星搭载了一台60公里幅宽、330谱段、30米分辨率的可见短波红外高光谱相机&#xff08;AHSI&#xff09;&#xff0c;可见近红外&#xff08;400~1000nm&#xff09;和短波红外光谱&#xff08;1000~2500nm&#xff09;分辨率分别达到5纳米和10纳米。单看参数性能优越&…...

前端如何识别上传的二维码---jsQR

npm npm i -d jsqrhtml <el-button click"$refs.input.click()">识别</el-button> <input type"file" style"display: none" id"input" input"upload">js import jsQR from "jsqr";decodeQR…...

flink1.18.0 自定义函数 接收row类型的参数

比如sql中某字段类型 array<row<f1 string,f2 string,f3 string,f4 bigint>> 现在需要编写 tableFunction 需要接受的参数如上 解决方案 用户定义函数|阿帕奇弗林克 --- User-defined Functions | Apache Flink...

JDK8和JDK11在Ubuntu18上切换(解决nvvp启动报错)

本文主要介绍JDK8和JDK11在Ubuntu18上切换&#xff0c;以供读者能够理解该技术的定义、原理、应用。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;计算机杂记 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人…...

基于eleiment-plus的表格select控件

控件不是我写的&#xff0c;来源于scui,但在使用中遇到了一些问题&#xff0c;希望能把过程记录下来&#xff0c;同时把这个问题修复掉。 在使用的时候对控件进行二级封装&#xff0c;比如我的一个商品组件&#xff0c;再很多地方可以用到&#xff0c;于是 <template>&l…...

「❤️万文总结 时光回忆录❤️」那年,我在北京邮电大学计算机学院求学的日子

文章目录 关于我 | About Me梦绕西土城&#xff0c;邮情涌流 | Dreams and Connections in Haidian 北邮求学记 | My Days at BUPT岁月如歌&#xff0c;追忆往昔 | Reminiscing the Fleeting Years新篇章&#xff1a;班级与环境 | New Class, New Surroundings高压与挑战&#…...

【四 (1)数据可视化之如何选用正确的图表】

目录 文章导航一、数据分析中可视化的作用1、揭示数据关联和模式2、支持数据分析和决策3、提升沟通和共享效果4、强调关键信息和发现5、增强故事叙述和记忆效果6、有效增强数据交互性数据7、复杂信息易理解8、数据多维度显示 二、如何选用合适的图表1、简洁性避免使用过于复杂或…...

PHP<=7.4.21 Development Server源码泄露漏洞 例题

打开题目 dirsearch扫描发现存在shell.php 非预期解 访问shell.php&#xff0c;往下翻直接就看到了flag.. 正常解法 访问shell.php 看见php的版本是7.3.33 我们知道 PHP<7.4.21时通过php -S开起的WEB服务器存在源码泄露漏洞&#xff0c;可以将PHP文件作为静态文件直接输…...

大语言模型RAG-技术概览 (一)

大语言模型RAG-技术概览 (一) 一 RAG概览 检索增强生成&#xff08;Retrieval-AugmentedGeneration, RAG&#xff09;。即大模型在回答问题或生成问题时会先从大量的文档中检索相关的信息&#xff0c;然后基于这些信息进行回答。RAG很好的弥补了传统搜索方法和大模型两类技术…...

【嵌入式DIY实例】-DIY锂电池电压检测表

DIY锂电池电压检测表 文章目录 DIY锂电池电压检测表1、直流电压检测传感器介绍2、硬件准备3、代码实现4、OLED显示在电子应用中,通常需要使用到电池,电源管理是必不可少的部分。本文将详细介绍如何使用一个0-25V的直流电压传感器来检测锂电池的电压。 1、直流电压检测传感器介…...

生成baidu.com域名的私有证书:Linux系统命令示例

在Linux系统上生成一个针对xzyxdev.prec-tech.com域名的私有证书&#xff08;通常指的是自签名证书&#xff09;&#xff0c;你可以使用openssl工具。以下是一个简单的步骤和命令示例来生成这样的证书&#xff1a; 生成私钥 首先&#xff0c;你需要生成一个私钥。这通常是一个…...

小程序学习4 mock

services/home.js import { config, cdnBase } from ../../config/index;/** 获取首页数据 */ function mockFetchHome() {const { delay } require(../_utils/delay);const { genSwiperImageList } require(../../model/swiper);return delay().then(() > {return {swip…...

Unity3D MMORPG角色的UI血条管理详解

前言 在Unity3D游戏开发中&#xff0c;MMORPG&#xff08;Massively Multiplayer Online Role-Playing Game&#xff09;游戏是一种非常流行的游戏类型。在这种类型的游戏中&#xff0c;玩家通常可以选择不同的角色来进行游戏&#xff0c;而角色的血条管理是游戏中非常重要的一…...

【python】爬取杭州市二手房销售数据做数据分析【附源码】

一、背景 在数据分析和市场调研中&#xff0c;获取房地产数据是至关重要的一环。本文介绍了如何利用 Python 中的 requests、lxml 库以及 pandas 库&#xff0c;结合 XPath 解析网页信息&#xff0c;实现对链家网二手房销售数据的爬取&#xff0c;并将数据导出为 Excel 文件的过…...

Day34:安全开发-JavaEE应用反射机制攻击链类对象成员变量方法构造方法

目录 Java-反射-Class对象类获取 Java-反射-Field成员变量类获取 Java-反射-Method成员方法类获取 Java-反射-Constructor构造方法类获取 Java-反射-不安全命令执行&反序列化链构造 思维导图 Java知识点 功能&#xff1a;数据库操作&#xff0c;文件操作&#xff0c;…...

Transformer代码从零解读【Pytorch官方版本】

文章目录 1、Transformer大致有3大应用2、Transformer的整体结构图3、如何处理batch-size句子长度不一致问题4、MultiHeadAttention&#xff08;多头注意力机制&#xff09;5、前馈神经网络6、Encoder中的输入masked7、完整代码补充知识&#xff1a; 1、Transformer大致有3大应…...

安卓性能优化面试题 31-35

31. 简述Handler导致的内存泄露的原因以及如何解决 ?在Android开发中,Handler对象可能导致内存泄漏的主要原因是由于Handler持有对外部类对象的隐式引用,从而导致外部类无法被垃圾回收,进而引发内存泄漏。下面是导致Handler内存泄漏的几种常见情况及相应的解决方法: 1. 长…...

QML与C++通信

一、QML中如何使用C的类和对象 前提条件&#xff1a; 1.从 QObject 或 QObject 的派生类继承 2.使用 Q_OBJECT 宏 这两个条件是为了让一个类能够进入 Qt 强大的元对象系统&#xff08;meta-object system&#xff09;中&#xff0c;只有使用元对象系统&#xff0c;一个类的某些…...

Explain详解与索引优化最佳实践

Explain工具介绍 使用EXPLAIN关键字可以模拟优化器执行SQL语句,分析你的查询语句或是结构的性能瓶颈 在select语句之前增加explain关键字,MySQL会在查询前设置一个标记,执行查询会返回执行计划的信息,而不是执行这条SQL 注意: 如果from中包含子查询,仍会执行该子查询,将结果…...

Spring Boot轻松整合Minio实现文件上传下载功能【建议收藏】

一、Linux 安装Minio 安装 在/root/xxkfz/soft目录下面创建文件minio文件夹&#xff0c;进入minio文件夹&#xff0c;并创建data目录&#xff1b; [rootxxkfz soft]# mkdir minio [rootxxkfz soft]# cd minio [rootxxkfz minio]# mkdir data执行如下命令进行下载 [rootxxkf…...

MySql入门教程--MySQL数据库基础操作

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …...

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Slider)

滑动条组件&#xff0c;通常用于快速调节设置值&#xff0c;如音量调节、亮度调节等应用场景。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 Slider(options?: SliderOption…...