【Linux】进程控制 — 进程程序替换 + 实现简易shell

news2024/11/18 12:44:27

文章目录

  • 📖 前言
  • 1. 进程程序替换
    • 1.1 程序替换的概念:
    • 1.2 为什么要程序替换:
    • 1.3 程序替换的原理:
  • 2. 六个exec替换函数
    • 2.1 execl函数:
    • 2.2 execv函数:
    • 2.3 execlp函数:
    • 2.4 execvp函数:
    • 2.5 execle函数:
    • 2.6 execvpe函数:
  • 3. 实现简易版shell
    • 3.1 内建命令等问题的解决:
      • 3.1 - 1 cd命令的处理:
      • 3.2 - 2 export的处理:

📖 前言

上一节我们讲了进程终止和进程等待等一系列问题,并做了相应的验证,本章将继续对进程控制进行学习,我们将学习进程程序替换,进行相关验证,运用系统进程程序替换接口,自己模拟写一个shell,该shell能够实现执行指令,等一系列命令行操作……


1. 进程程序替换

1.1 程序替换的概念:

概念引入:

将可执行程序加载到内存,并且重新调整子进程的页表映射,使之指向新的进程的代码和数据段,这种过程就叫做程序替换。

子进程执行的是父进程的代码片段,那么如果我们想让创建出来的子进程,执行全新的程序呢?

此时就要用到:进程的程序替换。

1.2 为什么要程序替换:

原因:

  • 原因是我们想让我们的子进程执行一个全新的程序。
  • 不同语言写的功能互相调用,这就是为什么要有程序替换的原因。

我们一般在服务器设计(Linux编程)的时候,往往需要子进程干两件种类事情:

    • 1.让子进程执行父进程的代码片段(服务器代码)
    • 2.让子进程执行磁盘中一个全新的程序(shell, 想让客户端执行对应的程序,通过我们的进程,执行其他人写的进程代码等等)C/C++ -> C/C++/Python/Shell/Php/Java…

1.3 程序替换的原理:

程序替换的原理:

  • 将磁盘中的程序,加载入内存结构。
  • 重新建立页表映射,谁执行程序替换,就重新建立子进程的映射关系。

效果:让我们的父进程和子进程彻底分离,并让子进程执行一个全新的程序!

父进程的映射关系:

在这里插入图片描述
程序替换之后,子进程的映射关系:

在这里插入图片描述
调整子进程的页表,让其不再与父进程代码和数据有任何关系,而是指向自己的代码和自己的数据区。

注意:

  • 请问:这个过程有没有创建新的讲程呢?
  • —— 没有!!!

小结:

  • 说白了就是让fork创建子进程,不想让子进程执行父进程代码片段。
  • 我们想让子进程执行磁盘当中全新的程序,而且我们没有创建新的进程。
  • 因为子进程的内核数据结构基本没变,只是重新建立了虚拟到物理的映射关系罢了。
  • 包括子进程的PID都不变,压根就没有创建新的进程,只不过让新的进程执行了不同的程序罢了。

2. 六个exec替换函数

上述我们讲了什么是程序替换,下面就要来见见猪跑了。
程序替换是由操作系统完成的,调用系统调用接口来完成操作。

  • 我们如果想执行一个全新的程序,我们需要做几件事情:
  • 先找到这个程序在哪里? —— 程序在那里
  • 程序可能携带选项进行执行(也可以不携带) —— 怎么执行

明确告诉OS,我想怎么执行这个程序是什么,要不要带选项。
我们平时在命令行中敲的指令都是一个一个可执行程序。

  • 程序替换的是子进程:(重点)
  • 进程替换永远影响的是进程的本身,子进程的替换永远不会影响父进程,因为进程具有独立性。
  • 独立性体现在内核层面,不同进程有不同的地址空间,有不同的页表替换只是加入新的代码和数据。
  • 重新建立的是页表映射但并不影响内核数据结构的具体情况。
  • 子进程虽然和父进程代码共享数据写实拷贝,但是一旦发生进程替换了,就认为代码和数据发生了双写实拷贝,就彻底将两个进程分开了。
  • 所以引入子进程的原因就是,一方面把需求做到位,另一方面不影响父进程,因为父进程可能还要接收新的命令,再去执行新的程序。

