LInux(四)进程控制(创建、终止、等待、替换)

news2024/9/22 4:13:03

目录

一、进程创建

1、pid_t fork(void)

 2、写时拷贝技术(父子进程间代码共享、数据独有)

3、vfork()--创建一个子进程

4、fork创建子进程流程是什么样的? 

5、一个关于fork的程序

6、程序a

7、 程序b

二、进程终止

1、在main函数中return                        仅在main函数中使用时 退出程序运行

2、库函数  void exit(int retval)                        在任意位置调用都会退出程序

3、系统调用接口  void _exit(int retval);          在任意位置调用都会退出程序

 4、进程退出返回值的意义

 5、进程的三种退出场景

 6、void perror(const char* msg);  出错打印错误信息

 7、const char* strerror(int errno);获取对应编号错误信息

三、进程等待

1、为什么要进行进程等待?

2、阻塞接口与非阻塞接口

3、pid_t wait(int *status)

4、pid_t  waitpid(pid_t pid, int *status, int options);

 5、使用wait与waitpid接口模拟阻塞等待

①、利用waitpid接口接收退出的pid为child_pid的子进程

②、假如随便等待一个pid为12345的进程

③、使用waitpid等待任意一个子进程退出

6、使用waitpid模拟非阻塞进程等待

 7、分清status与wait接口返回值

① 概念认知

② 代码举例

 ③ 随机取一个退出码,将上方exit(0) 替换为exit(99)

 ④ status的内层结构剖析

⑥ 使用257作为退出码(超出范围截断)

 ⑦ 获取statuts中的退出码与异常信号值

四、程序替换

1、概念理解与简单应用

 2、execve接口学习

 3、int execv(char* path, char* argv[])

4、execvp函数学习

5、不定参数替换函数


一、进程创建

1、pid_t fork(void)

关于LInux的第二篇博文就提到了fork()这个接口,这个接口的作用就是通过复制父进程来创建一个子进程,可以利用pit_t 的返回值来使用if判断来实现父子进程分流,返回值保存了子进程的pid

pit_t ret = fork();创建一个子进程

        ret == -1 创建失败

        ret == 0 子进程

        ret  >  0  父进程

再来剖析一下这段代码

  1 #include<stdio.h>                                                                          
  2 #include<unistd.h>
  3 
  4 int g_val = 10;
  5 int main()
  6 {
  7     int ret = fork();
  8     if(ret<0){
  9       printf("error fork\n");
 10     }else if(ret==0){
 11       g_val = 300;
 12       printf("i am child, my g_val = %d,my &g_val = %p\n",g_val,&g_val);
 13     }else{
 14       printf("i am parent, my g_val = %d,my &g_val = %p\n",g_val, &g_val);
 15     }
 16     return 0;
 17 }

 当子进程中没有对全局变量进行修改时,二者打印的数据以及地址都是一样的

 上图为定义一个全局变量g_val 父进程通过虚拟地址空间存放g_val,然后通过页表将虚拟地址映射到物理内存当中去,并在屋里内存中给变量g_val开辟一块空间存放值为10;

接着父进程调用fork()接口,创建了一个子进程,这个子进程的信息几乎和父进程一样,它复制了父进程的上下文数据、内存指针,以及虚拟地址空间存放g_val。形成了下方这个闲适恬淡的氛围

 可是这时候子进程开始了分裂割据,子进程希望拥有属于自己的g_val于是对g_val的值进行了修改

操作系统知道了它这个请求之后于是在物理内存中重新给它开辟了一块空间,这块空间中存放了子进程的g_val=300,于是子进程的虚拟地址空间在经过页表进行映射的时候就会指向新开辟的空间

 2、写时拷贝技术(父子进程间代码共享、数据独有)

子进程复制了父进程中大部分的信息,因此子进程有自己的变量,但是自己的变量经过页表映射后与父进程访问的是同一块物理内存,当这块内存空间中的数据将要修改,则给子进程重新开辟空间,并拷贝数据过去

