【Linux】-- 进程程序替换

news2025/1/11 23:54:37

目录

引入进程程序替换

进程程序替换

初步使用exec系列函数

原理分析

做一个简易的shell

cd - 内置命令的理解

export - 环境变量的深入理解


引入进程程序替换

        对于fork的学习让我们知道:fork()之后的,父子进程各自执行父进程代码的一部分。但是创建一个子进程的目的,肯定是为了做与父进程不同的事,于是想让子进程执行一个全新进程。就有了进程程序替换。

进程程序替换

        程序替换,是通过特定的接口,加载磁盘上的一个权限的程序(代码和数据),加载到调用的进程地址空间中。

#问:进程替换,有没有创建新的子进程?

        答:没有!进程替换是通过将磁盘中的代码和数据放到内存中,然后改变子进程内核数据结构中的页表映射关系。

是什么,进程程序替换?

        程序替换是通过特定的接口,加载磁盘上的一个程序(代码和数据),即加载到调用进程的地址空间中。

为什么,进程程序替换?

        想让子进程执行其他的程序,即不想让子进程执行父进程的部分而是执行一个全新的程序。

怎么办,进程程序替换?

        进程程序替换的核心在于,如何将程序放入内存当中,即所谓的新程序的加载。Linux中采用exec系列函数解决程序的加载。而exec系列函数进行的操作是在,几乎不变化内核结构PCB的角度,将新的磁盘上的程序加载到内存,并和进程的页表重新建立映射。

初步使用exec系列函数

  • 可知:

        上面的6个函数是man 3,所以是由库提供的。是对系统调用的再次封装。而且其文档中有环境变量时的environ,也可以看出其进程程序替换与环境变量的知识关联。此处可更加深层次的了解环境变量。

int execl(const char *path, const char *arg, ...);
  • path:路径 + 目标文件名
  • arg,. . .:可变参数列表(可以传入多个不定个数参数)

        使用方式与,在命令行上执行一样,参数一个一个对应填即可,最后一个参数必须是NULL,标识参数传递完毕。

复习:

        此处,对应环境变量时的知识,其中的main函数的第二个参数。即命令行参数的存储的char*数组。

#include <stdio.h>

int main(int argc, char *argv[])
{
    int i = 0;
    while(argv[i])
    {
        printf("%d: %s\n", i, argv[i++]);
    }
    return 0;
}

        最后一个参数是NULL,表示命令行参数的结束。

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("当前进程的开始代码\n");
    execl("/usr/bin/ls","ls","-l",NULL);
    return 0;
}

原理分析

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("当前进程的开始代码\n");
    execl("/usr/bin/ls","ls",NULL);
    
    printf("当前进程的结束\n");
    return 0;
}

        我们发现该程序所执行的进程,只是将execl函数之前的printf执行了,并未将其后的printf执行。这正是因为execl是程序替换,调用该函数成功后,会将当前进程的所有的代码和数据都进行替换!包括已经执行的与没有执行的。只不过由于execl函数之前的printf已经执行并输出了。所以,一旦调用成功,后续代码全部不执行。这也代表着execl无需进行返回值的判断,成功之后程序被替换,后续判断也会被替换没。所以没有判定返回值的意义。

融汇贯通的理解:

        加载新进程之前,子进程的数据和代码与父进程共享,并且数据具有写时拷贝,这是fork的基本知识。而当子进程加载新进程的时候,在fork时所讲的代码不变而共享的存在,在此时也可以说是和数据一样,称为一种“写入”。所以代码也是需要父子分离,也是需要写时拷贝。如此在不仅仅是数据,代码也是需要写时拷贝的。

        如此在execl之后:父子进程在代码和数据上就彻底分开了,虽然曾经不冲突。

库中的exec系列函数 - 极其相似

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量
execl系列
函数名参数格式路径提供当前环境变量

execl

列表默认
execlp列表默认
execle列表需自行传

        execlpexecl的区别可以说是多了一个p,即可以理解为execl的基础上多了一个PATH,意思就是会自行在环境变量PATH里查找,不需要使用写明执行的程序在哪个路径下。

int execlp(const char *file, const char *arg, ...);
#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("当前进程的开始代码\n");
    execlp("ls","ls", "-l", NULL);

    printf("当前进程的结束\n");
    return 0;
}

        execlpexecl的区别可以说是多了一个e,即可以理解为execl的基础上多了一个evn,也就是environ。意思就是需要我们自行传环境变量。

