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

Linux进程控制(四)之进程程序替换

文章目录

    • 进程程序替换
      • 单进程版程序替换
      • 替换原理
      • 多进程版程序替换
      • 替换函数
      • 函数解释
        • 小知识
      • 命名理解

进程程序替换

如果要让子进程执行与父进程完全不同的代码,就要进行进程程序替换。

单进程版程序替换

执行一个可执行文件

image-20250316212914342

makefile

mycommand:mycommand.cgcc -o $@ $^ -std=c99
.PHONY:clean
clean:rm -rf mycommand    

mycommand.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());//这类方法的标准写法 execl("/usr/bin/ls","ls","-a","-l",NULL);//必须以NULL结尾                              printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());return 0;
}

image-20250316215554319

现象:after没有执行,把ls -a -l执行了,每个pid也不一样了。

mycommand.c

#include <stdio.h>  
#include <unistd.h>  
#include <stdlib.h>  int main()  
{  printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid()); //这类方法的标准写法   //execl("/usr/bin/ls","ls","-a","-l",NULL);//必须以NULL结尾  execl("/usr/bin/top","top",NULL);//必须以NULL结尾                                      printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());  return 0;                                               
}

image-20250316215936672

程序可以把系统里的命令封装起来,程序变成进程之后把命令跑起来。

替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),

子进程往往要调用一种exec函数以执行另一个程序。

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。

调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。


代码运行起来就是我们系统当中的一个进程,

创建新进程总会创建一个进程的PCB(task_struct),地址空间,页表等,

要把代码、数据加载到内存里,通过页表映射内存,

然后CPU根据PCB找到虚拟地址、页表映射代码和数据并执行。

以ls为例,ls用execl加载进内存,

直接把ls的代码替换原来的代码,

ls的数据直接替换原来的数据。(没有创建新进程!)

(如果ls代码数据加载到内存了,小了就新增空间,大了就释放空间)

把页表的右侧的映射地址改变一下(收缩一下)。

新加载的程序从main函数重新运行(从0开始执行)。

–程序替换!

image-20250312144644539

多进程版程序替换

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id=fork();if(id==0){printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());sleep(5);//这类方法的标准写法 execl("/usr/bin/ls","ls","-a","-l",NULL);//必须以NULL结尾printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);        }                   pid_t ret=waitpid(id,NULL,0);if(ret>0)                                                                         {                                                                                 printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);              }sleep(5);return 0;
}  

image-20250317195303420

在子进程sleep(5)的时候,父进程阻塞等待子进程退出。

image-20250317103348714

子进程在调用execl时,不会影响父进程。

原因:发生了写时拷贝,进程之间是相互独立的。

没有调用execl的时候,子进程只指向父进程的代码和数据,

当子进程执行execl时,ls代码数据替换进来的时候,

发生写时拷贝,不管是代码还是数据都有写时拷贝。

代码不一定是不可被写入的,用户直接写,操作系统会拦截崩溃,

但是execl是让操作系统写。


程序替换没有创建新进程,只进行代码和数据的程序替换。

父子进程的pid一直都没有变。

子进程结束前是2254,结束后还是2254。

所以,task_struct,mm_struct是没有被释放或者重新建立的。(字段可能会稍微修改)

替换函数

其实有六种以exec开头的函数,统称exec函数:

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

函数解释

after及之后的代码没有被执行,

原因:after及后面代码是在execl程序替换之后的,

原来的代码会被替换,所以,after及后面代码被ls的代码替换了,

所以后续代码不会被执行。

程序替换成功之后,exec* 后续的代码不会被执行。

程序替换失败了,才可能执行后续代码。

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。

如果调用出错则返回-1

所以exec* 函数只有失败的返回值而没有成功的返回值。

小知识

CPU如何得知程序的入口地址?

Linux中形成的可执行程序,是由格式(ELF)的,可执行程序的表头,可执行程序的入口地址就在表头!!!

可执行程序的表包括:代码段,数据段,只读数据区等,这些段区的地址在表头。

加载可执行程序时,代码和数据可以先不加载,

但是一定要先加载表头,每个数据区的开始的数字(start)就是在表头中来的。

当我们替换了一个新进程,新进程也有表头,CPU就可以在表头读到对应的可执行程序的入口。

可执行程序在编译的时候,就产生了一个STARTART的地址(程序入口),

编写到表头中,加载到内存时,CPU可以获取。

表头一方面初始化我们的地址空间、页表等,另一方面告诉CPU代码的程序入口在哪里。

命名理解

image-20250317204830649

l(list) : 表示参数采用列表

v(vector) : 参数用数组

p(path) : 有p自动搜索环境变量PATH

e(env) : 表示自己维护环境变量

image-20250312144703544