每个进程都应该有它们自己的存储空间,这样才能互不影响(进程之间的独立性)

那么为什么不直接给子进程开辟一个空间呢?而是要通过这样当修改的时候才进行开辟?就像寒假作业不写,等老师开学了跟我要,我才开始写这个作业。

万一出现了给子进程开辟空间了,最后发现没有对子进程进行修改,那岂不是做无用功了,所以操作系统索性直接不给它开辟,等到它用的时候才进行开辟。

写时拷贝技术意义:主要是为了提升子进程的创建效率,避免不必要的内存消耗

那么如果是给父进程变量修改内容呢?

还是给子进程重新开辟空间,因为原本的这块空间就是人家父进程的

例子:malloc动态申请一块空间——其实只是先分配了一个虚拟地址(物理内存并没有直接被开辟),当第一次要修改空间数据时才会分配。

3、vfork()--创建一个子进程

(在fork实现了写时拷贝技术之后就少用了)

创建一个子进程、父子进程共用同一块虚拟地址

fork与vfork的区别:

fork创建进程之后,父子进程谁先运行不一定,看系统调度

但是vfork创建子进程,一定是子进程先运行,只有子进程退出或者程序替换之后父进程才继续运行

fork   父子进程代码共享数据独有

vfork  父子进程共用同一块虚拟地址

4、fork创建子进程流程是什么样的? 

5、一个关于fork的程序

 

 使用pstree命令查看

6、程序a

 

7、 程序b

 

不刷新缓冲区,所以当创建第二个进程的时候,第一个子进程的*也被保留下来

子进程复制父进程,复制了整个地址空间,所以地址空间中没有被刷新的数据也就保存了一份。

对于程序b有个知识点:程序在return 0 之前进行刷新缓冲区

加入了_exit(0) 退出程序并且不刷新缓冲区,结果就没有,说明程序在return的时候就会进行刷新缓冲区


二、进程终止

如何终止一个进程

1、在main函数中return                        仅在main函数中使用时 退出程序运行

        return是终止一个函数,并返回一个数据;

        main函数是程序的入口函数,入口函数一旦退出,程序运行就会终止

2、库函数  void exit(int retval)                        在任意位置调用都会退出程序

        #include<stdlib.h>

系统调用时操作系统向上层提供的用于访问内核的接口,功能结构都比较单一

大佬们针对典型场景,对系统调用接口进行封装,封装除了适用于典型场景的库函数

3、系统调用接口  void _exit(int retval);          在任意位置调用都会退出程序

        #include<unistd.h>

exit与_exit的区别就在于_exit不会刷新缓冲区,即不会将缓冲区中的数据进行刷新写入文件中。只有刷新了缓冲区,printf里面的数据才会被打印出来。

 4、进程退出返回值的意义

return以及exit给出的数据其实就是进程的退出码

(必须得有一种方式告诉我们这个任务完成的怎么样)

作用:进程的退出码就是表示当前进程任务处理的结果

 5、进程的三种退出场景

任务完美完成,正常退出;

任务没有完成,正常退出;

异常退出

 6、void perror(const char* msg);  出错打印错误信息

打印上一步系统调用接口使用失败的原因信息

在出错处理中是非常有用的,因为只有知道了为什么出错,才能知道如何改进。

  5 int main()
  6 {
  7     printf("hello\n");
  8     FILE *fp = fopen("tst.txt", "r");
  9     if(fp == NULL)
 10     {
 11       perror("fopen error");
 12       return -1;
 13     }
 14     return 0;                                                                              
 15 }


 7、const char* strerror(int errno);获取对应编号错误信息

根据错误编号,返回对应编号的字符串错误原因

下图:利用strerror打印对应编号的错误信息


三、进程等待

1、为什么要进行进程等待?

先来回忆一下什么是僵尸进程,僵尸进程就是子进程先于父进程退出,父进程没有获取子进程退出的返回值,导致子进程无法完全释放资源。而解决僵尸进程的方式一个就是直接退出父进程(kill或者exit(int  retval))另外一个就是进行进程等待。