int execle(const char *path, const char *arg, ..., char * const envp[]);
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[], char* env[])
{
    //extern char **environ;
    printf("当前进程的开始代码\n");
    execle("/usr/bin/ls", "ls", "-l", NULL, env);
    //execle("/usr/bin/ls", "ls", "-l", NULL, environ);

    printf("当前进程的结束\n");
    return 0;
}
execv系列
函数名参数格式路径提供当前环境变量
execv数组默认
execvp数组默认
execvpe数组需自行传

         execvexecl的区别可以说是l变为了v,即可以理解为l是list、v是vector,即一个是可变的链态传递,一个是定的数组态传递。

int execv(const char *path, char *const argv[]);
#include <stdio.h>
#include <unistd.h>

#define NUM 16

int main()
{

    printf("当前进程的开始代码\n");
    char *const _argv[NUM] = {
        (char*)"ls",
        (char*)"-a",
        (char*)"-l",
        (char*)"-i",
        NULL
    };
    execv("/usr/bin/ls", _argv);
    printf("当前进程的结束\n");
    return 0;
}

         execvpexecv的区别可以说是多了一个p,即可以理解为execv的基础上多了一个PATH,意思就是会自行在环境变量PATH里查找,不需要使用写明执行的程序在哪个路径下。

int execvp(const char *file, char *const argv[]);
#include <stdio.h>
#include <unistd.h>

#define NUM 16
int main()
{

    printf("当前进程的开始代码\n");
    char *const _argv[NUM] = {
        (char*)"ls",
        (char*)"-a",
        (char*)"-l",
        (char*)"-i",
        NULL
    };
    execvp("ls", _argv);
    printf("当前进程的结束\n");
    return 0;
}

         execvpeexecvp的区别可以说是多了一个e,即可以理解为execl的基础上多了一个evn,也就是environ。意思就是需要我们自行传环境变量。

int execvpe(const char *file, char *const argv[], char *const envp[]);
#include <stdio.h>
#include <unistd.h>

#define NUM 16
int main(int argc, char *argv[], char* env[])
{
    //extern char **environ;
    printf("当前进程的开始代码\n");
    char *const _argv[NUM] = {
        (char*)"ls",
        (char*)"-a",
        (char*)"-l",
        (char*)"-i",
        NULL
    };
    execvpe("/usr/bin/ls", _argv, env);
    //execvpe("/usr/bin/ls", _argv, environ);
    printf("当前进程的结束\n");
    return 0;
}

系统进程程序替换接口execve函数

#include <stdio.h>
#include <unistd.h>

#define NUM 16
int main()
{
    extern char **environ;
    printf("当前进程的开始代码\n");
    char *const _argv[NUM] = {
        (char*)"ls",
        (char*)"-a",
        (char*)"-l",
        (char*)"-i",
        NULL
    };
    execve("/usr/bin/ls", _argv, environ);
    printf("当前进程的结束\n");
}

        用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

融汇贯通的理解:

        进程程序替换原理:结合进程地址空间,通过将新的磁盘数据加载到内存中,通过改变子进程页表的映射关系,以此达到建立进程的时候,先创建子进程再加载代码与数据的操作。

        我们的手机或者电脑,并不是先将代码与数据加载到内存,而是先把进程创建出来。然后子进程通过加载函数exec系列系统接口,将磁盘的软件、app程序加载到内存里。

        所以说:进程是一个运行起来的程序,是对的但是不够准确。因为程序也有可能没运行(并未进程替换),但是进程已经有了。


拓展:

        windows也有进程程序替换的操作,以前我们在windows所用的代码书写工具VS19等,本质上就是一个进程,它们是一个集成开发环境,可以写代码也可以编译代码等。分别对应编辑器、编译器等,其是一个个的模块,它们自身只提供编辑(写代码)功能。而后面的操作需要我们进行安装对应模块,如编译代码时,由如VS19来创建子进程,并使用进程程序替换来让子进程来编译代码。于是乎我们的代码崩亏并不会印象到VS19的运行,因为进程具有相对独立性