#include <unistd.h>`int execl(const char *path, const char *arg, ...);//库函数int execlp(const char *file, const char *arg, ...);int execle(const char *path, const char *arg, ...,char *const envp[]);int execv(const char *path, char *const argv[]);//系统调用接口int execvp(const char *file, char *const argv[]);

execl:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id=fork();if(id==0){printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());execl("/usr/bin/ls","ls","-a","-l",NULL);//必须以NULL结尾               printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);                                                   }                                                              pid_t ret=waitpid(id,NULL,0);                                  if(ret>0)                                                           {                                                                   printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);}                                                              return 0;                                                      
}

image-20250317212533917

传参的时候,从第二个参数开始,一个一个地传递,最后一个必须为NULL。

函数名带 ‘l’ 的,传参采用可变参数,而且传参时,一个一个地传。

从第二个参数开始,在命令行当中怎么写的,就依次怎么传递参数给程序,

空格改成逗号,最后加上NULL。

image-20250317210015346

要执行一个程序第一件事就是找到要执行的程序。

所有的exec* 第一个参数(const char *path) :决定如何找到该程序。

名字不带 ‘p’ 必须是全路径(绝对或者相对路径的方式找到要执行的程序)

第一个参数解决:在什么路径下找到该程序。

第二个参数解决:如何执行这个程序,要不要涵盖选项,涵盖哪些。

execlp:

PATH:execlp会在默认的PATH环境变量中查找。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id=fork();if(id==0){printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());//execlp("/usr/bin/ls","ls","-a","-l",NULL);execlp("ls","ls","-a","-l",NULL);             printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);                                                   }                                                              pid_t ret=waitpid(id,NULL,0);                                  if(ret>0)                                                           {                                                                   printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);}                                                              return 0;                                                      
}

可以带路径也可以不带路径。

image-20250317212554232

函数名中带了 ‘p’ 就不用带路径了,execlp会自动在环境变量中查找,

所有子进程都会继承父进程的环境变量列表,

当前的进程所有的环境变量都是从bash里来的,

bash里面本来就有path,就会被所有的子进程继承。(环境变量具有全局属性)

image-20250317213114436

ls在usr/bin目录下,找到之后执行。

有两个ls是因为:

第一个参数为了 找到程序,不仅要告诉execlp路径(路径由path解决),

而且要告诉execlp要执行什么程序。

第一个ls代表要执行谁,第二个ls代表要怎么执行。


execv:

char *const argv[] -> 字符串指针数组

第一个参数:怎么找到该程序。

第二个参数:如何执行这个程序。

image-20250317214644413

就是把可变参数的传参形式变成了指针数组的形式。

中间const表示:

一旦写进去了,指向的地址不能改变。(指针本身不能改)

内容可以改,"-a"等可以改。(指针指向的内容可以修改)

  #include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/wait.h>#include <sys/types.h>int main(){pid_t id=fork();if(id==0){char *const myargv[]={"ls","-a","-l",NULL};printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());execv("/usr/bin/ls",myargv);                           printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);                                                      }                                                          pid_t ret=waitpid(id,NULL,0);                                     if(ret>0)                                                         {                                                                 printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);}                                                    return 0;                                            }

image-20250317215242619

ls是一个程序,有main函数,main函数有命令行参数,命令行参数由execv系统调用,第二个参数传入的。

可变参数最终也要变成指针数组的形式,然后传入ls调用的main函数当中。

execv系统调用,系统获取的命令行参数,会把参数传递给ls的main函数。

在Linux当中,所有的进程都是别人的子进程,

在命令行当中,所有的进程都是bash的子进程。

所以,所有的进程在启动的时候都是采用exec*系列函数来启动执行的。

程序替换在单进程当中,是把对应的可执行程序的代码和数据加载到内存当中,

为当前进程开辟空间等,然后把自己的代码数据加载进内存。

所以,exec*系列函数 - 起到了加载器的作用。(代码级别的加载器)

exec*函数把磁盘当中的可执行程序加载到内存中。

所以,exec*里会存在诸如内存申请、外设访问等动作。

exec把可执行程序导入到内存里,可以获得命令行参数,

所以execv就可以直接调用ls的main函数时,把argv参数传递给程序。

所有的函数都是压栈,在调用main函数之前先形成一个简单的栈帧结构,

把argv的地址push进去,构造一个main函数被调用的上下文,就可以把argv传入。

所以我们是可以把命令行参数传递给可执行程序的。


execvp:

  #include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/wait.h>#include <sys/types.h>int main(){pid_t id=fork();if(id==0){char *const myargv[]={"ls","-a","-l",NULL};printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());execvp("ls",myargv);                           printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);                                                      }                                                          pid_t ret=waitpid(id,NULL,0);                                     if(ret>0)                                                         {                                                                 printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);}                                                    return 0;                                            }

image-20250317223505942


exec*能够执行系统命令,那么也能执行我们自己的命令