进程等待作用:父进程在创建子进程之后,等待子进程退出,获取子进程的退出码,释放子进程的资源,避免出现僵尸进程

2、阻塞接口与非阻塞接口

阻塞接口:为了完成一个功能发起一个系统调用,但是这个调用完成条件不具备,则接口一直等待不返回,直到调用条件达成,才进行返回。(占用资源,但是具有及时性

非阻塞接口:当调用条件不具备的时候,立即报错返回(资源利用率高,不及时)

3、pid_t wait(int *status)

wait是一个阻塞接口,功能是等待当前调用者的任意一个子进程退出(如果已经有退出的直接处理),获取返回值,释放资源

status 参数是一个int空间的地址,用于向指定空间中存放子进程的退出返回码

返回值:成功返回处理的退出子进程的pid;  失败返回-1;

  1 #include<stdio.h>
  2 #include<unistd.h> //fork接口
  3 #include<sys/wait.h> // wait/waitpid接口
  4 #include<stdlib.h>  // exit()
  5 
  6 int main()
  7 {
  8     int ret = fork();
  9       if(ret<0){
 10         perror("fork error");
 11         return -1;
 12       }else if(ret == 0){
 13         exit(0); // 子进程直接退出了
 14       }
 15       int status;
 16       ret = wait(&status);//已经有子进程退出,则直接处理,子进程没有退出则会一直等待(阻塞)
 17       if(ret!=-1){
 18         printf("%d子进程退出了, 退出返回值为%d\n",ret,status);                           
 19       }
 20       return 0;
 21 }

在wait接收之前先进行一个sleep看一看进程信息

 15       sleep(6);
 16       int status;
 17       ret = wait(&status);
 18       if(ret!=-1){
 19         printf("%d子进程退出了, 退出返回值为%d\n",ret,status);
 20       }
 21       sleep(1000000);                                                                    

 

僵尸子进程33203退出了


4、pid_t  waitpid(pid_t pid, int *status, int options);

      waitpid接口既可以等待任意一个子进程退出,也可以等待指定的子进程退出

              pid参数:  >0 则表示等待指定pid的子进程退出;  -1表示等待任意一个子进程退出

      waitpid接口既可以表示阻塞等待,也可以使用非阻塞等待

                options参数: 0 表示默认阻塞等待            

                                  WNOHANG-设置为非阻塞(当前没有子进程退出则会立即报错返回)

      返回值:成功则返回处理的退出子进程的pid,  若没有子进程退出则返回0;出错返回-1;

 5、使用wait与waitpid接口模拟阻塞等待

①、利用waitpid接口接收退出的pid为child_pid的子进程

  1 #include<stdio.h>
  2 #include<unistd.h> //fork接口
  3 #include<sys/wait.h> // wait/waitpid接口
  4 #include<stdlib.h>  // exit()
  5 
  6 int main()
  7 {
  8       pid_t child_pid = fork();
 12       if(child_pid == 0){
 13         exit(0); // 子进程直接退出
 14       }
 15       sleep(6);
 16       int status;
 17       //ret = wait(&status); // wait接口
 18       int ret = waitpid(child_pid, &status, 0);
 19       if(ret<0){
 20         perror("waitpid error");
 21         return -1;
 22       }
 23       printf("%d子进程退出了,退出返回值为%d\n",ret, status);                            
 24       return 0;
 25 }

②、假如随便等待一个pid为12345的进程

无法找到pid为12345的进程,出错返回-1,并使用perror打印出错信息

 18       int ret = waitpid(12345, &status, 0);
 19       if(ret<0){                                                                         
 20         perror("waitpid error");
 21         return -1;
 22       }
 23       printf("%d子进程退出了,退出返回值为%d\n",ret, status);


③、使用waitpid等待任意一个子进程退出

代码 waitpit(-1, &status, 0)  与 wait(&status) 等价,均表示等待任意一个子进程退出

 18       int ret = waitpid(-1, &status, 0);                                                 
 19       if(ret<0){
 20         perror("waitpid error");
 21         return -1;
 22       }

6、使用waitpid模拟非阻塞进程等待

  6 int main()
  7 {
  8       pid_t child_ret = fork();
 12       if(child_ret == 0){
 13         sleep(6);// 6秒之后子进程退出
 14         exit(0);
 15       }
 16       int ret, status;                  //第三个参数使用WNOHANG
 17       while((ret = waitpid(-1, &status, WNOHANG)) == 0) 
 18       {
 19         printf("现在waitpid的返回值为0,所以目前没有子进程退出,2秒之后再次检测\n");     
 20         sleep(2);// 休眠2秒之后再次进行结果判断
 21       }
 22       if(ret<0){
 23         perror("waitpid error");
 24       }
 25       printf("%d子进程退出了,退出返回值为%d\n",ret, status);
 26       return 0;
 27 }

 注意需要while循环的使用,对于waitpid接口返回值的循环检测

如果while循环内部选择每次休眠5秒之后再进行检测,子进程在第6秒就退出了,而检测却在第10秒才检测出来,这个就体现了非阻塞等待的不及时性。

 7、分清status与wait接口返回值

① 概念认知

wait接口的返回值,当进程等待成功了返回处理的退出子进程pid,为一个大于0的值。

status是一个int类型的空间,用来存放子进程的退出码

② 代码举例

下方这个代码中,子进程使用exit(0)接口退出,exit(int retval);中retval就是退出码,然后这个retval也就存储在这个status中,打印出来也是0.

  6 int main()
  7 {
  8     pid_t child_pid = fork();
  9     if(child_pid == 0){
 10         sleep(2);
 11         exit(0);// 使用exit接口退出子进程,退出码为
 12     }
 13     int status;
 14     int ret = wait(&status);
 15     if(ret != -1){
 16         printf("%d子进程退出了,返回码为%d\n", ret, status);    
 17     }
 18     return 0;
 19 }

 ③ 随机取一个退出码,将上方exit(0) 替换为exit(99)

 这时我们发现,返回码竟然是这样一个数25344????打开计算器输入99与25344发现他们的16进制都有一个63??  25344--> 0x6300, 99--> 0x0063 

 ④ status的内层结构剖析

 status是一个int类型的数据,一共有四个字节32个比特位,而其高16位我们不关心。

而其中低16位中的高8位是用来保存进程退出码,使用一个字节进行保存,如果超过这1个字节的数据就会进行截断。(0~255)

异常信号值是用低7位来存储的进程时异常退出原因,0表示正常退出,非0表示异常退出。

有了上面的知识,那么99理解过来也就是,首先程序正常退出,所以6300后俩位为0,然后99转化为63存储在这个保存进程退出码的这一个字节中。

⑥ 使用257作为退出码(超出范围截断)

        exit(257);// 使用exit接口退出子进程,退出码为257

使用257作为退出码,最后返回的是256?

 上面说了存储退出码的只有一个字节空间,超出的就会截断,那么这一个字节中只有257最后那个1了,转为16进制就是1,然后在加上status中的后8位0就是 0x100  -->256就是上面编译器中出现的256.

 ⑦ 获取statuts中的退出码与异常信号值

status中低7位为异常信号值  可以通过 与运算 来获取这低七位  (status & 0x7f)

上面说过异常信号值低16位中的高八位为进程退出码(status>>8)& 0xff

int ret = wait(&status);
if((status&0x7f) == 0)
{
    printf("正常退出,%d子进程退出了,退出返回值为%d\n",ret, (status>>8)&0xff);                                                
}


四、程序替换

1、概念理解与简单应用

替换掉一个pcb所描述的要调度管理的程序,替换成另外一个程序

简单理解:将一个程序加载到内存中,然后修改当前pcb进程的页表映射信息,刷新虚拟地址空间,让其管理新程序。

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6 
  7   printf("这是我代码的开始\n");
  8   execlp("ls","ls","-1",NULL);                                                                                                     
  9   printf("这是我代码的结束\n");
 10   return 0;
 11 }

 发现并没有打印我的下一个printf内容,只是执行了ls -l的命令。这就是程序替换