做一个简易的shell

        shell执行命令的时候,我们所执行的命令是处于磁盘的、在文件系统里面、在目录结构里面是一个特定路径下的程序,跑起来之后变成一个进程。其运行的本质就是shell给其创建了一个子进程,然后让子进程直接执行一个exec类型的系统接口,将磁盘的内容加载到内存中去跑起来。

简易的shell的核心

  • 需要将从标准输入中获取的支付串进行分析,将其分散为可用的命令(如:"ls -a -l -i" -> "ls" "-a" "-l" "-i")。利用C字符串函数strtok
  • 将需要补充的命令参数进行补充(如:ls命令的颜色显示参数"--color=auto")。利用if语句判断第一个参数
  • 利用fork后执行程序替换,让子进程执行命令,父进程阻塞等待。此时父进程就是shell

(下列代码点是1,2,3,5,省略了4,由于4更难,完成简单的后再讲解)

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define NUM 1024
#define SIZE 32
#define SEP " "

//保存完整的命令行字符串
static char cmd_line[NUM];
//保存打散之后的命令行字符串
static char *g_argv[SIZE];

int main()
{
    while(1)
    {
        //1. 打印出提示信息 [qcr@我的系统 myshell]#
        printf("[qcr@我的系统 myshell]#");
        fflush(stdout);
        memset(cmd_line, '0', sizeof cmd_line);

        //2. 获取用户的键盘输入(如:输入的是各种指令和选项: "ls -a -l -i")
        if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
            continue;
        //意义:"-a -l -i\n\0"

        //3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-l" "-i"
        cmd_line[strlen(cmd_line) - 1] = '\0';
        g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串
        int index = 1;
        if(strcmp(g_argv[0], "ls") == 0)
            g_argv[index++] = "--color=auto";
        while(g_argv[index++] = strtok(NULL, SEP)); //第二次调用,如果还要解析原始字符串,传入NULL

        //5. fork()
        pid_t id = fork();
        if(id == 0)
        {
            //child
            printf("下面功能让子进程进行的\n");
            execvp(g_argv[0], g_argv); // ls -a -l -i
            exit(1);
        }
        //father
        int status = 0;
        pid_t ret = waitpid(id, &status, 0); //阻塞等待
        if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));
    }
    return 0;
}

 

        上面利用50多行就简单实现了一个我们的shell,由于功能实现不全,所以只能实现部分命令(后续博客会随着知识上升,会随之完整,但限于为理解,所以会很简陋)。此处的我们的shell是一个while循环打造的,所以结束需要使用CTRL + c。由于输入分析不够完整,所以对于Delete需要CTRL + Delete。

cd - 内置命令的理解

当我们使用myshell执行cd会发现:

       当我们执行cd的时候,会发现使用cd成功了,但是myshell中的当前地址并且改变。这是因为我们将cd的操作放在了子进程上,子进程进行了cd并不会改变父进程的当前地址。

        我们执行的命令是通过父进程也就是myshell创出的子进程,经过进程替换成我们所输入的命令的可执行程序的代码和数据,其执行起来的进程是子进程角度上的。我们需要在父进程上。

内置命令:让父进程(myshell)自己执行的命令,叫做内置命令,内建命令。

        cd由于是内置命令,所以其是与普通的如"ls"命令是不同的,其并不是一个存储在硬盘的可执行程序,而是由系统提供的系统接口实现。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define NUM 1024
#define SIZE 32
#define SEP " "

//保存完整的命令行字符串
static char cmd_line[NUM];
//保存打散之后的命令行字符串
static char *g_argv[SIZE];

int main()
{
    while(1)
    {
        //1. 打印出提示信息 [qcr@我的系统 myshell]#
        printf("[qcr@我的系统 myshell]#");
        fflush(stdout);
        memset(cmd_line, '0', sizeof cmd_line);
        //2. 获取用户的键盘输入(如:输入的是各种指令和选项: "ls -a -l -i")
        if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
            continue;
        //意义:"-a -l -i\n\0"
        cmd_line[strlen(cmd_line) - 1] = '\0';
        g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串
        int index = 1;
        if(strcmp(g_argv[0], "ls") == 0)
            g_argv[index++] = "--color=auto";
        while(g_argv[index++] = strtok(NULL, SEP)); //第二次调用,如果还要解析原始字符串,传入NULL
        
        //4.内置命令
        if(strcmp(g_argv[0], "cd") == 0) //父进程执行
        {
            if(g_argv[1] != NULL) chdir(g_argv[1]);
            continue;
        }
        
        //fork
        pid_t id = fork();
        if(id == 0)
        {
            //child
            printf("下面功能让子进程进行的\n");
            execvp(g_argv[0], g_argv); // ls -a -l -i
            exit(1);
        }
        //5.father
        int status = 0;
        pid_t ret = waitpid(id, &status, 0); //阻塞等待
        if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));
    }
    return 0;
}