六个exec替换函数:

在这里插入图片描述

2.1 execl函数:

int execl(const char *path, const char *arg, ...);

path:

  • 这个是路径,可执行程序的路径。

arg:

  • 命令行怎么写(1s -1 -a), 这个参数就怎么填"ls",“-l”,“-a”,最后必须是NULL结尾
  • 标识 “如何执行程序的” 参数传递完毕

… :

  • 可变参数,可以传多个参数

第一个参数是解决了,程序在哪里的问题,第二个参数往后所有的参数,解决的都是程序如何执行的问题。

代码演示:

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

int main()
{
    //让我的程序执行系统上的: ls -a -i这样的一个命令
    printf("我是一个进程,我的pid是 : %d\n", getpid());

    //int ret = execl("/usr/bin/ls", "ls", "-l", "-a", NULL); //带选项
    //execl("/usr/bin/top", "top", NULL); //不带选项
    //execl("/usr/bin/which", "which", "pwd", NULL); //不带选项

    //下面这行代码没有打印出来
    //一旦代码执行到这里,必然是进程替换失败了
    
    //替换失败的情况
    int ret = execl("/usr/bin/lsssss", "ls", "-l", "-a", NULL); //带选项
    printf("我执行完毕了,我的pid : %d, ret = %d\n", getpid(), ret);

    return 0;
}

一旦进程替换成功了,就不会再执行程序替换函数以后的代码了,因为直接去是该进程被替换掉了。

在这里插入图片描述
显而易见,代码中程序替换以后的打印内容并没有显示出来,说明进程替换以后的代码压根就没执行,而是去执行ls进程了。

总结:

  • 一旦替换成功,是将当前进程的代码和数据全部替换了!!
  • 前一个printf被执行是因为程序替换并没有执行。
  • 所以替换上面的代码依旧是当前进程执行执行,execl之后代码就不复存在了。

所以程序替换不用判断返回值:

不需要返回值,一旦有值返回那么必然是返回失败了!!!因为只要成功了,就不会有返回值,而失败的时候,必然会继续向后执行!!最多通过返回值得到什么原因导致的替换失败!

引入进程创建:

  • 子进程执行程序替换,会不会影响父进程呢??
    • 不会(因为进程具有独立性)
  • 为什么,如何做到的??
    • 数据层面发生写时拷贝!
    • 当程序替换的时候,我们可以理解成为,代码和数据都发生了写时拷贝完成父子的分离!

在这里插入图片描述)

2.2 execv函数:

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

实现的功能和execl一模一样。

path:

  • 这个是路径,可执行程序的路径。

argv[]:

  • 如何执行,和execl的唯一区别就是传参方式的不一样

在这里插入图片描述
代码演示:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main()
{
    printf("我是父进程,我的pid是:%d\n", getpid());
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        //我们要子进程执行全新的程序,以前我们是子进程执行父进程的代码片段
        
        printf("我是子进程,我的pid是:%d\n", getpid());
        //char* const argv_[] = {
        //    (char*)"ls",
        //    (char*)"-l",
        //    (char*)"-a",
        //    (char*)"-i",
        //    NULL
        //};
        
        char* const argv_[] = {
            (char*)"top",
            NULL 
        };

       //execv("/usr/bin/ls", argv_);

       execv("/usr/bin/top", argv_);   
    }

    //一定是父进程
    int status = 0;
    int ret = waitpid(id, &status, 0);
    if(ret == id)
    {
        sleep(2);
        printf("进程等待成功!\n");
    }

    return 0;
}

程序替换不仅可以替换成指令,还可以替换成我们自己写的可执行程序。

  • 用exec系列,这种系统级的函数,可以把任何语言耦合到一起。
  • 任何程序都可以用系统级接口调用其他语言的。
  • 所以说操作系统是所有技术的基座。