execlp命令使原来虚拟内存映射到原先代码的物理内存转移到映射ls命令的代码中去了。

 2、execve接口学习

        int execve(char* path, char* argv[], char* env[]);

        功能:让path这个路径的程序加载到内存中,然后让系统调度运行这个程序。

        而程序运行有可能会有运行参数与环境变量

                argv用于设定运行参数           env用来设定环境变量

   失败:返回-1        替换成功没有返回值(因为替换成功就去执行新程序了)

 下面这个代码A就是之前关于main函数的参数的代码,打印结果横线上层为运行参数,下方为环境变量。

int main(int argc, char* argv[], char* env[])
{
    for(int i = 0; argv[i]!=NULL; i++)
    {
      printf("argv[%d]=[%s]\n",i,argv[i]);
    }
    printf("-----------------------------------------------------------------\n");
    for(int i = 0; env[i]!=NULL; i++)
    {
      printf("env[%d]=[%s]\n",i, env[i]);
    }
    return 0;
}

 这俩段代码在同一个文件下,所以path就直接给出了./argc以达到使用argc来替换当前程序,并给出argv与env参数

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

int main()
{
    printf("这是我程序的开始\n");
    //execve(char* path, char* argv[], char* env[]);
    char* argv[] = {"hello","-o","-p",NULL};
    char* env[] = {"myval=1000","open=100","word=999",NULL};
    execve("./argc", argv, env);
    printf("这是我程序的结尾\n");
    return 0;
}

 3、int execv(char* path, char* argv[])