一个c语言程序调用一个C++程序,两个可执行代码。

#include <stdio.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <sys/wait.h>  
#include <sys/types.h>  
int main()  
{  pid_t id=fork();  if(id==0)  {printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());  execl("./otherexe","otherexe",NULL);printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);}pid_t ret=waitpid(id,NULL,0);if(ret>0){printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);}return 0;
} 

image-20250318134328091

第一个参数代表要执行的文件在哪,执行谁。

第二个参数代表怎么执行。

execl("./otherexe","otherexe",NULL);
execl("./otherexe","./otherexe",NULL);

以上两个都可以跑,结果一样。

命令行带"./"是因为要告诉bash可执行程序在哪。

但是execl的第一个参数已经说明了可执行程序的路径,所以第二个参数可以不用写"./"了。


用c语言调用其他的语言

c语言调用sh

test.sh

#!usr/bin/bashfunction myfun()
{cnt=1while [ $cnt -le 10 ]doecho "Hello $cnt"let cnt++done
}
echo "Hello Linux!"
echo "Hello Linux!"
echo "Hello Linux!"ls -a -lmyfun     

所有的脚本语言都以"#!"开头,后面跟着脚本语言对应的解释器。

脚本语言并不是脚本在跑,而是由解释器来解释式执行的。

image-20250318135937945

mycommand.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id=fork();if(id==0){printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());execl("/usr/bin/bash","bash","test.sh",NULL);printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);                                                    }                                                               pid_t ret=waitpid(id,NULL,0);                                   if(ret>0)                                                       {                                                                   printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);}                                                               return 0;                                                       
}                                                                   

在命令行上,要执行的可执行文件不是脚本文件,而是脚本文件的解释器。

image-20250318140323807


用c语言调用py

  1 #!/usr/bin/python3                                                              2                                                                                 3 print("Hello py!")      

image-20250318140720684

mycommand.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id=fork();if(id==0){printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());execl("/usr/bin/python3","python3","test.py",NULL);printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);                                                }                                                           pid_t ret=waitpid(id,NULL,0);                               if(ret>0)                                                   {                                                                   printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);}                                                           return 0;                                                   
}

py


无论是可执行程序,还是脚本语言,为什么能跨语言调用?

所有语言运行起来,本质都是进程!

只要是进程就可以被调用。

基本上所有的语言都有execl等的接口。

补充:

C++文件名后缀包括: .cc .cpp .cxx

image-20250318131641351

image-20250318131736720

otherex.cc

#include <iostream>
using namespace std;
int main()
{cout<<"Hello C++ Linux!"<<endl;cout<<"Hello C++ Linux!"<<endl;cout<<"Hello C++ Linux!"<<endl;return 0;
}

image-20250318131817628

makefile

mycommand:mycommand.cgcc -o $@ $^ -std=c99
otherexe:otherexe.cppg++ -o $@ $^ -std=c++11
.PHONY:clean                     
clean:                       rm -rf mycommand otherexe

image-20250318132144981

为什么只形成mycommand呢

在makefile中,自上往下的扫描,遇到的第一个文件就是目标文件,所以只执行目标文件的方法。

哪个目标文件在前就执行哪个依赖方法。

.PHONY:all
all:otherexe mycommandmycommand:mycommand.c      gcc -o $@ $^ -std=c99  
otherexe:otherexe.cpp      g++ -o $@ $^ -std=c++11  
.PHONY:clean                 
clean:                       rm -rf mycommand otherexe

在makefile自顶向下扫描时,遇到的第一个目标文件是伪目标all

all又依赖于otherexe和mycommand,

所以就先形成otherexe,再形成mycommand

all没有依赖方法,所以关系推导完之后,就不执行了。

image-20250318133012474


execle:

我们可以在我们编写的代码里获取命令行参数和环境变量!

可以验证mycommand给otherexe传入命令行参数和环境变量

mycommand一个程序形成的环境变量如何导给另一个程序otherexe?

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h> 
int main(){pid_t id=fork();if(id==0){printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());char*const myargv[]={       "otherexe","-a","-b","-c",NULL};execv("./otherexe",myargv);printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);}pid_t ret=waitpid(id,NULL,0);if(ret>0){printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);}return 0;}

会把myargv作为参数传递给otherexe,otherexe就可以拿到对应的参数了。

  #include <iostream>using namespace std;int main(int argc,char *argv[]){cout<<argv[0]<<" begin running"<<endl;for(int i=0;argv[i];i++){cout<<i<<" : "<<argv[i]<<endl;}cout<<argv[0]<<" stop running"<<endl;return 0;                           }     

image-20250319101028134