2.3 execlp函数:

int execlp(const char *file, const char *arg, ...);

file:

  • 你想执行什么程序。 —— 找到它
  • 执行指令的时候,默认的搜索路径,在哪里搜索呢?—— 环境变量PATH
  • 命名带p的,可以不带路径,只说出你要执行哪一个程序即可!

arg:

  • 想如何执行它

代码演示:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

//有时候不想让父进程做一件事,只想让子进程做一件事
//将进程创建引入进来

int main()
{
    printf("我是父进程,我的pid是:%d\n", getpid());
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        //我们要子进程执行全新的程序,以前我们是子进程执行父进程的代码片段
        
        printf("我是子进程,我的pid是:%d\n", getpid());

        execlp("ls", "ls", "-a", "-l", "-i", NULL);//这里出现了两个ls,含义一样吗?-- 不一样!
        //第一个参数是供系统去找要执行谁的指令,后面一坨是表示如何执行该指令

        exit(100); //只要执行了exit,就意味着,execl系列的函数失败了 -- 进程替换失败了
    }

    //一定是父进程
    int status = 0;
    int ret = waitpid(id, &status, 0);
    if(ret == id)
    {
        sleep(2);
        printf("wait success, ret : %d, 我所等待子进程的退出码: %d, 退出信号是: %d\n", ret, (status >> 8) & 0xFF, status & 0x7F);
    }

    return 0;
}

作用和execI和execv是一样的,也是执行一个新的程序。

2.4 execvp函数:

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

file:

  • PATH找,只要程序名即可。

argv[]:

  • 如何执行,将命令行参数字符串,统一放入数组中即可完成调用!

在这里插入图片描述

2.5 execle函数:

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

envp[]:

  • 环境变量

execle:test.c程序替换代码演示:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main()
{
    //环境变量的指针声明
    extern char** environ;

    printf("我是父进程,我的pid是:%d\n", getpid());
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        //我们要子进程执行全新的程序,以前我们是子进程执行父进程的代码片段
        
        printf("我是子进程,我的pid是:%d\n", getpid());

        //绝对路径
        //execl("/home/Zh_Ser/linux/lesson16/mycmd", "mycmd", NULL);
        
        //相对路径
        //execl("./mycmd", "mycmd", NULL);
        
        //我们来手动导入一个环境变量
        char* const env_[] = {
            (char*)"MYPATH=You Can See Me!!",
            NULL 
        };

        //e: 添加环境变量给目标进程,是覆盖式的!
        //execle("./mycmd", "mycmd", NULL, env_);
        
        //execle("/usr/bin/ls", "ls", NULL, env_);

        execle("./mycmd", "mycmd", NULL, environ);

        exit(100); //只要执行了exit,就意味着,execl系列的函数失败了 -- 进程替换失败了
    }

    //一定是父进程
    int status = 0;
    int ret = waitpid(id, &status, 0);
    if(ret == id)
    {
        sleep(2);
        printf("进程等待成功!\n");
    }

    return 0;
}

上述代码代码在程序替换的时候,执行了./mycmd,目的是手动导入环境变量的时候,执行./mycmd获取导入的环境变量。

mycmd.cpp代码演示:

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

using namespace std;

int main()
{
    extern char** environ;

    cout << "打印环境变量" << endl;
    for (int i = 0; environ[i]; i++)
    {
        printf("%d: %s\n", i, environ[i]);
    }

    cout << "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
    //根据环境变量名,获取环境变量的内容
    cout << "PATH:" << getenv("PATH") << endl;
    cout << "----------------------------------------------" << endl;
    cout << "MYPATH:" << getenv("MYPATH") << endl;
    cout << "----------------------------------------------" << endl;
    
    //程序崩溃了 -- 因为环境变量里根本就没有MYPATH  
    
    cout << "Hello World!" << endl;
    cout << "Hello World!" << endl;
    cout << "Hello World!" << endl;
    cout << "Hello World!" << endl;

    return 0;
}