少了一个参数环境变量参数,默认使用PATH环境变量中找。

int main(int argc, char* argv[], char* env[])
{
    printf("begin\n");
    char* argv1[] = {"ls","-l",NULL};
    execv("/bin/ls", argv1);
    printf("end\n");
    return 0;
}

4、execvp函数学习

是基于execve系统调用接口封装来的一种库函数 ,它的第一个参数不需要指定路径,默认会在PATH环境变量下找

它更方便于进行使用系统指定命令进行程序替换,该命令只需要file文件名和argv运行参数 这俩个参数,它会自动查询当前环境变量中的系统库命令路径下的命令进行程序替换

 使用execvp库函数(头文件unistd.h)调用ls程序替换当前程序

 execvp("ls", argvv);    ==》  execve("/bin/ls", argv, env-->main函数第三个参数)

这三个是等价的均打印下面文件信息内容 

    char* argv1[] = {"ls","-l",NULL};

    execve("/bin/ls", argv1, env);        默认手动设置        系统调用接口
    execv("/bin/ls", argv1);        少了一个环境变量        基于上方接口封装出来的库函数
    execvp("ls",argv1);
        不需要指定路径        进一步封装

int main(int argc, char* argv[], char* env[])
{
    printf("这是我程序的开始\n");
    char* argv1[] = {"ls","-l",NULL};
    execve("/bin/ls", argv1, env);
    execv("/bin/ls", argv1);
    execvp("ls",argv);
    printf("这是我程序的结束");
    return 0;
}

5、不定参数替换函数

        int  execl("path", "ls","-l");

execl("/bin/ls", "ls", "-l", NULL);

        int execlp("file", "ls", "-l");

execlp("ls", "ls", "-l", NULL);

        int execle("path",  "ls", "-l", env)

后面有p就说明不需要指定路径与环境变量,会默认在PATH里面找,直接使用命名名即可

后面有e就表示需要手动设置环境变量

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

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

相关文章

E2PROM 蓝桥杯 stm32 AT24C02读写函数

本文 代码使用 HAL 库 。 文章目录前言一、AT24C02 读写函数&#xff1a;1. 读函数2. 写函数&#xff1a;二. AT24C02 读写地址&#xff0c;原理图三. 延时问题总结下一节讲解 MCP4017。使用 E2PROM 来计算板子的上电次数 实验效果。前言 本文 先列出 AT24C02 读写函数 &#…

