【Linux】—— 进程程序替换

news2024/11/23 19:55:23

目录

序言

(一)替换原理

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)
      {
         //child
          printf("我是子进程: %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 ;
  }

程序执行如下:

【说明】 

  1.  在子进程中,我们打印出子进程的进程ID,并调用 execl() 函数来将子进程的映像替换为 /bin/lssssss 这个不存在的命令;
  2. 由于该命令不存在,所以 execl() 函数会失败,execl() 函数将返回 -1。因此,打印出 you can see me:" 后的返回值将是 -1。

【结论】

  1. 如果替换成功,不会有返回值,如果替换失败,一定有返回值 ;
  2. 如果失败了,必定返回;只要有返回值,就失败了;
  3. 因此不用对该函数进行返回值判断,只要继续向后运行一定是失败的!

 


 有了上述对 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、程序角度——看图理解

Linux->进程程序替换_linux程序替换_波奇~的博客-CSDN博客

上诉这张图,我来给大家隆重介绍一下!!

  1. 那么实际上呢,我曾经讲过,当你出启动一个进程时,那你是不是就要有PCB啊,会创建一个PCB;
  2. 对于一个进程,也要有自己的虚拟利空间,也要有自己的列表,那么当前进程的代码数据,它都要经过页表映射到物理内存的特定区,所以呢,那么当我们当前的这个进程,它在执行代码时,如果执行了你刚刚所调用的系统调用exec等这样的接口时,它就会根据你所传入的程序的路径和你要执行的程序的名称及选项,把磁盘当中的一个其他的程序加载到我们对应的内存,用新程序的代码来替换,此时当用我们对应的当前进程去替换我们老进程的数据和代码,把数据和代码用新的程序全部给你重新替换一遍;
  3. 其中上图中右侧那部分基本不变啊,当然了,你替换的时候,如果空间要增多,那你就重新再去调整页面就行了;
  4. 反正呢,我们在替换时就相当于当前我们的进程的内核数据结构不变,而把这个进程所匹配的代码和数据用新的程序它的代码数据来进行替换,这个行为就叫做程序替换

💨接下来,回答一个大家可能关心的问题?那就是进程进行程序替换有没有创建新进程呢?

  1. 答案是没有创建新的进程,为什么没有创建新的进程呢?很简单,因为我只是把一个新的程序加载到我们当前进程所对应的代码和数据段;
  2. 然后呢,我让CPU去调度当前进程,它就可以跑起来了,在这其中,我们并没有创建新的进程,因为当前进程的内核、PCB、地址空间,尤其是PCB的pid没有变化
     

而站在程序的角度,我们可以这样去进行理解:

  1. 假设当前我是一个进程,我家里闲的没事儿干,躺在那儿看电视呢,突然有一个人把我拉走了,让我就给它办一些它对应的事情,那么站在程序的角度呢,它是不是就相当于被动的被加载到的到了内存当中;
  2. 站在程序的角度,那么其中就相当于这个程序被加载了,是不是相当于这个程序就直接被加载到内存了?所以呢,我们也可以称我们对应的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)
   {
      //child
      printf("我是子进程: %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)
   {
      //child
      printf("我是子进程: %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)
   {
      //child
      printf("我是子进程: %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;
}

【说明】

  1. 用于输出当前程序的PID和环境变量。程序会循环输出这些信息,并每秒钟输出一次;
  2. 程序会使用 getpid() 函数获取当前进程的PID,并使用 getenv() 函数获取环境变量的值;
  3. 需要注意的是,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)
   {
      //child
      printf("我是子进程: %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 ;
}
  • 输出展示:

  1.  上述不难看出,当我们调用 execle函数时,发生的是覆盖式的调入。老的数据会被覆盖掉;
  2. 此时即传入了我自己定义的环境变量

但是有一天,我不想传自己的环境变量了。此时,我想传系统的环境变量,此时我们可以怎么做呢?

💨 此时,我们需要引入一个概念:extern char **environ;

  • 在 POSIX 标准中,全局环境变量是一个字符串指针数组,其中每个指针指向一个以 key=value 格式表示的环境变量字符串;
  • 通过使用 extern char **environ; 的声明,我们可以在程序中访问这个全局环境变量数组。

接下来,我们改动一下代码:

 

输出展示:

 【说明】

  • 我们再进行程序此时呢我们可以发现,用我们的程序去运行 other 时,此时【myenv】当前是没有的,但是【PATH】此时还是有的。确实交给子进程了

当我不仅想传系统的环境变量,还想把自己的环境变量都传给子进程时,该怎么做呢?

此时我们需要在认识一个接口:putenv

putenv 是一个 C 语言标准库函数,用于设置环境变量的值。它可以添加新的环境变量或修改已存在环境变量的值。

  • 函数原型如下:
int putenv(char *string);
  1. 参数 string 是一个以 "key=value" 格式表示的字符串;
  2. key 是要设置或修改的环境变量的名称;
  3. value 是要将该环境变量设置为的值。

代码展示:

 

输出展示:

除了上述这样的做法之外,我们还可以像下述这样去进行操作:

  • 我们在 myproc.c 中不进行 putenv 操作,我们在当前命令行中进行 export操作:

 输出展示: 

有了上述的理解。接下来解释一个问题:

 在之前学习环境变量时,我们知道 环境变量具有全局属性,可以被子进程继承下去,但是是怎么办到的呢?

  1. 很简单,因为所有的指令都是bash的子进程,而bash执行所有的指令,都可以直接通过exec去执行;
  2. 我们要给子进程把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 1
        pid_t id = fork();
        assert(id >= 0);
        (void)id;

        if(id == 0)
        {
            //child
            execvp(argv[0], argv);
            exit(1);
        }

        int status = 0;
        waitpid(id, &status, 0);
        
    }
}

 💨 整体代码:进程替换代码 


总结

以上便是关于进程程序替换的全部内容。接下来,简单回顾下本文都讲了什么!!

进程程序替换是指在一个正在运行的进程中,用另外一个可执行程序替换当前进程的执行内容,从而使新的程序代码开始执行。

进程程序替换通常用于实现进程的动态更新、功能扩展或进程间通信。在 Linux 系统中,常用的进程程序替换函数是 exec 函数族,包括 execlexecleexeclpexecvexecvp 等。

这些函数可以加载新的可执行文件,并用其替换当前进程的执行内容,从而运行新的程序。替换后,新的程序将继承原进程的一些属性,如进程 ID、文件描述符等。

需要注意以下几点:

  1. 替换后,原有的程序代码、数据和堆栈信息都会被新的程序取代,因此原有进程的状态将完全丢失。
  2. 替换的新程序需具备执行权限,并与原进程使用相同的用户身份执行,否则可能会导致权限问题。
  3. 替换后,新程序的命令行参数、环境变量等可以与原进程不同,从而实现不同的功能。

到此,本文便讲解完毕了。感谢大家的观看与支持!!!

 

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

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

相关文章

【C++】C++入门基础详解(1)

本篇内容要分享的是C的基础内容&#xff0c;C的诞生简单的说就是为了填补C语言中的语法坑&#xff0c;同时对比C语言来说增添很多便捷的语法规则&#xff0c;使用起来比C语言便捷不少&#xff0c;但是学习难度也大大增强&#xff0c;不过难度是成线性增长&#xff0c;可以一步一…

编译工具:CMake(三)| 最简单的实例升级

编译工具&#xff1a;CMake&#xff08;三&#xff09;| 最简单的实例升级 前言过程语法解释ADD_SUBDIRECTORY 指令 如何安装目标文件的安装普通文件的安装&#xff1a;非目标文件的可执行程序安装(比如脚本之类)目录的安装 修改 Helloworld 支持安装测试 前言 本篇博客的任务…

H3C交换机MIB库

非常齐全的官方MIB库 为Zabbix监控华三交换机提供诸多方便。 如下信息提供下载链接和下载账号: MIB清单下载:交换机-新华三集团-H3C MIB库:MIB-新华三集团-H3C

Python Opencv实践 - 图像透射变换

import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/pomeranian.png", cv.IMREAD_COLOR) rows,cols img.shape[:2] print(rows,cols)#opencv中的透射变换&#xff0c;需要一个3x3透射变换矩阵 #这个矩阵可以通过…

基于IDE Eval Resetter延长IntelliJ IDEA等软件试用期的方法(包含新版本软件的操作方法)

本文介绍基于IDE Eval Resetter插件&#xff0c;对集成开发环境IntelliJ IDEA等JetBrains公司下属的多个开发软件&#xff0c;加以试用期延长的方法。 我们这里就以IntelliJ IDEA为例&#xff0c;来介绍这一插件发挥作用的具体方式。不过&#xff0c;需要说明使用IDE Eval Rese…

Spring Boot+Redis 实现消息队列实践示例

Spring BootRedis 实现一个轻量级的消息队列 文章目录 Spring BootRedis 实现一个轻量级的消息队列0.前言1.基础介绍2.步骤2.1. 引入依赖2.2. 配置文件2.3. 核心源码 4.总结答疑 5.参考文档6. Redis从入门到精通系列文章 0.前言 本文将介绍如何利用Spring Boot与Redis结合实现…

二自由度机械臂的gazebo仿真

一、创建ros软件包 #1、创建工作空间 mkdir 2d_robot_ws cd 2d_robot_ws mkdir src cd src catkin_init_workspace #2、编译工作空间 cd .. catkin_make #3、创建软件包 catkin_create_pkg 2d_robot std_msgs rospy roscpp二、创建模型文件 1、编写urdf模型文件 在2d_robot_…

科研经费的来源有哪些?

目前&#xff0c;高校在我国科技创新中发挥着越来越重要的作用&#xff0c;自然高校获得经费也越来越多。一所高校的科研经费充足&#xff0c;越有利于科研学术水平的提高。那么科研经费的来源有哪些呢&#xff1f; 1. 国家拨款 对于高校的科研发展享有国家的重点支持。近年来…

人工智能原理(3)

目录 一、搜索策略 1、引言 2、盲目搜索 3、启发式搜索 二、基于状态空间图搜索技术 1、图搜索基本概念 2、状态空间搜索 3、一般的图搜索算法 三、盲目搜索 1、广度优先搜索 2、深度优先搜索 3、有界深度搜索和迭代加深搜索 四、启发式算法 1、启发性信息和评估…

容器技术发展和编排技术演进之路

目录 Jail 时代 1979 年 贝尔实验室发明 chroot 2000 年 FreeBSD 4.0 发行 FreeBSD Jail 2001 年 Linux VServer 发行 2004 年 Solaris Containers 发行 云时代 2006 年 google 推出 Process Containers 2008 年 LXC 推出 2011 年 CloudFoundry 推出 Warden 2013 年 LMCTFY 启动…

JavaScript 【DOM】

【DOM】 原创内容&#xff0c;转载请注明出处&#xff01; 一、DOM基本概念 DOM&#xff08;Document Object Model&#xff0c;文档对象模型&#xff09;是 JavaScript 操作 HTML 文档的接口&#xff0c;使文档操作变得非常优雅、简便。 DOM 最大的特点就是将 HTML 文档表示…

Pycharm找不到Conda可执行文件路径(Pycharm无法导入Anaconda已有环境)

在使用Pycharm时发现无法导入Anaconda创建好的环境&#xff0c;会出现找不到Conda可执行文件路径的问题。 解决 在输入框内输入D:\anaconda3\Scripts\conda.exe&#xff0c;点击加载环境。 注意前面目录是自己Anaconda的安装位置&#xff0c;之后就可以找到Anaconda的现有环…

STM32F407使用Helix库软解MP3并通过DAC输出,最精简的STM32+SD卡实现MP3播放器

只用STM32单片机SD卡耳机插座&#xff0c;实现播放MP3播放器&#xff01; 看过很多STM32软解MP3的方案&#xff0c;即不通过类似VS1053之类的解码器芯片&#xff0c;直接用STM32和软件库解码MP3文件&#xff0c;通常使用了labmad或者Helix解码库实现&#xff0c;Helix相对labm…

Mariadb高可用MHA

本节主要学习了Mariadb高可用MHA的概述&#xff0c;案例如何构建MHA 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、概述 1、概念 MHA&#xff08;MasterHigh Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。…

如何使用Kali Linux进行渗透测试?

1. 渗透测试简介 渗透测试是通过模拟恶意攻击&#xff0c;评估系统、应用或网络的安全性的过程。Kali Linux为渗透测试人员提供了丰富的工具和资源&#xff0c;用于发现漏洞、弱点和安全风险。 2. 使用Kali Linux进行渗透测试的步骤 以下是使用Kali Linux进行渗透测试的基本…

搭建WebDAV服务手机ES文件浏览器远程访问

文章目录 1. 安装启用WebDAV2. 安装cpolar3. 配置公网访问地址4. 公网测试连接5. 固定连接公网地址6. 使用固定地址测试连接 有时候我们想通过移动设备访问群晖NAS 中的文件,以满足特殊需求,我们在群辉中开启WebDav服务,结合cpolar内网工具生成的公网地址,通过移动客户端ES文件…

【Unity每日一记】进行发射,位置相关的方法总结

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

绘制世界地图or中国地图

写在前面 在8月初,自己需要使用中国地图的图形,自己就此也查询相关的教程,自己也做一下小小总结,希望对自己和同学们有所帮助。 最终图形 这个系列从2022年开始,一直更新使用R语言分析数据及绘制精美图形。小杜的生信笔记主要分享小杜学习日常!如果,你对此感兴趣可以加…

【C++面向对象】--- 继承 的奥秘(下篇)

个人主页&#xff1a;平行线也会相交&#x1f4aa; 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】&#x1f48c; 本专栏旨在记录C的学习路线&#xff0c;望对大家有所帮助&#x1f647;‍ 希望我们一起努力、成长&…

自动化测试用例设计实例

在编写用例之间&#xff0c;笔者再次强调几点编写自动化测试用例的原则&#xff1a; 1、一个脚本是一个完整的场景&#xff0c;从用户登陆操作到用户退出系统关闭浏览器。 2、一个脚本脚本只验证一个功能点&#xff0c;不要试图用户登陆系统后把所有的功能都进行验证再退出系统…