mycmd是为了获取环境变量。

如果我们用这种方式导入环境变量:

在这里插入图片描述
出现的情况:

在这里插入图片描述
将mycmd.cpp中的getenv(“PATH”)给屏蔽掉,再执行test结果就可以将MYPATH打印出来:

在这里插入图片描述
e: 添加环境变量给目标进程,是覆盖式的!所以环境变量只剩下MYPATHT。

子进程会继承父进程的环境变量的!!(重点)

  • 子进程会继承父进程的环境变量,当父进程调用fork()创建子进程时,子进程会继承父进程的所有环境变量
  • 当子进程调用execlp()等函数执行其他程序时,子进程也会继承父进程的环境变量。
  • 如果需要在子进程中更改环境变量,可以使用setenv()或putenv()等函数进行更改。
  • 但是,更改的环境变量只会影响当前进程和它的子进程,并不会影响父进程或其他进程的环境变量。

如何理解覆盖?(重点)

  • 当子进程调用execle()函数替换自己的程序时,可以传递一个新的环境变量数组,以覆盖子进程继承的父进程的环境变量。如果不传递新的环境变量数组,子进程会继承父进程的环境变量。因此,如果在调用execle()函数时没有传递新的环境变量数组,子进程的环境变量不会被覆盖。
  • 如果传递了新的环境变量数组,则子进程的环境变量将被替换为新的环境变量数组中的值。这可能会导致子进程无法访问父进程中的一些环境变量,除非在新的环境变量数组中显式地包含它们。

验证execle覆盖了子进程会继承父进程的环境变量:

  • 我们执行test程序的时候
  • 调用execle接口,程序替换去执行mytest
  • 既然mytest是替换了子进程,它就会继承父进程的全部环境变量
  • execle函数我们传了一个env_将子进程的环境变量覆盖了

在这里插入图片描述
我们在mycmd程序开始的地方,加了查看全部环境变量的代码:

extern char** environ;

for (int i = 0; environ[i]; i++)
{
	printf("%d: %s\n", i, environ[i]);
}

目的是通过该代码查看子进程(mycmd)的环境变量,被execle传的env_覆盖之后的样子:

在这里插入图片描述
显而易见,子进程的环境变量只有env_[]的内容了!!!所以getenv("PATH")才获取不到!!!

正确做法:

我们将全部环境变量传过去,将environ传过去。

在这里插入图片描述


补充:(重点)

  • ls 是一个常见的系统命令,它通常位于系统的某个标准路径(如 /bin 或 /usr/bin)。即使 PATH 为空,execlp() 会检查这些标准路径,找到 ls 的可执行文件并执行它。
  • 可能是直接在execlp中定义好的路径了,所以 PATH 环境变量没了也可以找到。

详细说明:

  • 如果PATH环境变量为空,execlp()函数会无法在环境变量中查找可执行文件的路径。但是,execlp()函数会检查一些默认路径,例如/bin、/usr/bin等,来查找可执行文件。因此,即使PATH为空execlp()函数也可能会在这些默认路径中找到可执行文件并执行它
  • 但是,如果在默认路径中也找不到可执行文件,则execlp()函数会执行失败,并将errno设置为ENOENT,表示无法找到可执行文件。因此,如果需要执行特定路径下的可执行文件,最好使用execv()或execve()等函数,并指定可执行文件的完整路径。这样可以避免依赖PATH环境变量来查找可执行文件的路径。

验证:

  • 我们在在mycmd程序中再进行程序替换
  • 用execlp函数第一个参数是在PATH路径下找的可执行文件
  • mycmd进程的环境变量中只有一句话(只有MYPATH了)
  • 但是我们照样可以在mycmd中进行程序替换执行出ls的结果!!

在这里插入图片描述
即使我们将父进程中的PATH给改了,命令行中都用不了ls,execlp照样可以找到ls并执行它。

在这里插入图片描述

2.6 execvpe函数:

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