【蓝桥杯】简单数论3——素数

1、素数判断 素数定义:只能被1和自己整除的正整数。注:1不是素数&#xff0c;最小素数是2。 判断一个数n是不是素数:当n≤时&#xff0c;用试除法;n>时&#xff0c;试除法不够用&#xff0c;需要用高级算法&#xff0c;例如Miller_Rabin算法。 试除法&#xff1a;用[2, n…

MySQL —— 库操作

目录 一、库操作 1. 创建数据库 2. 创建数据库案例 二、字符集和校验规则 1. 查看系统默认字符集以及校验规则 2. 查看数据库支持的字符集 3. 查看数据库支持的字符集校验规则 4. 校验规则对数据库的影响 三、操纵数据库 1. 查看数据库 2. 显示创建语句 3. 修改数…

Pandas 数据可视化

数据可视化Joyful Pandas基本绘图动手学数据分析2 第二章&#xff1a;数据可视化2.7 如何让人一眼看懂你的数据&#xff1f;2.7.1 任务一2.7.2 任务二2.7.3 任务三2.7.4 任务四2.7.5 任务五2.7.6 任务六2.7.7 任务七Joyful Pandas Datawhale社区 Joyful Pandas 基本绘图 一维…

《深入浅出计算机组成原理》学习笔记 Day9

乘法器1. 顺序乘法2. 并行加速方法3. 电路并行参考1. 顺序乘法 以 13913 \times 9139为例&#xff0c;131011012,9101001213_{10} 1101_2, 9_{10} 1001_21310​11012​,910​10012​。用列竖式的方式计算&#xff1a; 在二进制下&#xff0c;由于只有 0 和 1&#xff0c;计…

逆序对问题、树状数组与离散化

目录 一、前言 二、逆序对问题 1、暴力法 2、归并排序法 3、树状数组与逆序对&#xff08;最巧妙最好的方法&#xff09; &#xff08;1&#xff09;倒序 &#xff08;2&#xff09;正序 &#xff08;3&#xff09;离散化 三、例题 1、小朋友排队&#xff08;lanqiao…

springBoot——SpringBoot 整合 Mybatis 流程

SpringBoot 整合持久层SpringBoot 整合 Mybatis 流程一、添加依赖1. pom.xml二、写配置文件1. application.yml三、写 Java 文件1. entity2. dao3. service4. controller5. mapper四、建立数据库五、测试SpringBoot 整合 Mybatis-Plus 流程一、添加依赖1. pom.xml二、写配置文件…

linux基本功系列之chattr命令

文章目录一. chattr命令介绍二. 语法格式及常用选项三. 参考案例3.1 给指定文件添加隐藏属性&#xff0c;阻止文件被修改&#xff1a;3.2 撤销i属性3.3 允许补充&#xff08;追加&#xff09;内容&#xff0c;无法覆盖/删除内容总结前言&#x1f680;&#x1f680;&#x1f680…

LabVIEW监控操纵杆,键盘或鼠标

LabVIEW监控操纵杆&#xff0c;键盘或鼠标在LabVIEW中从操纵杆/鼠标/键盘获取数据在LabVIEW中监控我的输入设备如何知道键盘上按了哪些键&#xff1f;用轮询的方式监控鼠标&#xff0c;键盘或操纵杆的动作。要在LabVIEW中监控这些设备&#xff0c;可以使用内置的输入设备采集VI…

【STM32】寄存器原理

如果我们想要深入去学习STM32单片机的存储原理和方式&#xff0c;就要花时间去了解STM32单片机有关寄存器的基本原理 单片机型号&#xff1a;正点原子STM32F103ZET6 目录 寄存器的定义 寄存器分类 存储器映射 寄存器映射 通过地址访问控制单元运作 通过定义宏来取代直接访…

C++ 哈希表查询_进入哈希函数结界的世界