export - 环境变量的深入理解

makefile 

.PHONY:all
all:myshell mytest

myshell:myshell.c
	gcc -o $@ $^
mytest:mytest.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -rf myshell mytest

        all没有目标文件有两个路径文件,于是会分别执行两个路径,其又作为目标文件有各自对应的路径,于是就有了两个可执行程序。

myshell.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#define NUM 1024
#define SIZE 32
#define SEP " "

//保存完整的命令行字符串
static char cmd_line[NUM];
//保存打散之后的命令行字符串
static char *g_argv[SIZE];
//利用缓冲区,将环境变量数据传递给子进程
static char g_myargv[64];

int main()
{
    while(1)
    {
        //1. 打印出提示信息 [qcr@我的系统 myshell]#
        printf("[qcr@我的系统 myshell]#");
        fflush(stdout);
        memset(cmd_line, '0', sizeof cmd_line);
        //2. 获取用户的键盘输入(如:输入的是各种指令和选项: "ls -a -l -i")
        if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
            continue;
        //意义:"-a -l -i\n\0"
        cmd_line[strlen(cmd_line) - 1] = '\0';
        g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串
        int index = 1;
        if(strcmp(g_argv[0], "ls") == 0)
            g_argv[index++] = "--color=auto";
        while(g_argv[index++] = strtok(NULL, SEP)); //第二次调用,如果还要解析原始字符串,传入NULL
        
        //4.内置命令
        if(strcmp(g_argv[0], "cd") == 0) //父进程执行
        {
            if(g_argv[1] != NULL) chdir(g_argv[1]);
            continue;
        }

        //在myshell中添加环境变量 - 是父进程上的行为,所以是内置命令
        //如:g_val[0] = export;g_val[1] = Hello="你好"
        if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL)
        {
            strcpy(g_myargv, g_argv[1]);
            int ret = putenv(g_myargv);
            if(ret == 0) printf("export success\n");
            continue;
        }
        
        
        //5.fork
        pid_t id = fork();
        if(id == 0)
        {
            //child
            printf("%s\n", getenv("Hello"));
            printf("下面功能让子进程进行的\n");
            execvp(g_argv[0], g_argv); // ls -a -l -i
            exit(1);
        }
        //father
        int status = 0;
        pid_t ret = waitpid(id, &status, 0); //阻塞等待
        if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));
    }
    return 0;
}
//利用缓冲区,将环境变量数据传递给子进程
static char g_myargv[64];

//在myshell中添加环境变量 - 是父进程上的行为,所以是内置命令
//如:g_val[0] = export;g_val[1] = Hello="你好"
if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL)
{
    strcpy(g_myargv, g_argv[1]);
    int ret = putenv(g_myargv);
    if(ret == 0) printf("export success\n");
    continue;
}

复习:

        环境变量表中,存储的是环境变量字符串的地址,通过使用字符串地址的方式找到环境变量。

        此处所写的myshell是用while为核心的,而在经过export环境变量操作后continue,将会进行下一次while,而上一个while的环境变量的实际存储位置是在g_argv中,再次循环会因为命令的输入而刷新掉实际环境变量的数据。于是环境变量表中存储的地址就会变为野指针。所以我们需要写一个全局的变量,进行拷贝存放。

mytest.c

#include <stdio.h>
#include <stdlib.h>
int main()
{
    printf("Hello=%s\n",getenv("Hello"));
    return 0;
}

        用于检验我们所创建的shell是否export了环境变量。因为由程序mytest在myshell中运行,是属于myshell的子进程,而环境变量具有全局属性,所以test会继承myshell环境变量。

融汇贯通的理解:
        在环境变量时,有所谓的环境变量以及本地变量,有export的是环境变量,无export的是本地变量,环境变量具有全局属性,能被子进程继承。本地变量不能被子进程继承,而在此处我们可以更加深刻的理解。

        进程程序替换本质上:是将磁盘中的一个程序全部执行起来,其实是一个加载器的角色,而当它加载的时候,我们可以手动的导入我们自定义的环境变量,也可以使用默认的环境变量(父进程的继承给子进程)