有了上面的基础这个想必就不用再啰嗦了,只是第二个参数传的不同,这里传的是一个指针数组。

在这里插入图片描述
这些函数原型看起来很容易混,但只要掌握了规律:

  • l (list) : 表示参数采用列表
  • v (vector) : 参数用数组
  • p (path) : 有p自动搜索环境变量PATH
  • e (env) : 表示自己维护环境变量

为什么有那么多的接口?

  • 目的是:适配应用场景
  • 其实上述函数都是对系统接口的封装

严格意义来说不是系统接口,是基于系统接口之上的封装。

真正意义上的系统接口:

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

上述6个函数在执行时都会调用execve()函数,将参数列表和环境变量数组转换为execve()函数所需的格式,并调用execve()函数来执行可执行文件。因此,execve()函数是这些函数的底层实现。


3. 实现简易版shell

只要我们懂得了程序替换的原理,会用程序替换的接口,就很好理解:

  • shell本身执行起来就是个死循环
  • 我们命令行就是去执行其他程序
  • shell创建子进程,将子进程给替换掉就ok了
  • 过程中要获取输入指令等操作…

myshell代码实现:

#include <stdio.h>
#include <string.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define SEP " "
#define NUM 1024
#define SIZE 128

char command_line[NUM];
char* command_args[SIZE];

char env_buffer[NUM];

extern char** environ;

//对应上层的内建命令
int ChangeDir(const char* new_path)
{
    chdir(new_path);

    return 0;//调用成功
}

void PutEnvInMyShell(char* new_env)
{
    putenv(new_env);
}

int main()
{
    //shell本质就是一个死循环
    
    while(1)
    {
        //不关心获取这些属性的接口,搜索一下都有
        
        //1.显示提示符
        printf("[用户名@我的主机名 当前目录]# ");
        fflush(stdout);

        //2.获取用户输入
        memset(command_line, '\0', sizeof(command_line));

        //从键盘获取,标准输入,stdin,获取到的是C风格的字符串(stdio.h结尾的),'\0'结尾
        fgets(command_line, NUM, stdin);
        command_line[strlen(command_line) - 1] = '\0';//清空\n回车
        //printf("%s\n", command_line);

        //3. "ls -a -l -i" -> "ls" "-a" "-l" "-i" 字符串切分 -- 因为这些参数一定得以列表或者数组方式传递给程序替换接口
        //shell必须切分,因为必须调用execl函数
        
        //将第一个字符串地址用0号下标指向,第二个字符串地址用1号下标指向 
        command_args[0] = strtok(command_line, SEP);

        int index = 1;

        //给ls命令添加颜色: 如果提取出来的程序名是ls -- 1下标设置成改颜色的
        if(strcmp(command_args[0], "ls") == 0) command_args[index++] = (char*)"--color=auto";

        //strtok截取成功返回字符串起始地址
        //截取失败,返回NULL
        while(command_args[index++] = strtok(NULL, SEP));
        
        //for debug
        //int i = 0;
        //for(i = 0; i < index; i++)
        //{
        //    printf("%d : %s\n", i, command_args[i]);
        //}
        
        //4.TODO -- 编写后面的逻辑,内建命令(由父Shell自己实现的自己调用的一个函数)
        if(strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL)
        {
            //让调用方进行路径切换,父进程
            ChangeDir(command_args[1]);
            continue;
        }
        
        //走到这里一定是将命令行参数解析完了,包括命令 + 选项
        
        //将环境变量的信息导入在了父进程的上下文当中
        if(strcmp(command_args[0], "export") == 0 && command_args[1] != NULL)
        {
            //环境变量列表(是个指针数组,每个元素是个指针指向一个环境变量)
            //我们传的是一个字符串首地址,但是环境变量的内容还是我们自己维护的
            //目前,环境变量信息在comman_line,会被清空,那么环境变量当然就没有了
            //所以此处我们需要自己保存一下环境变量的内容
            
            strcpy(env_buffer, command_args[1]);
            PutEnvInMyShell(env_buffer);
            //PutEnvInMyShell(command_args[1]);//MYENV=112233
            continue;
        }

        //5.创建进程,执行
        //如果自己直接程序替换的话,就把自己写的shell给替换了
        
        pid_t id = fork();
        if(id == 0)
        {
            //子进程
            //6.程序替换
           
            //execvpe(command_args[0], command_args, environ);
            execvp(command_args[0], command_args);

            exit(1);//执行到这里,子进程一定替换失败了
        }

        int status = 0;
        
        pid_t ret = waitpid(id, &status, 0);

        if(ret > 0)
        {
            printf("等待子进程成功: sig: %d, code: %d\n", status & 0x007F, (status & 0xFF00) >> 8);
        }

    }//end while

    return 0;
}