所以,exec所对应的参数就传入了。

  #include <iostream>using namespace std;int main(int argc,char *argv[],char*env[]){cout<<argv[0]<<" begin running"<<endl;cout<<"这是命令行参数:"<<endl;for(int i=0;argv[i];i++){cout<<i<<" : "<<argv[i]<<endl;}cout<<"这是环境变量:"<<endl;for(int i=0;env[i];i++){cout<<i<<" : "<<env[i]<<endl;}cout<<argv[0]<<" stop running"<<endl;return 0;}

image-20250319101532527

命令行参数和环境变量都有!

在默认情况下,尽管没有传环境变量,但是子进程自动获取(继承)环境变量。

环境变量也是数据,在地址空间里是有命令行参数和环境变量列表的,

创建子进程的时候,环境变量就已经被子进程继承下去了。

extern char**environ 这个第三方变量直接指向进程的环境变量信息。

这个变量已经被父进程初始化,指向自己的环境变量表了。

这个变量拷贝的时候,也被子进程继承下去了。

不通过传参方式,在程序地址空间里也可以获得环境变量和命令行参数。

因为子进程会继承父进程的地址空间、页表等,所以命令行参数和环境变量就可以被继承。

程序替换只替换了代码和数据,环境变量信息不会被替换!


想给子进程传递环境变量,如何传递?

1.新增环境变量

给父进程导入新的环境变量,就会被子进程继承下去。

在Shell里新增环境变量

image-20250319103853403

image-20250319104003883

环境变量信息不随着进程替换而被替换,只会随着系统一路的被子进程获取。

bash -> mycommand ->otherexe 环境变量具有全局属性。

但如果只想在mycommand父进程中新增环境变量导入传递给otherexe呢?

putenv 添加一个环境变量 添加到调用进程的上下文。

image-20250319104540716

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h> 
int main(){putenv("MY_ENV=6666666666666");pid_t id=fork();if(id==0){printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());           char*const myargv[]={"otherexe","-a","-b","-c",NULL};execv("./otherexe",myargv);printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);}pid_t ret=waitpid(id,NULL,0);if(ret>0){printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);}return 0;
}

image-20250319105047262

image-20250319105208178

putenv可以导入属于自己和自己的子进程的环境变量。

所以mycommand导入新环境变量与bash(父进程)没关系!

如果非得传

image-20250319105543484

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h> 
int main(){extern char** environ;pid_t id=fork();if(id==0){printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());           execle("./otherexe","otherexe","-a","-w",NULL,environ);printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);}pid_t ret=waitpid(id,NULL,0);if(ret>0){printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);}return 0;}

image-20250319105927772

可以把环境变量交给子进程,那也可以把整型、字符串交给子进程。

可以通过子进程继承父进程的数据,并且不修改,这样就是共享的方式去传,

也可以通过环境变量去传。