shell执行命令的方式通常有两种:

  1. 第三方提供的对应的在磁盘中有具体的二进制文件的可执行程序(由子进程执行)。
  2. shell内部,自己实现的方法,由自己(父进程)来进行执行。 (因为有一些命令就是要影响到shell本身,如:cd,export)

        从myshell的角度更可以直接的解释,程序替换的必要性,一定是和应用场景有关的,有时候就是需要子进程去执行一个全新的程序。

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

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

相关文章

IO初识233

绝对路径和相对路径 路径是用来描述一个文件在电脑上的具体位置。 这里的 E:\绘画合集\CCE展会logo 2.0就是绝对路径 目录之间的分隔符可以用\也可以用/来表示 相对路径就是以一个基准路径&#xff08;工作路径&#xff09;&#xff0c;以基准路径为起点往下走要怎么表示目标…

Java字符串训练

Java字符串训练一、用户登录二、统计字符次数三、拼接字符串1. 使用String2. 使用StringBuilder四、字符串反转五、金额转换六、手机号屏蔽七、身份证信息查看八、敏感词替换九、对称字符串十、数字转罗马数字十一、调整字符串十二、打乱字符串一、用户登录 需求&#xff1a;已…

MySQL监控(一):了解SigNoz

1.SigNoz介绍 github SigNoz SigNoz官方文档 2022 年 11 大 MYSQL 监控工具 MySQL | 六个最常用的 MySQL 数据库监控工具 2.SigNoz安装 从官方文档上得知使用以下命令进行安装&#xff1a; git clone -b main https://github.com/SigNoz/signoz.git && cd signoz/d…

SpringSecurity(十三)【授权】

十三、授权 什么是授权权限管理核心概念Spring Security 权限管理策略基于 URL 地址的权限管理基于方法的权限管理实战 权限管理 身份认证&#xff0c;就是判断一个用户是否为合法用户的处理过程。SpringSecurity中支持多种不同方式的认证&#xff0c;但是无论开发者使用那种方…

【uniapp】uniapp使用高德地图定位打包成安卓app的一些记录,比如打包后定位失效、

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、创建你的uniapp1.打开Dcloud开发者后台2.下载你的证书、获取你的SHA1安全码、证书私钥密码二、打开高德开放平台申请key1.打开官网2.创建一个应用三、在unia…

快速傅里叶变换FFT和逆变换的python编程

0. 预备知识 快速傅里叶变换旨在解决离散傅里叶变换DFT计算量大效率低的问题。当我们想要抑制噪声提取出某段信号中的有效信息时&#xff0c;如系统模型辨识或者是使用高精度力传感器测量人体腕部寸关尺脉搏信号这类应用&#xff0c;应该如何设计采样流程&#xff1f; 首先&a…

《通讯录》思路及代码实现详解

目录 一、通讯录功能实现的详细描述 二、通讯录的代码及思路实现 2、1 定义联系人结构体 2、2 初始化就结构体与释放动态开辟空间的实现 2、3 菜单打印 2、4 添加联系人信息 2、5 删除联系人信息 2、6 查询联系人信息 2、7 修改联系人信息 2、8 打印所有联系人信息 2、9 排序整…

75. 序列模型的代码实现

1. 训练 在了解了上述统计工具后&#xff0c;让我们在实践中尝试一下&#xff01; 首先&#xff0c;我们生成一些数据&#xff1a;(使用正弦函数和一些可加性噪声来生成序列数据&#xff0c; 时间步为 1,2,…,1000 。) %matplotlib inline import torch from torch import nn…

新手nvm npm 卸载不用依赖包,项识别为 cmdlet、函数、脚本文件,等命令集合

nvm安装包&#xff1a;Releases coreybutler/nvm-windows GitHub下载ta就不用单独下载node了注意:vnm安装位置尽量不要动C:\Users\Administrator\AppData\Roaming\nvm\settings.txt增加下面代码node_mirror: https://npm.taobao.org/mirrors/node/ npm_mirror: https://npm.t…

java+Springboot交通事故档案管理系统