ls设置颜色的办法:
在这里插入图片描述

3.1 内建命令等问题的解决:

3.1 - 1 cd命令的处理:

在命令行中操作cd时,会跳转路径,但是用绝对命令时,就不行了,还是原来的路径:

  • 一个进程也存在对应路径, 进程对应的路径可以理解成
  • 当进程启动的时候时,在哪个路径启动时,这个进程所在路径就是当前进程所启动的路径

一般一个进程的路径是会被于进程继承的,路径的变化我们希望的是父进程路径的变化。

在这里插入图片描述
原因就是,我们平时用的cd时做过处理的cd:

  • 我们知道指令都是一些可执行程序
  • 执行可执行程序就是去执行其他程序,程序替换了
  • 如果我们创建的子进程跳转路径
  • 子进程退出之后,只是子进程的路径跳转了
  • 并不影响父进程的路径,会发现命令行路径还是没变
  • 显然这这种做法是不可取的

就不能用程序替换的方式来执行一些特殊的命令了:

  • 我们可以在父进程将一些命令单独处理
  • 让其不进行程序替换

重点:

  • 程序替换影响的是子进程和父进程没关系,子进程一 跑就完了,曾经所有的操作就没有意义,路径切换就没意义了,所以我们要让父进程的路径发生变化。
  • 如果有些行为,是必须让父进程shell执行的,不想让子进程执行,绝对不能创建子进程!只能是父进程自己实现对应的代码!

内建命令:

  • 我们把由父进程自己提供的代码或者提供的逻辑(在命令行上体现的也是一个命令),但是这部分命令不是子进程执行的,而是父进程自己执行的,我们叫做内建命令。
  • 由shell自己执行的命令,我们称之为内建(内置bind- in)命令。

更改工作目录的函数:

在这里插入图片描述
验证一下:
在这里插入图片描述

3.2 - 2 export的处理:

导入环境变量:

在这里插入图片描述
export不是一个可执行程序和cd,ls,cat等指令不同:

export是一个shell内置命令,用于设置环境变量。它并不是一个可执行程序,而是由shell解释器直接执行的命令。当我们在shell中使用export命令时,它会将指定的环境变量设置为当前shell进程的环境变量,以便后续的命令或程序可以使用该环境变量。

所以用execvp进行程序替换的时候,是不能替换成功的!

在这里插入图片描述
注意:

  • 环境变量列表(是个指针数组,每个元素是个指针指向一个环境变量)
  • 我们传的是一个字符串首地址,但是环境变量的内容还是我们自己维护的
  • 目前,环境变量信息在comman_line,会被清空,那么环境变量当然就没有了
  • 所以此处我们需要自己保存一下环境变量的内容

环境变量是数据,进程替换不是替换进程的代码和数据吗?

  • 但是环境变量是属于系统的数据
  • 子进程在执行程序替换时
  • 当前进程的环境变量数据,不会被替换掉
  • 而且是以父进程为模版继承下来的
  • 所以才会让父进程以内建命令的方式putenv,子进程就能直接获取了

环境变量的数据,在进程的上下文中:

  1. 环境变量会被子进程继承下去,所以他会有全局属性。
  2. 当我们进行程序替换的时候,当前进程的环境变量非但不会被替换,而且是继承父进程的!!

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

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