2.彻底替换

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h> 
int main()
{pid_t id=fork();if(id==0){          printf("before,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());char *const myenv[]={"MYVAL=123666","MYlll=567999",NULL};execle("./otherexe","otherexe","-a","-w",NULL,myenv);printf("After,I am a process,pid:%d,ppid:%d\n",getpid(),getppid());exit(0);}pid_t ret=waitpid(id,NULL,0);if(ret>0){printf("wait successfully,father pid:%d,ret:%d\n",getpid(),ret);}return 0;}

image-20250319110731196

当我们传入我们自己编写的环境变量时,采用的策略是覆盖,而不是追加。

如果不传入环境变量,子进程在被调用的时候,操作系统会把父进程的环境变量传给子进程。

如果有写相应的参数,操作系统就传对应的参数,如果没,就使用系统默认的。


事实上,只有execve是真正的系统调用,其它六个函数最终都调用 execve来完成程序替换。

所以execve在man手册 第2节–系统调用,

其它函数在man手册第3节–库函数。

区别:只是传参的不同。

image-20250319150732378

这些函数之间的关系如下图所示。

下图exec函数族 一个完整的例子:

image-20250312144752856

相关文章:

Linux进程控制(四)之进程程序替换

文章目录 进程程序替换单进程版程序替换替换原理多进程版程序替换替换函数函数解释小知识 命名理解 进程程序替换 如果要让子进程执行与父进程完全不同的代码&#xff0c;就要进行进程程序替换。 单进程版程序替换 执行一个可执行文件 makefile mycommand:mycommand.cgcc -…...

python-selenium 爬虫 由易到难

本质 python第三方库 selenium 空值 浏览器驱动 浏览器驱动控制浏览器 推荐 edge 浏览器驱动&#xff08;不容易遇到版本或者兼容性的问题&#xff09; 驱动下载网址&#xff1a;链接: link 1、实战1 &#xff08;1&#xff09;安装 selenium 库 pip install selenium&#…...

希尔排序

希尔排序是一种改进的插入排序算法&#xff0c;它通过将原始数据分成多个子序列来改善插入排序的性能&#xff0c;每个子序列的元素间隔为 d&#xff08;增量&#xff09;。随着算法的进行&#xff0c;d 逐渐减小&#xff0c;最终减为 1&#xff0c;此时整个序列就被排序好了。…...

Pydantic Mixin:构建可组合的验证系统体系

title: Pydantic Mixin:构建可组合的验证系统体系 date: 2025/3/22 updated: 2025/3/22 author: cmdragon excerpt: Pydantic的Mixin模式通过继承组合实现校验逻辑复用,遵循以Mixin后缀命名、不定义初始化方法等设计原则。支持基础校验模块化封装与多策略组合,如电话号码…...

策略模式 vs. 工厂模式:对比与分析

相同点 解耦思想 两者都通过接口/抽象类将实现与调用方解耦&#xff0c;降低模块间的直接依赖。 符合开闭原则 新增策略或产品时&#xff0c;只需扩展新类&#xff0c;无需修改已有代码。 封装变化 策略模式封装算法的变化&#xff0c;工厂模式封装对象创建的变化。 不同…...

RK3568 I2C底层驱动详解

前提须知&#xff1a;I2C协议不懂的话就去看之前的内容吧&#xff0c;这个文章需要读者一定的基础。 RK3568 I2C 简介 RK3568 支持 6 个独立 I2C: I2C0、I2C1、I2C2、I2C3、I2C4、I2C5。I2C 控制器支持以下特性: ① 兼容 i2c 总线 ② AMBA APB 从接口 ③ 支持 I2C 总线主模式…...

【大语言模型_8】vllm启动的模型通过fastapi封装增加api-key验证

背景&#xff1a; vllm推理框架启动模型不具备api-key验证。需借助fastapi可以实现该功能 代码实现&#xff1a; rom fastapi import FastAPI, Header, HTTPException, Request,Response import httpx import logging# 创建 FastAPI 应用 app FastAPI() logging.basicConfig(…...

hadoop-HDFS操作

1. 使用的是hadoop的用户登录到系统&#xff0c;那么 cd ~ 是跳转到/home/hadoop下。 2. 在操作hdfs时&#xff0c;需要在hadoop用户下的/usr/local/hadoop&#xff0c;此时是在根目录下。 cd /usr/local/hadoop或者cd / cd usr/local/hadoop 3. 回到Linux的操作目录 我们把…...

Mysql 安装教程和Workbench的安装教程以及workbench的菜单栏汉化

Mysql 安装教程和Workbench的安装教程 详细请参考我的文件 Mysql 安装教程和Workbench的安装教程 或者下载我的资源Mysql 安装教程和Workbench的安装教程 汉化菜单 英文版菜单文件&#xff1a;下载链接 汉化版菜单文件&#xff1a;下载链接 默认情况下&#xff0c;安…...

失物招领|校园失物招领系统|基于Springboot的校园失物招领系统设计与实现(源码+数据库+文档)

校园失物招领系统目录 目录 基于Springboot的校园失物招领系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、 管理员功能实现 (1) 失物招领管理 (2) 寻物启事管理 (3) 公告管理 (4) 公告类型管理 2、用户功能实现 (1) 失物招领 (2) 寻物启事 (3) 公告 …...

一条不太简单的TEX学习之路

目录 rule raisebox \includegraphics newenviro 、\vspace \stretch \setlength 解释&#xff1a; 总结&#xff1a; 、\linespread newcommand \par 小四 \small simple 、mutiput画网格 解释&#xff1a; 图案解释&#xff1a; xetex pdelatex etc index 报…...

如何为AI开发选择合适的服务器?

选择适合的服务器可以为您的AI项目带来更高的效率&#xff0c;确保最佳性能、可扩展性和可靠性&#xff0c;从而实现无缝的开发与部署。 选择适合的AI开发服务器可能并不容易。您需要一台能够处理大量计算和大型数据集的服务器&#xff0c;同时它还需要符合您的预算并易于管理…...

doris:审计日志

Doris 提供了对于数据库操作的审计能力&#xff0c;可以记录用户对数据库的登陆、查询、修改操作。在 Doris 中&#xff0c;可以直接通过内置系统表查询审计日志&#xff0c;也可以直接查看 Doris 的审计日志文件。 开启审计日志​ 通过全局变量 enable_audit_plugin 可以随时…...

CSS中的transition与渐变

目录 一、CSS transition 1. 核心属性 简写语法 2. 子属性详解 2.1 transition-property 2.2 transition-duration 2.3 transition-timing-function 2.4 transition-delay 3. 使用场景示例 3.1 悬停效果&#xff08;Hover&#xff09; 3.2 展开/收起动画 3.3 动态移…...

AI + 医疗 Qwq大模型离线本地应用

通义千问Qwq-32b-FP16可用于社区医院、乡镇卫生院、诊所等小型医疗机构&#xff0c;替代专业合理用药系统&#xff0c;作为药品知识库&#xff0c;实现以下功能&#xff1a; 药品信息智能查询&#xff1a;检索药品的详细说明书、适应症、禁忌症、不良反应及药物相互作用等关键信…...

大数据环境搭建

目录 一&#xff1a;虚拟机:VirtualBox 二&#xff1a;Shell工具:MobaXterm 三&#xff1a;安装脚本 四&#xff1a;JDK和Hadoop 4.1&#xff1a;安装 4.2&#xff1a;启动 4.3&#xff1a;Hadoop可视化访问 4.4&#xff1a;关机 一&#xff1a;虚拟机:VirtualBox Virt…...

七天免登录 为什么不能用seesion,客户端的http请求自动携带cookei的机制(比较重要)涉及HTTP规范

如果是七天免登录,和session肯定没关系,因为session不能持久化,主要是客户端一旦关闭,seesion就失效了/// 所以必须是能持久化的&#xff0c;这就清晰了&#xff0c;要莫在的服务器保存&#xff0c;要摸在客户端设置 cook机制 1. 使用Cookie实现七天免登录 前端&#xff08;登…...

从PGC到AIGC:海螺AI多模态内容生成系统的技术革命

一、内容生产的范式迁移&#xff1a;从PGC到AIGC的进化之路 在数字内容生产的历史长河中&#xff0c;人类经历了三次重大范式转变&#xff1a;专业生成内容&#xff08;PGC&#xff09;的工业化生产、用户生成内容&#xff08;UGC&#xff09;的全民创作浪潮&#xff0c;以及当…...

常考计算机操作系统面试习题(三上)

目录 1. 为何要引入与设备的无关性&#xff1f;如何实现设备的独立性&#xff1f; 2. 页面置换先进先出算法 3. 页面置换先进先出算法&#xff0c;4个页框 4. 进程优先级调度算法 5. 短作业优先调度策略 6. 平均内存访问时间计算 7. 页式存储和段式存储的物理地址计算 …...

数据结构之双向链表-初始化链表-头插法-遍历链表-获取尾部结点-尾插法-指定位置插入-删除节点-释放链表——完整代码

数据结构之双向链表-初始化链表-头插法-遍历链表-获取尾部结点-尾插法-指定位置插入-删除节点-释放链表——完整代码 #include <stdio.h> #include <stdlib.h>typedef int ElemType;typedef struct node{ElemType data;struct node *next, *prev; }Node;//初化链表…...

一键部署 GPU Kind 集群,体验 vLLM 极速推理

随着 Kubernetes 在大模型训练和推理领域的广泛应用&#xff0c;越来越多的开发者需要在本地环境中搭建支持 GPU 的 Kubernetes 集群&#xff0c;以便进行测试和开发。大家都知道&#xff0c;本地搭建 Kubernetes 集群通常可以使用 Kind&#xff08;Kubernetes IN Docker&#…...

C/C++蓝桥杯算法真题打卡(Day6)

一、P8615 [蓝桥杯 2014 国 C] 拼接平方数 - 洛谷 方法一&#xff1a;算法代码&#xff08;字符串分割法&#xff09; #include<bits/stdc.h> // 包含标准库中的所有头文件&#xff0c;方便编程 using namespace std; // 使用标准命名空间&#xff0c;避免每次调用…...

【C++】入门

1.命名空间 1.1 namespace的价值 在C/C中&#xff0c;变量&#xff0c;函数和后面要学到的类都是大量存在的&#xff0c;这些变量&#xff0c;函数和类的名称将存在于全局作用域中&#xff0c;可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化&#xff0c;…...

CUDA 学习(2)——CUDA 介绍

GeForce 256 是英伟达 1999 年开发的第一个 GPU&#xff0c;最初用作显示器上渲染高端图形&#xff0c;只用于像素计算。 在早期&#xff0c;OpenGL 和 DirectX 等图形 API 是与 GPU 唯一的交互方式。后来&#xff0c;人们意识到 GPU 除了用于渲染图形图像外&#xff0c;还可以…...

构建自定义MCP天气服务器:集成Claude for Desktop与实时天气数据

构建自定义MCP天气服务器:集成Claude for Desktop与实时天气数据 概述 本文将指导开发者构建一个MCP(Model Control Protocol)天气服务器,通过暴露get-alerts和get-forecast工具,为Claude for Desktop等客户端提供实时天气数据支持。该方案解决了传统LLM无法直接获取天气…...

[Lc_2 二叉树dfs] 布尔二叉树的值 | 根节点到叶节点数字之和 | 二叉树剪枝

目录 1.计算布尔二叉树的值 题解 2.求根节点到叶节点数字之和 3. 二叉树剪枝 题解 1.计算布尔二叉树的值 链接&#xff1a;2331. 计算布尔二叉树的值 给你一棵 完整二叉树 的根&#xff0c;这棵树有以下特征&#xff1a; 叶子节点 要么值为 0 要么值为 1 &#xff0c;其…...

搜广推校招面经五十六

字节推荐算法 一、Attention的复杂度是多少&#xff1f; 见【搜广推校招面经三十八】 二、如何对普适性强的物品&#xff08;如新华字典&#xff09;设计指标进行降权 2.1. 问题背景 普适性强的物品&#xff08;如新华字典&#xff09;在推荐系统或搜索排序中可能频繁出现…...

ZYNQ的cache原理与一致性操作

在Xilinx Zynq SoC中&#xff0c;Cache管理是确保处理器与外部设备&#xff08;如FPGA逻辑、DMA控制器&#xff09;之间数据一致性的关键。Zynq的ARM Cortex-A9处理器包含L1 Cache&#xff08;指令/数据&#xff09;和L2 Cache&#xff0c;其刷新&#xff08;Flush/Invalidate&…...

安装React开发者工具

我们在说组件之前&#xff0c;需要先安装一下React官方推出的开发者工具&#xff0c;首先我们分享在线安装方式 首先打开谷歌网上应用商店(针对谷歌浏览器)&#xff0c;在输入框内搜索react&#xff0c;安装如下插件&#xff1a; 注意安装提供方为Facebook的插件&#xff0c;这…...

多层感知机

多层感知机&#xff08;Multilayer Perceptron&#xff0c;简称 MLP&#xff09;是一种基于前馈神经网络&#xff08;Feedforward Neural Network&#xff09;的深度学习模型&#xff0c;由多个神经元层组成&#xff0c;每一层与前一层全连接。它包括至少一个隐藏层&#xff08…...

2025年渗透测试面试题总结- PingCAP安全工程师(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 PingCAP安全工程师 一、SQL注入判断数据库类型技术分析 1. 常规判断方法 2. 盲注场景下的判断 3. 补…...

CAD模型导入Geant4

CADMesh是一个开源项目&#xff0c;专门用于将STL格式的CAD模型导入Geant4。以下是使用CADMesh操作STL模型的步骤&#xff1a; 准备工作 下载CADMesh开源代码&#xff1a;可以从GitHub或Gitee下载CADMesh的开源代码。 将CAD模型转换为STL格式&#xff1a;在CAD软件中创建几何…...

DeepSORT 目标追踪算法详解

DeepSORT&#xff08;Deep Simple Online and Realtime Tracking&#xff09;是 多目标追踪&#xff08;MOT&#xff09; 领域的经典算法&#xff0c;通过结合目标检测、运动预测和外观特征匹配&#xff0c;实现了高效、稳定的实时追踪。其核心思想是通过 检测驱动追踪&#xf…...

mne溯源后的数据初步处理方法

文章目录 导入库 Yeo2011_7Networks_N1000绘制一些圆球来代表区域大小和强度 单网络绘制和扩展的方式AI补充一下背景知识&#x1f4da; **背景与研究来源**&#x1f9e0; **7 个功能网络的定义**&#x1f4c2; **标签数据获取**&#x1f50d; **标签文件内容解析**&#x1f6e0…...

基于STM32进行FFT滤波并计算插值DA输出

文章目录 一、前言背景二、项目构思1. 确定FFT点数、采样率、采样点数2. 双缓存设计 三、代码实现1. STM32CubeMX配置和HAL库初始化2. 核心代码 四、效果展示和后话五、项目联想与扩展1. 倍频2. 降频3. 插值3.1 线性插值3.2 样条插值 一、前言背景 STM32 对 AD 采样信号进行快…...

【用 Trace读源码】PlanAgent 执行流程

前提条件 在 Trae 中打开 OpenManus 工程&#xff0c;使用 build 模式&#xff0c;模型选择 claude-sonnet-3.7 提示词 分析 agent/planning.py 中 main 方法及相关类的执行流程&#xff0c;以流程图的方式展示PlanningAgent 执行流程图 以下流程图展示了 PlanningAgent 类…...

AI代码编辑器:Cursor和Trae

Cursor 定义&#xff1a;Cursor 是一款基于AI的代码编辑器&#xff0c;它继承了VS Code的核心功能&#xff0c;并在此基础上增加了深度AI支持。它支持代码生成、优化、重构以及调试等功能&#xff0c;提供直观的Diff视图和自动补全功能&#xff0c;是一款功能强大的编程工具。…...

LSM-Tree(Log-Structured Merge-Tree)详解

1. 什么是 LSM-Tree? LSM-Tree(Log-Structured Merge-Tree)是一种 针对写优化的存储结构,广泛用于 NoSQL 数据库(如 LevelDB、RocksDB、HBase、Cassandra)等系统。 它的核心思想是: 写入时只追加写(Append-Only),将数据先写入内存缓冲区(MemTable)。内存数据满后…...

介绍一个测试boostrap表格插件的好网站!

最近在开发一个物业管理系统。用到bootstrap的表格插件bootstrap table&#xff0c;官方地址&#xff1a; https://bootstrap-table.com/ 因为是英文界面&#xff0c;对国人不是很友好。后来发现了IT小书童网站 IT小书童 - 为程序员提供优质教程和文档 网站&#xff1a; IT…...

虚拟路由与单页应用(SPA):详解

在单页应用&#xff08;SPA&#xff0c;Single Page Application&#xff09;中&#xff0c;虚拟路由&#xff08;也称为前端路由&#xff09;是一种关键的技术&#xff0c;用于管理页面导航和状态变化&#xff0c;而无需重新加载整个页面。为了帮助你更好地理解这一概念&#…...

基于树莓派3B+的人脸识别实践:Python与C联合开发

基于树莓派3B的人脸识别实践&#xff1a;Python与C联合开发 引言 树莓派因其小巧的体积和丰富的扩展性&#xff0c;成为嵌入式开发的理想平台。本文将分享如何通过Python与C语言联合开发&#xff0c;在树莓派3B上实现从硬件控制、摄像头拍照到百度API人脸比对的完整流程。项目…...

尝试使用Tauri2+Django+React项目(2)

前言 尝试使用tauri2DjangoReact的项目-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146403103在前面笔者不知道怎么做&#xff0c;搞了半天 笔者看到官网&#xff0c;原来可以使用二进制文件&#xff0c;好好好 嵌入外部二进制文件 | Taurihttps://v2.taur…...

Qt桌面客户端跨平台开发实例

在Windows平台上&#xff0c;桌面客户端软件通常使用C/C语言和Qt跨平台开发框架进行开发。因此&#xff0c;大部分代码可以运行于不同平台环境&#xff0c;但是程序运行依赖的三方库以及代码中一些平台相关的头文件和接口需要进行平台兼容。本文以windows桌面端应用迁移到Linux…...

c++进阶之------红黑树

一、概念 红黑树&#xff08;Red-Black Tree&#xff09;是一种自平衡二叉查找树&#xff0c;它在计算机科学的许多领域中都有广泛应用&#xff0c;比如Java中的TreeMap和C中的set/map等数据结构的底层实现。红黑树通过在每个节点上增加一个颜色属性&#xff08;红色或黑色&am…...

政安晨【超级AI工作流】—— 使用Dify通过工作流对接ComfyUI实现多工作流协同

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 目录 一、准备工作 Dify跑起来 ollama局域网化配置 Dify配置并验证 启动ComfyUI 二、…...

javaweb开发以及部署

先说一个阿里云学生无门槛免费领一年2核4g服务器的方法&#xff1a; 阿里云服务器学生无门槛免费领一年2核4g_阿里云学生认证免费服务器-CSDN博客 Java Web开发是使用Java编程语言开发Web应用程序的过程&#xff0c;通常涵盖了使用Java EE&#xff08;Java Enterprise Edition…...

树莓派5介绍与系统安装

简介 Raspberry Pi 5采用运行频率为2.4GHz的64位四核Arm Cortex-A76处理器&#xff0c;与Raspberry Pi 4相比&#xff0c; CPU性能提高了2至3倍。此外&#xff0c;它还配备了一个800MHz的VideoCore VII GPU&#xff0c;可以提供大幅度的图形 性能提升&#xff0c;通过HDMI实现…...

菜鸟之路Day25一一前端工程化(二)

菜鸟之路Day25一一前端工程化&#xff08;二&#xff09; 作者&#xff1a;blue 时间&#xff1a;2025.3.19 文章目录 菜鸟之路Day25一一前端工程化&#xff08;二&#xff09;1.概述2.Element快速入门3.综合案例一.布局二.组件三.Axios异步加载数据1. 生命周期钩子概述2. mo…...

vue如何获取 sessionStorage的值,获取token

// 使用Axios发送请求并处理下载 import axios from axios;const handleDownload () > {const params {warehouseId: selectedWarehouseId.value};const apiUrl /api/materials/wmMatCheck/export-wmMatCheckDetail;axios.get(apiUrl, {params,responseType: blob, // 接…...

图解AUTOSAR_CP_DiagnosticLogAndTrace

AUTOSAR 诊断日志和跟踪(DLT)模块详解 AUTOSAR 经典平台中的诊断和调试关键组件 目录 1. 概述2. DLT模块架构 2.1 模块位置2.2 内部组件2.3 接口定义 3. DLT操作流程 3.1 初始化流程3.2 日志和跟踪消息处理3.3 控制命令处理 4. 数据结构与配置模型 4.1 配置类4.2 消息格式4.3 …...