系统分为用户和管理员两个角色 用户的主要功能有&#xff1a; 1.用户注册和登陆系统 2.用户查看警察相关信息 3.用户查看我的相关事故信息&#xff0c;可以对交通事故进行交通申诉 4.用户查看交通申诉审核信息 5.退出登陆 管理员的主要功能有&#xff1a; 1.管理员输入账户登陆…

Metasploit渗透框架介绍及永恒之蓝复现

Metasploit渗透框架介绍及永恒之蓝复现一、Metasploit渗透框架介绍1.1 名词解释1.2 MSF简介1.3 MSF框架结构1.4 MSF命令汇总1.4.1 常用命令1.4.2 基本命令1.4.3 Exploits模块1.4.4 漏洞名称规则1.5 MSF模块介绍1.5.1 auxiliary(辅助模块)1.5.2 exploits(漏洞利用模块)1.5.3 pay…

Open3D 泊松盘网格采样(Python版本)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 在图形的许多应用中,特别是在渲染中,从蓝色噪声分布生成样本是很重要的。然而,现有的有效技术不容易推广到二维以外。不过泊松盘采样是个例外,它允许在O(N)时间内生成泊松盘样本,而且该方法很容易在任意维度上…

分布式CAP和BASE理论学习笔记

参考至&#xff1a;https://blog.csdn.net/solihawk/article/details/124442443 1. CAP理论 CAP理论是计算机科学家Eric Brewer在2000年提出的理论猜想&#xff0c;在2002年被证明并成为分布式计算领域公认的定理&#xff0c;其理论的基本观念是&#xff0c;在分布式系统中不…

加密算法 AES和RSA

一&#xff0c;加密&#xff08;一&#xff09;加密基础&#xff1f;通过互联网发送数据&#xff0c;数据可能会被第三者恶意窃听&#xff0c;造成损失。因此需要给重要的数据进行加密&#xff0c;加密后的数据被称为“密文”。接收方通过解除加密或得原本的数据&#xff0c;把…

人工智能卷积算法

文章目录前言数字信号处理与卷积运算卷积公式与计算过程边缘卷积计算与0填充NumPy卷积函数二维矩阵卷积计算图像卷积应用实例总结前言 卷积运算实际上是一种常见的数学方法&#xff0c;与加法&#xff0c;乘法等运算类似&#xff0c;都是由两个输入的到一个输出。不同的是&…

迷宫问题---数据结构实践作业

迷宫问题—数据结构实践作业 ✅作者简介&#xff1a;大家好,我是新小白2022&#xff0c;让我们一起学习&#xff0c;共同进步吧&#x1f3c6; &#x1f4c3;个人主页&#xff1a;新小白2022的CSDN博客 &#x1f525;系列专栏&#xff1a;算法与数据结构 &#x1f496;如果觉得博…

什么是HAL库和标准库,区别在哪里?

参考文章https://blog.csdn.net/u012846795/article/details/122227823 参考文章 https://zhuanlan.zhihu.com/p/581798453 STM32的三种开发方式 通常新手在入门STM32的时候&#xff0c;首先都要先选择一种要用的开发方式&#xff0c;不同的开发方式会导致你编程的架构是完全…

Java 面向对象程序设计 消息、继承与多态实验 课程设计研究报告

代码&#xff1a;Java计算机课程设计面向对象程序设计对战游戏SwingGUI界面-Java文档类资源-CSDN文库 一、课程设计内容 一个游戏中有多种角色(Character)&#xff0c;例如&#xff1a;国王&#xff08;King&#xff09;、皇后&#xff08;Queen&#xff09;、骑士&#xff0…

【Linux多线程】

Linux多线程Linux线程概念什么是线程线程的优点线程的缺点线程异常线程用途Linux进程VS线程进程和线程进程的多个线程共享Linux线程控制POSIX线程库线程创建线程等待线程终止分离线程线程ID及进程地址空间布局Linux线程概念 什么是线程 在一个程序里的一个执行路线就叫做线程…

JavaScript 如何正确的分析报错信息

文章目录前言一、报错类型1.控制台报错2.终端报错二、错误追查总结前言 摸爬滚打了这么长时间…总结了一些排查错误的经验, 总的来说, 这是一篇JavaScript新手向文章. 里面会有些不那么系统性的, 呃, 知识? 一、报错类型 报错信息该怎么看, 怎么根据信息快速的追查错误. 1.…