相关文章

chatgpt赋能python:Python[:2]——简介和应用

Python [:2]——简介和应用 Python [:2]是一种流行的编程语言&#xff0c;其简单易用的语法使其成为许多人的首选编程语言之一。Python [:2]的迅速增长已经超越了其他编程语言&#xff0c;并且它正在成为各行各业中最有前途的编程语言之一。 Python 基础 Python [:2]的语法非…

MSQL系列(三) Mysql实战-索引最左侧匹配原则原理

Mysql实战-索引最左侧匹配原则原理 前面我们讲解了索引的存储结构&#xff0c;我们知道了BTree的索引结构&#xff0c;索引的叶子节点是严格排序的&#xff0c;就像你看到的 底层叶子节点 15->18->20->30->49->50等等 这样做有什么好处呢&#xff1f; 这就引出…

利用qsort排序

一、简单排序10个元素的一维数组 #define _CRT_SECURE_NO_WARNINGS #pragma warning(disable:6031) #include<stdio.h> #include<stdlib.h> void print_arr(int arr[], int sz) {int i 0;for (i 0; i < sz; i){printf("%d ", arr[i]);}printf("…

WMS服务启动

WMS服务启动 1、SystemServer.java#startOtherServices(t)中启动2、WindowManagerService.java#main创建初始化3、简易时序图4、相关线程 1、SystemServer.java#startOtherServices(t)中启动 WMS属于SystemServer启动众多的系统服务中的一个&#xff0c;WindowManagerService中…

社会工程学技术框架解读

社会工程学技术其实就是利用各种心理进行技术上的欺骗。 尽管许多社会工程学大师都是无师自通,依赖自己的天赋悟性、聪明才智和临场应变能力不断演绎着社会工程学艺术,然而,社会工程学仍然具有一些通用的技术流程与共性特征。Social-Engineer 网站创始人克里斯哈德纳吉对其加…

Spring Cloud Alibaba 快速上手搭建公司项目(二)Nacos