1. 前言 哈希表或称为散列表&#xff0c;是一种常见的、使用频率非常高的数据存储方案。 哈希表属于抽象数据结构&#xff0c;需要开发者按哈希表数据结构的存储要求进行 API 定制&#xff0c;对于大部分高级语言而言&#xff0c;都会提供已经实现好的、可直接使用的 API&…

基于PythonOpenCv的视频图像处理

博主简介 博主是一名大二学生&#xff0c;主攻人工智能研究。感谢让我们在CSDN相遇&#xff0c;博主致力于在这里分享关于人工智能&#xff0c;c&#xff0c;Python&#xff0c;爬虫等方面知识的分享。 如果有需要的小伙伴可以关注博主&#xff0c;博主会继续更新的&#xff0c…

【教程】虚拟环境与Pytorch安装保姆级教学

【教程】虚拟环境与Pytorch安装保姆级教学NVIDIA驱动安装虚拟环境创建激活/退出相关库的安装Pycharm内设置虚拟环境Pytorch安装安装地址可能遇到的问题处理报错安装卡顿测试是否安装完成参考NVIDIA驱动安装 NVIDIA驱动可在官网进行安装&#xff1a;NVIDIA驱动官网 命令行输入…

一文上手决策树:从理论到实战

一、基础概念 决策树是一类极为常用的机器学习方法&#xff0c;尤其是在分类场景。决策树通过树形结构来递归地将样本分割到不同的叶子结点中去&#xff0c;并根据每个叶子结点中的样本构成对该结点中的样本进行分类。 我们可以从两个视角来理解决策树模型。 第一种视角是将…

Python副业技术总结,手把手教你用宝塔面板部署Django程序

前言 最近写了几个Django项目&#xff0c;写完以后怎么让对方测试成了问题&#xff0c;因为之前都是自己在本地写的练习项目&#xff0c;对于部署这一块很陌生&#xff0c;不知道怎么操作&#xff0c;内心很忐忑。没办法&#xff0c;只能硬着头皮上&#xff0c;一边百度&#…

17种编程语言实现排序算法-插入排序

开源地址 https://gitee.com/lblbc/simple-works/tree/master/sort/ 覆盖语言&#xff1a;C、C、C#、Java、Kotlin、Dart、Go、JavaScript(JS)、TypeScript(TS)、ArkTS、swift、PHP。 覆盖平台&#xff1a;安卓(Java、Kotlin)、iOS(SwiftUI)、Flutter(Dart)、Window桌面(C#)、…

ARP渗透与攻防(四)之WireShark截获用户数据

ARP-WireShark截获用户数据 系列文章 ARP渗透与攻防(一)之ARP原理 ARP渗透与攻防(二)之断网攻击 ARP渗透与攻防(三)之流量分析 1.WireShark工具介绍 wireshark的官方下载网站&#xff1a; WireShark wireshark是非常流行的网络封包分析软件&#xff0c;功能十分强大。可以…

PowerShell 学习笔记:压缩、解压缩文件

在自动构建的时候&#xff0c;最常用的就是压缩备份项目的源文件&#xff0c;PowerShell提供了相关命令。Compress-Archive&#xff08;压缩文件&#xff09;Compress-Archive[-Path] <String[]>[-DestinationPath] <String>[-CompressionLevel <String>][-P…

Crossplane - 比 Terraform 更先进的云基础架构管理平台?

&#x1f449;️URL: https://crossplane.io/ &#x1f4dd;Description: 将云基础架构和服务组成自定义平台 API 简介 在 11 月的 KCD 上海现场&#xff0c;听了一场阿里云的工程师关于他们自己的多云基础架构管理工具的介绍&#xff0c;前边的引言部分有介绍到 Terraform&am…

连续系统的数字PID控制仿真-3

在连续系统的数字PID控制仿真-2的基础上&#xff0c;利用S函数实现PID离散控制器的Simulink仿真。在S函数中&#xff0c;采用初始化函数、更新函数和输出函数&#xff0c;即 mdlInitializeSizes函数、mdIUpdates函数和mdlOutputs函数。在初始化中采用sizes 结构&#xff0c;选择…