Nacos(全称为&#xff1a;阿里巴巴开源项目 - 命名服务 & 配置中心)是阿里巴巴集团开源的一个动态服务发现、配置管理和服务管理平台。它提供了一种简单易用的方式来管理和监控微服务应用程序中的服务实例、配置和元数据。 Nacos是一个高度可扩展的平台&#xff0c;支持多…

chatgpt赋能python:Python中的[::-1]操作:反转列表、元组和字符串

Python中的[::-1]操作&#xff1a;反转列表、元组和字符串 在Python编程中&#xff0c;[::-1]是一个相当常用的操作符&#xff0c;它可以对列表、元组、字符串等序列类型进行反转。本文将详细介绍这个操作符的语法和使用方法&#xff0c;并且为您提供一些在实际应用中的例子。…

css浮动特性

1. 传统网页的三种布局方式 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"wid…

类和对象【3】初始化列表

全文目录 引言初始化列表定义特性 总结 引言 上一篇文章中介绍了构造函数&#xff0c;它可以在实例化一个类对象的时候自动调用&#xff0c;以初始化类对象&#xff1a; 戳我看默认成员函数详解 但是&#xff0c;不难发现&#xff0c;在构造函数体中对成员变量的初始化其实是属…

武汉环保门禁电子台账视频监控系统

武汉环保门禁电子台账视频监控系统&#xff0c;是顺应国家政策需求&#xff0c;基于视频监控、环保门禁系统、物联技术&#xff0c;结合大数据和人工智能等技术手段&#xff0c;对汽车排放单位进行环境管理的一套综合系统。 系统介绍 该系统实现对机动车排放检测的监管&#…

Android 读取本地数据进行本地开发

前言 在日常开发当中&#xff0c;API接口还没有部署&#xff0c;但是UI已经出来了&#xff0c;这时候往往都会使用本地数据进行功能界面的搭建&#xff0c;这样往往能很大程度节约开发时间&#xff0c;工具类拿来直接用&#xff0c;话不多说&#xff0c;开整 一、项目搭建 1…

Web应用技术(第十四周/持续更新)

本次练习基于how2j和课本,初步认识Spring。 以后我每周只写一篇Web的博客&#xff0c;所以的作业内容会在这篇博客中持续更新。。。 一、Spring基础1.Spring概述:2.Sring组成&#xff1a;3.BeanFactory&#xff1a;4.控制反转&#xff1a;5.依赖注入&#xff1a;6.JavaBean与S…

学习Java可以从事什么岗位(合集)

学习Java可以从事什么岗位 学习Java可以从事的岗位 Java可以做网站 Java可以用来编写网站&#xff0c;现在很多大型网站都用Jsp写的&#xff0c;JSP全名Java Server Pages 它是一种动态网页技术&#xff0c;比如我们熟悉的163&#xff0c;一些政府网站都是采用JSP编写的。 所以…

MySQL小练习(使用JDBC操作数据库)

题目&#xff1a; 1.创建一个数据库(学号姓名缩写,如: 2020001zs)在数据库中创建一张表 (五个以上字段) ; 2.使用JDBC(使用PreparedStatement接口) 操作数据库对表中的数据进行增删改查操作 目录 一、数据库 1.创建数据库 2.创建表 3.添加数据 二、JDBC 1.准备环境 2.查询…

TCO-PEG-Thiol,反式环辛烯聚乙二醇巯基,具有末端硫醇基团的双功能TCO PEG衍生物

产品描述&#xff1a; TCO PEG Thiol是具有末端硫醇基团的双功能TCO PEG衍生物。TCO&#xff08;反式环辛烯&#xff09;基团与四嗪基团快速有效地反应&#xff0c;而硫醇&#xff08;巯基&#xff09;可用于与马来酰亚胺反应&#xff0c;与金表面结合并参与许多其他反应。 TC…

DOTA PSMA,1702967-37-0,PSMA-617,特异性膜抗原 (PSMA) 的强有效抑制剂

产品描述&#xff1a; DOTA-PSMA是Prostate特异性膜抗原 (PSMA) 的强有效抑制剂&#xff0c;其 Ki 值为 0.37 nM。DOTA-PSMA由三种成分组成:药效基团Glutamate-urea-Lysine&#xff0c;螯合剂DOTA&#xff08;能够结合68Ga或177Lu&#xff09;&#xff0c;以及连接这两个实体的…

sftp配置免密以及权限配置

场景&#xff1a;机器A通过sftp免密登录机器B 机器A有用户redis、 nginx, 机器B有用户monitor、 bak用户 需求&#xff1a;机器A在nginx用户环境下&#xff0c;sftp机器B的bak目录 注意&#xff1a;因为sshd为了安全&#xff0c;对属主的目录和文件权限有所要求。如果权限…

[LitCTF 2023]ssvvgg(Steghide爆破)

题目是一张.svg的图片 关于SVG的简介&#xff1a; SVG格式文件是可缩放矢量图形文件的缩写&#xff0c;是一种标准的图形文件类型&#xff0c;用于在互联网上渲染二维图像。与其他流行的图像文件格式不同&#xff0c;SVG格式文件将图像存储为矢量&#xff0c;这是一种基于数学…

SpringCloud(27):授权控制实现

很多时候&#xff0c;我们需要根据调用来源来判断该次请求是否允许放行&#xff0c;这时候可以使用 Sentinel 的来源访问控制&#xff08;黑白名单控制&#xff09;的功能。来源访问控制根据资源的请求来源&#xff08;origin&#xff09;判断资源访问是否通过&#xff0c;若配…

运营-18.积分体系概念

积分体系是一种通过平台补贴来提升用户忠诚度、为平台各项业务的导流的运营手段&#xff1b; 作用 1. 积分体系可以引导用户逐渐投入沉没成本&#xff0c;包括时间、精力和金钱&#xff1b; 2. 沉没成本越高&#xff0c;用户越难以离开&#xff1b; 3. 积分体系可以给其他业务导…